irctest/irctest/server_tests/message_tags.py

197 lines
8.0 KiB
Python
Raw Normal View History

2020-11-26 05:25:52 +00:00
"""
`IRCv3 message-tags <https://ircv3.net/specs/extensions/message-tags>`_
2020-11-26 05:25:52 +00:00
"""
import pytest
2020-11-26 05:25:52 +00:00
from irctest import cases
from irctest.irc_utils.message_parser import parse_message
from irctest.numerics import ERR_INPUTTOOLONG
from irctest.patma import ANYDICT, ANYSTR, StrRe
2020-11-26 05:25:52 +00:00
2022-04-12 16:48:03 +00:00
class MessageTagsTestCase(cases.BaseServerTestCase):
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
2020-11-26 05:25:52 +00:00
def testBasic(self):
def getAllMessages():
2021-02-22 18:02:13 +00:00
for name in ["alice", "bob", "carol", "dave"]:
2020-11-26 05:25:52 +00:00
self.getMessages(name)
def assertNoTags(line):
# tags start with '@', without tags we start with the prefix,
# which begins with ':'
2021-02-22 18:02:13 +00:00
self.assertEqual(line[0], ":")
2020-11-26 05:25:52 +00:00
msg = parse_message(line)
self.assertEqual(msg.tags, {})
return msg
self.connectClient(
2021-02-22 18:02:13 +00:00
"alice", name="alice", capabilities=["message-tags"], skip_if_cap_nak=True
)
self.joinChannel("alice", "#test")
self.connectClient(
"bob", name="bob", capabilities=["message-tags", "echo-message"]
)
2021-02-22 18:02:13 +00:00
self.joinChannel("bob", "#test")
self.connectClient("carol", name="carol")
self.joinChannel("carol", "#test")
self.connectClient("dave", name="dave", capabilities=["server-time"])
self.joinChannel("dave", "#test")
2020-11-26 05:25:52 +00:00
getAllMessages()
2021-02-22 18:02:13 +00:00
self.sendLine("alice", "@+baz=bat;fizz=buzz PRIVMSG #test hi")
self.getMessages("alice")
bob_msg = self.getMessage("bob")
carol_line = self.getMessage("carol", raw=True)
self.assertMessageMatch(
bob_msg,
command="PRIVMSG",
params=["#test", "hi"],
tags={"+baz": "bat", "msgid": ANYSTR, **ANYDICT},
)
2020-11-26 05:25:52 +00:00
# should not relay a non-client-only tag
2021-02-22 18:02:13 +00:00
self.assertNotIn("fizz", bob_msg.tags)
2020-11-26 05:25:52 +00:00
# carol MUST NOT receive tags
carol_msg = assertNoTags(carol_line)
self.assertMessageMatch(carol_msg, command="PRIVMSG", params=["#test", "hi"])
# dave SHOULD receive server-time tag
2021-02-22 18:02:13 +00:00
dave_msg = self.getMessage("dave")
self.assertMessageMatch(
dave_msg,
command="PRIVMSG",
params=["#test", "hi"],
tags={"time": ANYSTR, **ANYDICT},
)
# dave MUST NOT receive client-only tags
2021-02-22 18:02:13 +00:00
self.assertNotIn("+baz", dave_msg.tags)
2020-11-26 05:25:52 +00:00
getAllMessages()
2021-02-22 18:02:13 +00:00
self.sendLine("bob", "@+bat=baz;+fizz=buzz PRIVMSG #test :hi yourself")
bob_msg = self.getMessage("bob") # bob has echo-message
alice_msg = self.getMessage("alice")
carol_line = self.getMessage("carol", raw=True)
2020-11-26 05:25:52 +00:00
carol_msg = assertNoTags(carol_line)
for msg in [alice_msg, bob_msg]:
self.assertMessageMatch(
msg,
command="PRIVMSG",
params=["#test", "hi yourself"],
tags={"+bat": "baz", "+fizz": "buzz", "msgid": ANYSTR, **ANYDICT},
2021-02-22 18:02:13 +00:00
)
self.assertMessageMatch(
carol_msg,
command="PRIVMSG",
params=["#test", "hi yourself"],
)
2021-02-22 18:02:13 +00:00
self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"])
getAllMessages()
2020-11-26 05:25:52 +00:00
# test TAGMSG and basic escaping
self.sendLine("bob", r"@+buzz=fizz\:buzz;cat=dog;+steel=wootz TAGMSG #test")
2021-02-22 18:02:13 +00:00
bob_msg = self.getMessage("bob") # bob has echo-message
alice_msg = self.getMessage("alice")
2020-11-26 05:25:52 +00:00
# carol MUST NOT receive TAGMSG at all
2021-02-22 18:02:13 +00:00
self.assertEqual(self.getMessages("carol"), [])
# dave MUST NOT receive TAGMSG either, despite having server-time
2021-02-22 18:02:13 +00:00
self.assertEqual(self.getMessages("dave"), [])
2020-11-26 05:25:52 +00:00
for msg in [alice_msg, bob_msg]:
self.assertMessageMatch(
alice_msg,
command="TAGMSG",
params=["#test"],
tags={
"+buzz": "fizz;buzz",
"+steel": "wootz",
"msgid": ANYSTR,
**ANYDICT,
},
)
2021-02-22 18:02:13 +00:00
self.assertNotIn("cat", msg.tags)
self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"])
@pytest.mark.arbitrary_client_tags
@cases.mark_capabilities("message-tags")
2021-03-03 19:37:03 +00:00
@cases.mark_specifications("ircdocs")
def testLengthLimits(self):
self.connectClient(
2021-02-22 18:02:13 +00:00
"alice",
name="alice",
capabilities=["message-tags", "echo-message"],
skip_if_cap_nak=True,
)
2021-02-22 18:02:13 +00:00
self.joinChannel("alice", "#test")
self.connectClient("bob", name="bob", capabilities=["message-tags"])
self.joinChannel("bob", "#test")
self.getMessages("alice")
self.getMessages("bob")
2021-07-02 20:25:45 +00:00
# this is right at the limit of 4094 bytes of server tag data,
# 4096 bytes of client tag data (including the starting '@' and the final ' ')
2021-02-22 18:02:13 +00:00
max_tagmsg = "@foo=bar;+baz=%s TAGMSG #test" % ("a" * 4081,)
self.assertEqual(max_tagmsg.index("TAGMSG"), 4096)
self.sendLine("alice", max_tagmsg)
echo = self.getMessage("alice")
relay = self.getMessage("bob")
self.assertMessageMatch(
echo,
command="TAGMSG",
params=["#test"],
tags={"+baz": "a" * 4081, "msgid": StrRe(".+"), **ANYDICT},
)
self.assertMessageMatch(
relay,
command="TAGMSG",
params=["#test"],
tags={"+baz": "a" * 4081, "msgid": StrRe(".+"), **ANYDICT},
)
2021-02-22 18:02:13 +00:00
self.assertEqual(echo.tags["msgid"], relay.tags["msgid"])
2021-02-22 18:02:13 +00:00
excess_tagmsg = "@foo=bar;+baz=%s TAGMSG #test" % ("a" * 4082,)
self.assertEqual(excess_tagmsg.index("TAGMSG"), 4097)
self.sendLine("alice", excess_tagmsg)
reply = self.getMessage("alice")
self.assertEqual(reply.command, ERR_INPUTTOOLONG)
2021-02-22 18:02:13 +00:00
self.assertEqual(self.getMessages("bob"), [])
2021-02-22 18:02:13 +00:00
max_privmsg = "@foo=bar;+baz=%s PRIVMSG #test %s" % ("a" * 4081, "b" * 496)
# irctest adds the '\r\n' for us, this is right at the limit
self.assertEqual(len(max_privmsg), 4096 + (512 - 2))
2021-02-22 18:02:13 +00:00
self.sendLine("alice", max_privmsg)
echo = self.getMessage("alice")
# the server may still reject this message on the grounds that the final
# parameter is too long to be relayed without truncation, once alice's
# NUH is included. however, if the message was accepted, the tags MUST
2021-03-03 19:37:03 +00:00
# be relayed intact, because they are unquestionably valid. See the
# original context of ERR_INPUTTOOLONG:
# https://defs.ircdocs.horse/defs/numerics.html#err-inputtoolong-417
if echo.command != ERR_INPUTTOOLONG:
relay = self.getMessage("bob")
self.assertMessageMatch(
echo,
command="PRIVMSG",
params=["#test", StrRe("b{400,496}")],
tags={"+baz": "a" * 4081, "msgid": StrRe(".+"), **ANYDICT},
)
self.assertMessageMatch(
relay,
command="PRIVMSG",
params=["#test", StrRe("b{400,496}")],
tags={"+baz": "a" * 4081, "msgid": StrRe(".+"), **ANYDICT},
)
self.assertEqual(echo.tags["msgid"], relay.tags["msgid"])
# message may have been truncated
self.assertIn("b" * 400, echo.params[1])
self.assertEqual(echo.params[1].rstrip("b"), "")
self.assertIn("b" * 400, relay.params[1])
self.assertEqual(relay.params[1].rstrip("b"), "")
2021-02-22 18:02:13 +00:00
excess_privmsg = "@foo=bar;+baz=%s PRIVMSG #test %s" % ("a" * 4082, "b" * 495)
# TAGMSG data is over the limit, but we're within the overall limit for a line
2021-02-22 18:02:13 +00:00
self.assertEqual(excess_privmsg.index("PRIVMSG"), 4097)
self.assertEqual(len(excess_privmsg), 4096 + (512 - 2))
2021-02-22 18:02:13 +00:00
self.sendLine("alice", excess_privmsg)
reply = self.getMessage("alice")
self.assertEqual(reply.command, ERR_INPUTTOOLONG)
2021-02-22 18:02:13 +00:00
self.assertEqual(self.getMessages("bob"), [])