end\r
\r
\r
+# We extend the Regexp class with an Irc module which will contain some\r
+# Irc-specific regexps\r
+#\r
+class Regexp\r
+\r
+ # We start with some general-purpose ones which will be used in the\r
+ # Irc module too, but are useful regardless\r
+ DIGITS = /\d+/\r
+ HEX_DIGIT = /[0-9A-Fa-f]/\r
+ HEX_DIGITS = /#{HEX_DIGIT}+/\r
+ HEX_OCTET = /#{HEX_DIGIT}#{HEX_DIGIT}?/\r
+ DEC_OCTET = /[01]?\d?\d|2[0-4]\d|25[0-5]/\r
+ DEC_IP_ADDR = /#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}/\r
+ HEX_IP_ADDR = /#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}/\r
+ IP_ADDR = /#{DEC_IP_ADDR}|#{HEX_IP_ADDR}/\r
+\r
+ # IPv6, from Resolv::IPv6, without the \A..\z anchors\r
+ HEX_16BIT = /#{HEX_DIGIT}{1,4}/\r
+ IP6_8Hex = /(?:#{HEX_16BIT}:){7}#{HEX_16BIT}/\r
+ IP6_CompressedHex = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)/\r
+ IP6_6Hex4Dec = /((?:#{HEX_16BIT}:){6,6})#{DEC_IP_ADDR}/\r
+ IP6_CompressedHex4Dec = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}:)*)#{DEC_IP_ADDR}/\r
+ IP6_ADDR = /(?:#{IP6_8Hex})|(?:#{IP6_CompressedHex})|(?:#{IP6_6Hex4Dec})|(?:#{IP6_CompressedHex4Dec})/\r
+\r
+ # We start with some IRC related regular expressions, used to match\r
+ # Irc::User nicks and users and Irc::Channel names\r
+ #\r
+ # For each of them we define two versions of the regular expression:\r
+ # * a generic one, which should match for any server but may turn out to\r
+ # match more than a specific server would accept\r
+ # * an RFC-compliant matcher\r
+ #\r
+ module Irc\r
+\r
+ # Channel-name-matching regexps\r
+ CHAN_FIRST = /[#&+]/\r
+ CHAN_SAFE = /![A-Z0-9]{5}/\r
+ CHAN_ANY = /[^\x00\x07\x0A\x0D ,:]/\r
+ GEN_CHAN = /(?:#{CHAN_FIRST}|#{CHAN_SAFE})#{CHAN_ANY}+/\r
+ RFC_CHAN = /#{CHAN_FIRST}#{CHAN_ANY}{1,49}|#{CHAN_SAFE}#{CHAN_ANY}{1,44}/\r
+\r
+ # Nick-matching regexps\r
+ SPECIAL_CHAR = /[\x5b-\x60\x7b-\x7d]/\r
+ NICK_FIRST = /#{SPECIAL_CHAR}|[[:alpha:]]/\r
+ NICK_ANY = /#{SPECIAL_CHAR}|[[:alnum:]]|-/\r
+ GEN_NICK = /#{NICK_FIRST}#{NICK_ANY}+/\r
+ RFC_NICK = /#{NICK_FIRST}#{NICK_ANY}{0,8}/\r
+\r
+ USER_CHAR = /[^\x00\x0a\x0d @]/\r
+ GEN_USER = /#{USER_CHAR}+/\r
+\r
+ # Host-matching regexps\r
+ HOSTNAME_COMPONENT = /[[:alnum:]](?:[[:alnum:]]|-)*[[:alnum:]]*/\r
+ HOSTNAME = /#{HOSTNAME_COMPONENT}(?:\.#{HOSTNAME_COMPONENT})*/\r
+ HOSTADDR = /#{IP_ADDR}|#{IP6_ADDR}/\r
+\r
+ GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/\r
+\r
+ # # FreeNode network replaces the host of affiliated users with\r
+ # # 'virtual hosts' \r
+ # # FIXME we need the true syntax to match it properly ...\r
+ # PDPC_HOST_PART = /[0-9A-Za-z.-]+/\r
+ # PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/\r
+\r
+ # # NOTE: the final optional and non-greedy dot is needed because some\r
+ # # servers (e.g. FreeNode) send the hostname of the services as "services."\r
+ # # which is not RFC compliant, but sadly done.\r
+ # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/ \r
+\r
+ # Sadly, different networks have different, RFC-breaking ways of cloaking\r
+ # the actualy host address: see above for an example to handle FreeNode.\r
+ # Another example would be Azzurra, wich also inserts a "=" in the\r
+ # cloacked host. So let's just not care about this and go with the simplest\r
+ # thing:\r
+ GEN_HOST_EXT = /\S+/\r
+\r
+ # User-matching Regexp\r
+ GEN_USER_ID = /(#{GEN_NICK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/\r
+\r
+ # Things such has the BIP proxy send invalid nicks in a complete netmask,\r
+ # so we want to match this, rather: this matches either a compliant nick\r
+ # or a a string with a very generic nick, a very generic hostname after an\r
+ # @ sign, and an optional user after a !\r
+ BANG_AT = /#{GEN_NICK}|\S+?(?:!\S+?)?@\S+?/\r
+\r
+ # # For Netmask, we want to allow wildcards * and ? in the nick\r
+ # # (they are already allowed in the user and host part\r
+ # GEN_NICK_MASK = /(?:#{NICK_FIRST}|[?*])?(?:#{NICK_ANY}|[?*])+/\r
+\r
+ # # Netmask-matching Regexp\r
+ # GEN_MASK = /(#{GEN_NICK_MASK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/\r
+\r
+ end\r
+\r
+end\r
+\r
+\r
module Irc\r
\r
\r
# Now we can see if the given string _str_ is an actual Netmask\r
if str.respond_to?(:to_str)\r
case str.to_str\r
- when /^(?:(\S+?)(?:!(\S+)@(?:(\S+))?)?)?$/\r
+ # We match a pretty generic string, to work around non-compliant\r
+ # servers\r
+ when /^(?:(\S+?)(?:(?:!(\S+?))?@(\S+))?)?$/\r
# We do assignment using our internal methods\r
self.nick = $1\r
self.user = $2\r
end\r
end\r
\r
- # A Netmask is easily converted to a String for the usual representation\r
+ # A Netmask is easily converted to a String for the usual representation.\r
+ # We skip the user or host parts if they are "*", unless we've been asked\r
+ # for the full form\r
#\r
+ def to_s\r
+ ret = nick.dup\r
+ ret << "!" << user unless user == "*"\r
+ ret << "@" << host unless host == "*"\r
+ return ret\r
+ end\r
def fullform\r
"#{nick}!#{user}@#{host}"\r
end\r
- alias :to_s :fullform\r
\r
# Converts the receiver into a Netmask with the given (optional)\r
# server/casemap association. We return self unless a conversion\r
#\r
def matches?(arg)\r
cmp = arg.to_irc_netmask(:casemap => casemap)\r
- debug "Matching #{self.fullform} against #{arg.fullform}"\r
+ debug "Matching #{self.fullform} against #{arg.inspect} (#{cmp.fullform})"\r
[:nick, :user, :host].each { |component|\r
us = self.send(component).irc_downcase(casemap)\r
them = cmp.send(component).irc_downcase(casemap)\r
# Checks if a User is well-known or not by looking at the hostname and user\r
#\r
def known?\r
- return nick!= "*" && user!="*" && host!="*"\r
+ return nick != "*" && user != "*" && host != "*"\r
end\r
\r
# Is the user away?\r
#\r
def initialize(text="", set_by="", set_on=Time.new)\r
@text = text\r
- @set_by = set_by.to_irc_user\r
+ @set_by = set_by.to_irc_netmask\r
@set_on = set_on\r
end\r
\r
:typec => nil, # Type C: needs a parameter when set\r
:typed => nil # Type D: must not have a parameter\r
},\r
- :channellen => 200,\r
- :chantypes => "#&",\r
+ :channellen => 50,\r
+ :chantypes => "#&!+",\r
:excepts => nil,\r
:idchan => {},\r
:invex => nil,\r
:network => nil,\r
:nicklen => 9,\r
:prefix => {\r
- :modes => 'ov'.scan(/./),\r
- :prefixes => '@+'.scan(/./)\r
+ :modes => [:o, :v],\r
+ :prefixes => [:"@", :+]\r
},\r
:safelist => nil,\r
:statusmsg => nil,\r
# Resets the Channel and User list\r
#\r
def reset_lists\r
- @users.each { |u|\r
+ @users.reverse_each { |u|\r
delete_user(u)\r
}\r
- @channels.each { |u|\r
+ @channels.reverse_each { |u|\r
delete_channel(u)\r
}\r
end\r
def clear\r
reset_lists\r
reset_capabilities\r
+ @hostname = @version = @usermodes = @chanmodes = nil\r
end\r
\r
# This method is used to parse a 004 RPL_MY_INFO line\r
groups.each { |g|\r
k, v = g.split(':')\r
@supports[key][k] = v.to_i || 0\r
+ if @supports[key][k] == 0\r
+ warn "Deleting #{key} limit of 0 for #{k}"\r
+ @supports[key].delete(k)\r
+ end\r
}\r
}\r
when :chanmodes\r
channel_names.each { |n|\r
count += 1 if k.include?(n[0])\r
}\r
- raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]\r
+ # raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]\r
+ warn "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]\r
}\r
\r
# So far, everything is fine. Now create the actual Channel\r