mirror of https://github.com/progval/irctest.git
197 lines
8.0 KiB
Python
197 lines
8.0 KiB
Python
"""
|
|
`IRCv3 message-tags <https://ircv3.net/specs/extensions/message-tags>`_
|
|
"""
|
|
|
|
import pytest
|
|
|
|
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
|
|
|
|
|
|
class MessageTagsTestCase(cases.BaseServerTestCase):
|
|
@pytest.mark.arbitrary_client_tags
|
|
@cases.mark_capabilities("message-tags")
|
|
def testBasic(self):
|
|
def getAllMessages():
|
|
for name in ["alice", "bob", "carol", "dave"]:
|
|
self.getMessages(name)
|
|
|
|
def assertNoTags(line):
|
|
# tags start with '@', without tags we start with the prefix,
|
|
# which begins with ':'
|
|
self.assertEqual(line[0], ":")
|
|
msg = parse_message(line)
|
|
self.assertEqual(msg.tags, {})
|
|
return msg
|
|
|
|
self.connectClient(
|
|
"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"]
|
|
)
|
|
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")
|
|
getAllMessages()
|
|
|
|
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},
|
|
)
|
|
# should not relay a non-client-only tag
|
|
self.assertNotIn("fizz", bob_msg.tags)
|
|
# 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
|
|
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
|
|
self.assertNotIn("+baz", dave_msg.tags)
|
|
getAllMessages()
|
|
|
|
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)
|
|
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},
|
|
)
|
|
self.assertMessageMatch(
|
|
carol_msg,
|
|
command="PRIVMSG",
|
|
params=["#test", "hi yourself"],
|
|
)
|
|
self.assertEqual(alice_msg.tags["msgid"], bob_msg.tags["msgid"])
|
|
getAllMessages()
|
|
|
|
# test TAGMSG and basic escaping
|
|
self.sendLine("bob", r"@+buzz=fizz\:buzz;cat=dog;+steel=wootz TAGMSG #test")
|
|
bob_msg = self.getMessage("bob") # bob has echo-message
|
|
alice_msg = self.getMessage("alice")
|
|
# carol MUST NOT receive TAGMSG at all
|
|
self.assertEqual(self.getMessages("carol"), [])
|
|
# dave MUST NOT receive TAGMSG either, despite having server-time
|
|
self.assertEqual(self.getMessages("dave"), [])
|
|
for msg in [alice_msg, bob_msg]:
|
|
self.assertMessageMatch(
|
|
alice_msg,
|
|
command="TAGMSG",
|
|
params=["#test"],
|
|
tags={
|
|
"+buzz": "fizz;buzz",
|
|
"+steel": "wootz",
|
|
"msgid": ANYSTR,
|
|
**ANYDICT,
|
|
},
|
|
)
|
|
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")
|
|
@cases.mark_specifications("ircdocs")
|
|
def testLengthLimits(self):
|
|
self.connectClient(
|
|
"alice",
|
|
name="alice",
|
|
capabilities=["message-tags", "echo-message"],
|
|
skip_if_cap_nak=True,
|
|
)
|
|
self.joinChannel("alice", "#test")
|
|
self.connectClient("bob", name="bob", capabilities=["message-tags"])
|
|
self.joinChannel("bob", "#test")
|
|
self.getMessages("alice")
|
|
self.getMessages("bob")
|
|
|
|
# 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 ' ')
|
|
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},
|
|
)
|
|
self.assertEqual(echo.tags["msgid"], relay.tags["msgid"])
|
|
|
|
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)
|
|
self.assertEqual(self.getMessages("bob"), [])
|
|
|
|
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))
|
|
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
|
|
# 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"), "")
|
|
|
|
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
|
|
self.assertEqual(excess_privmsg.index("PRIVMSG"), 4097)
|
|
self.assertEqual(len(excess_privmsg), 4096 + (512 - 2))
|
|
self.sendLine("alice", excess_privmsg)
|
|
reply = self.getMessage("alice")
|
|
self.assertEqual(reply.command, ERR_INPUTTOOLONG)
|
|
self.assertEqual(self.getMessages("bob"), [])
|