irclib: Fix support of power prefix chars in RPL_NAMREPLY

nickFromHostmask now (legitimately) complains when it's getting @ or !
at the beginning of a hostmask; so we need to strip them before passing
it to nickFromHostmask.

Then re-add them before calling c.addUser, because it uses them to
sort users in the right sets (ops/halfops/voices).

Additionally, this commit replaces the hardcoded set of prefix chars
(`@%+&~!`) with the one advertised in ISUPPORT when possible.
This commit is contained in:
Valentin Lorentz 2021-07-16 19:50:12 +02:00
parent aa6bd7257d
commit d308329461
2 changed files with 56 additions and 7 deletions

View File

@ -389,15 +389,15 @@ class ChannelState(utils.python.Object):
"""Returns whether the given nick is an halfop, or an op."""
return nick in self.halfops or nick in self.ops
def addUser(self, user):
def addUser(self, user, prefix_chars='@%+&~!'):
"Adds a given user to the ChannelState. Power prefixes are handled."
nick = user.lstrip('@%+&~!')
nick = user.lstrip(prefix_chars)
if not nick:
return
# & is used to denote protected users in UnrealIRCd
# ~ is used to denote channel owner in UnrealIRCd
# ! is used to denote protected users in UltimateIRCd
while user and user[0] in '@%+&~!':
while user and user[0] in prefix_chars:
(marker, user) = (user[0], user[1:])
assert user, 'Looks like my caller is passing chars, not nicks.'
if marker in '@&~!':
@ -963,13 +963,27 @@ class IrcState(IrcCommandDispatcher, log.Firewalled):
if channel not in self.channels:
self.channels[channel] = ChannelState()
c = self.channels[channel]
# Set of prefixes servers may append before a NAMES reply when
# the user is op/halfop/voice/...
# https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.15
prefix = self.supported.get('PREFIX')
if prefix is None:
prefix_chars = '@%+&~!' # see the comments in addUser
else:
prefix_chars = prefix.split(')', 1)[-1]
for item in items.split():
if ircutils.isUserHostmask(item):
name = ircutils.nickFromHostmask(item)
self.nicksToHostmasks[name] = name
stripped_item = item.lstrip(prefix_chars)
item_prefix = item[0:-len(stripped_item)]
if ircutils.isUserHostmask(stripped_item):
nick = ircutils.nickFromHostmask(stripped_item)
self.nicksToHostmasks[nick] = nick
name = item_prefix + nick
else:
name = item
c.addUser(name)
c.addUser(name, prefix_chars=prefix_chars)
if type == '@':
c.modes['s'] = None

View File

@ -529,6 +529,41 @@ class IrcStateTestCase(SupyTestCase):
st = irclib.IrcState()
self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1)
def testNamreply(self):
"""RPL_NAMREPLY / reply to NAMES"""
# Just nicks (à la RFC 1459 + some common prefix chars)
st = irclib.IrcState()
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
args=('*', '=', '#chan', 'nick1 @nick2 ~@nick3')))
chan_st = st.channels['#chan']
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', 'nick3']))
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2', 'nick3']))
self.assertEqual(st.nicksToHostmasks, ircutils.IrcDict({}))
# with userhost-in-names
st = irclib.IrcState()
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
args=('*', '=', '#chan', 'nick1!u1@h1 @nick2!u2@h2 ~@nick3!u3@h3')))
chan_st = st.channels['#chan']
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', 'nick3']))
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2', 'nick3']))
self.assertEqual(st.nicksToHostmasks['nick1'], 'nick1')
self.assertEqual(st.nicksToHostmasks['nick2'], 'nick2')
self.assertEqual(st.nicksToHostmasks['nick3'], 'nick3')
# Prefixed with chars not in ISUPPORT PREFIX
st = irclib.IrcState()
st.supported['PREFIX'] = '(ov)@+'
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
args=('*', '=', '#chan', 'nick1!u1@h1 @nick2!u2@h2 ~@nick3!u3@h3')))
chan_st = st.channels['#chan']
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', '~@nick3']))
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2']))
self.assertEqual(st.nicksToHostmasks['nick1'], 'nick1')
self.assertEqual(st.nicksToHostmasks['nick2'], 'nick2')
self.assertEqual(st.nicksToHostmasks['~@nick3'], '~@nick3')
class IrcCapsTestCase(SupyTestCase, CapNegMixin):
def testReqLineLength(self):