From 9268356e973f8c0553df69027c9b8dc626d0be3e Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Fri, 25 Oct 2019 23:07:31 +0200 Subject: [PATCH] Split 'CAP REQ' commands to not exceed 512 bytes. --- src/irclib.py | 28 ++++++++++++++++++++-------- test/test_irclib.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/irclib.py b/src/irclib.py index 069a3f5be..2847b1b4c 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -32,6 +32,7 @@ import copy import time import random import base64 +import textwrap import collections try: @@ -1300,19 +1301,24 @@ class Irc(IrcCommandDispatcher, log.Firewalled): self._addCapabilities(msg.args[3]) elif len(msg.args) == 3: # End of LS self._addCapabilities(msg.args[2]) - common_supported_capabilities = set(self.state.capabilities_ls) & \ - self.REQUEST_CAPABILITIES + if 'sasl' in self.state.capabilities_ls: s = self.state.capabilities_ls['sasl'] if s is not None: self.filterSaslMechanisms(set(s.split(','))) + + # Normally at this point, self.state.capabilities_ack should be + # empty; but let's just make sure we're not requesting the same + # caps twice for no reason. + new_caps = ( + set(self.state.capabilities_ls) & + self.REQUEST_CAPABILITIES - + self.state.capabilities_ack) # NOTE: Capabilities are requested in alphabetic order, because # sets are unordered, and their "order" is nondeterministic. # This is needed for the tests. - if common_supported_capabilities: - caps = ' '.join(sorted(common_supported_capabilities)) - self.sendMsg(ircmsgs.IrcMsg(command='CAP', - args=('REQ', caps))) + if new_caps: + self._requestCaps(new_caps) else: self.endCapabilityNegociation() else: @@ -1353,9 +1359,15 @@ class Irc(IrcCommandDispatcher, log.Firewalled): self.REQUEST_CAPABILITIES - self.state.capabilities_ack) if common_supported_unrequested_capabilities: - caps = ' '.join(sorted(common_supported_unrequested_capabilities)) + self._requestCaps(common_supported_unrequested_capabilities) + + def _requestCaps(self, caps): + caps = ' '.join(sorted(caps)) + # textwrap works here because in ASCII, all chars are 1 bytes: + cap_lines = textwrap.wrap(caps, MAX_LINE_SIZE-len('CAP REQ :')) + for cap_line in cap_lines: self.sendMsg(ircmsgs.IrcMsg(command='CAP', - args=('REQ', caps))) + args=('REQ', cap_line))) def monitor(self, targets): """Increment a counter of how many callbacks monitor each target; diff --git a/test/test_irclib.py b/test/test_irclib.py index 10622233d..76d8dbdd5 100644 --- a/test/test_irclib.py +++ b/test/test_irclib.py @@ -379,6 +379,37 @@ class IrcStateTestCase(SupyTestCase): st = irclib.IrcState() self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1) +class IrcCapsTestCase(SupyTestCase): + def testReqLineLength(self): + self.irc = irclib.Irc('test') + + m = self.irc.takeMsg() + self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m) + self.failUnless(m.args == ('LS', '302'), 'Expected CAP LS 302, got %r.' % m) + + m = self.irc.takeMsg() + self.failUnless(m.command == 'NICK', 'Expected NICK, got %r.' % m) + + m = self.irc.takeMsg() + self.failUnless(m.command == 'USER', 'Expected USER, got %r.' % m) + + self.irc.REQUEST_CAPABILITIES = set(['a'*400, 'b'*400]) + caps = ' '.join(self.irc.REQUEST_CAPABILITIES) + self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', + args=('*', 'LS', '*', 'a'*400))) + self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', + args=('*', 'LS', 'b'*400))) + + m = self.irc.takeMsg() + self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m) + self.assertEqual(m.args[0], 'REQ', m) + self.assertEqual(m.args[1], 'a'*400) + + m = self.irc.takeMsg() + self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m) + self.assertEqual(m.args[0], 'REQ', m) + self.assertEqual(m.args[1], 'b'*400) + class IrcTestCase(SupyTestCase): def setUp(self): self.irc = irclib.Irc('test')