Browse Source

Track usermask and account for it in the message splitting

Since the ircd will prefix each message with the origin usermask when broadcasting to the other users, it will have to split or truncate the message sent by the user. Charybdis and ratbox silently truncate it.
master
JustAnotherArchivist 4 years ago
parent
commit
609829bf55
1 changed files with 40 additions and 3 deletions
  1. +40
    -3
      http2irc.py

+ 40
- 3
http2irc.py View File

@@ -288,6 +288,7 @@ class IRCClientProtocol(asyncio.Protocol):
self.pongReceivedEvent = asyncio.Event()
self.sasl = bool(self.config['irc']['certfile'] and self.config['irc']['certkeyfile'])
self.authenticated = False
self.usermask = None

@staticmethod
def nick_command(nick: str):
@@ -298,6 +299,11 @@ class IRCClientProtocol(asyncio.Protocol):
nickb = nick.encode('utf-8')
return b'USER ' + nickb + b' ' + nickb + b' ' + nickb + b' :' + real.encode('utf-8')

def _maybe_set_usermask(self, usermask):
if b'@' in usermask and b'!' in usermask.split(b'@')[0] and all(x not in usermask for x in (b' ', b'*', b'#', b'&')):
self.usermask = usermask
self.logger.debug(f'Usermask is now {usermask!r}')

def connection_made(self, transport):
self.logger.info('IRC connected')
self.transport = transport
@@ -386,11 +392,12 @@ class IRCClientProtocol(asyncio.Protocol):
break
channelB = channel.encode('utf-8')
messageB = message.encode('utf-8')
if len(b'PRIVMSG ' + channelB + b' :' + messageB) > 510:
usermaskPrefixLength = 1 + (len(self.usermask) if self.usermask else 100) + 1
if usermaskPrefixLength + len(b'PRIVMSG ' + channelB + b' :' + messageB) > 510:
self.logger.debug(f'Splitting up into smaller messages')
# Message too long, need to split. First try to split on spaces, then on codepoints. Ideally, would use graphemes between, but that's too complicated.
prefix = b'PRIVMSG ' + channelB + b' :'
prefixLength = len(prefix)
prefixLength = usermaskPrefixLength + len(prefix) # Need to account for the origin prefix included by the ircd when sending to others
maxMessageLength = 510 - prefixLength # maximum length of the message part within each line
messages = []
while message:
@@ -463,7 +470,8 @@ class IRCClientProtocol(asyncio.Protocol):

def message_received(self, message):
self.logger.debug(f'Message received: {message!r}')
if message.startswith(b':'):
rawMessage = message
if message.startswith(b':') and b' ' in message:
# Prefixed message, extract command + parameters (the prefix cannot contain a space)
message = message.split(b' ', 1)[1]

@@ -482,6 +490,13 @@ class IRCClientProtocol(asyncio.Protocol):
self.transport.close()
elif message == b'AUTHENTICATE +':
self.send(b'AUTHENTICATE +')
elif message.startswith(b'900 '): # "You are now logged in", includes the usermask
words = message.split(b' ')
if len(words) >= 3 and b'!' in words[2] and b'@' in words[2]:
if b'!~' not in words[2]:
# At least Charybdis seems to always return the user without a tilde, even if identd failed. Assume no identd and account for that extra tilde.
words[2] = words[2].replace(b'!', b'!~', 1)
self._maybe_set_usermask(words[2])
elif message.startswith(b'903 '): # SASL auth successful
self.authenticated = True
self.send(b'CAP END')
@@ -527,6 +542,28 @@ class IRCClientProtocol(asyncio.Protocol):
asyncio.create_task(self.send_messages())
asyncio.create_task(self.confirm_messages())

# JOIN success
elif message.startswith(b'JOIN ') and not self.usermask:
# If this is my own join message, it should contain the usermask in the prefix
if rawMessage.startswith(b':' + self.config['irc']['nick'].encode('utf-8') + b'!') and b' ' in rawMessage:
usermask = rawMessage.split(b' ', 1)[0][1:]
self._maybe_set_usermask(usermask)

# Services host change
elif message.startswith(b'396 '):
words = message.split(b' ')
if len(words) >= 3:
# Sanity check inspired by irssi src/irc/core/irc-servers.c
if not any(x in words[2] for x in (b'*', b'?', b'!', b'#', b'&', b' ')) and not any(words[2].startswith(x) for x in (b'@', b':', b'-')) and words[2][-1:] != b'-':
if b'@' in words[2]: # user@host
self._maybe_set_usermask(self.config['irc']['nick'].encode('utf-8') + b'!' + words[2])
else: # host (get user from previous mask or settings)
if self.usermask:
user = self.usermask.split(b'@')[0].split(b'!')[1]
else:
user = b'~' + self.config['irc']['nick'].encode('utf-8')
self._maybe_set_usermask(self.config['irc']['nick'].encode('utf-8') + b'!' + user + b'@' + words[2])

def connection_lost(self, exc):
self.logger.info('IRC connection lost')
self.connected = False


Loading…
Cancel
Save