diff --git a/init.lua b/init.lua index c54a1bc..c9f6065 100644 --- a/init.lua +++ b/init.lua @@ -178,7 +178,9 @@ function meta:think() local line = getline(self, 3) if line and #line > 0 then if not self:invoke("OnRaw", line) then - self:handle(parse(line)) + local msg = Message() + msg:fromRFC1459(line) + self:handle(msg) end else break @@ -227,7 +229,8 @@ function meta:whois(nick) while true do local line = getline(self, 3) if line then - local msg = parse(line) + local msg = Message() + msg:fromRFC1249(line) local handler = whoisHandlers[msg.command] if handler then diff --git a/messages.lua b/messages.lua index 62aa108..02c6963 100644 --- a/messages.lua +++ b/messages.lua @@ -2,6 +2,7 @@ local assert = assert local setmetatable = setmetatable local unpack = unpack local pairs = pairs +local insert = table.insert module "irc" @@ -17,6 +18,18 @@ function Message(cmd, args) }, msg_meta) end +local tag_escapes = { + [";"] = "\\:", + [" "] = "\\s", + ["\0"] = "\\0", + ["\\"] = "\\\\", + ["\r"] = "\\r", + ["\n"] = "\\n", +} + +local tag_unescapes = {} +for x, y in pairs(tag_escapes) do tag_unescapes[y] = x end + function msg_meta:toRFC1459() s = "" @@ -25,10 +38,7 @@ function msg_meta:toRFC1459() for key, value in pairs(self.tags) do s = s..key if value ~= true then - assert(not value:find("[%z\07\r\n; ]"), - "NUL, BELL, CR, LF, semicolon, and" - .." space are not allowed in RFC1459" - .." formated tag values.") + value = value:gsub("[; %z\\\r\n]", tag_escapes) s = s.."="..value end s = s..";" @@ -59,6 +69,55 @@ function msg_meta:toRFC1459() return s end +local function parsePrefix(prefix) + local user = {} + user.nick, user.username, user.host = prefix:match("^(.+)!(.+)@(.+)$") + if not user.nick and prefix:find(".", 1, true) then + user.server = prefix + end + return user +end + +function msg_meta:fromRFC1459(line) + -- IRCv3 tags + if line:sub(1, 1) == "@" then + self.tags = {} + local space = line:find(" ", 1, true) + -- For each semicolon-delimited section from after + -- the @ character to before the space character. + for tag in line:sub(2, space - 1):gmatch("([^;]+)") do + local eq = tag:find("=", 1, true) + if eq then + self.tags[tag:sub(1, eq - 1)] = + tag:sub(eq + 1):gsub("\\([:s0\\rn])", tag_unescapes) + else + self.tags[tag] = true + end + end + line = line:sub(space + 1) + end + + if line:sub(1, 1) == ":" then + local space = line:find(" ", 1, true) + self.prefix = line:sub(2, space - 1) + self.user = parsePrefix(self.prefix) + line = line:sub(space + 1) + end + + local pos + self.command, pos = line:match("(%S+)()") + line = line:sub(pos) + + for pos, param in line:gmatch("()(%S+)") do + if param:sub(1, 1) == ":" then + param = line:sub(pos + 1) + insert(self.args, param) + break + end + insert(self.args, param) + end +end + function msgs.privmsg(to, text) return Message("PRIVMSG", {to, text}) end @@ -135,9 +194,9 @@ end function msgs.mode(target, modes) -- We have to split the modes parameter because the mode string and -- each parameter are seperate arguments (The first command is incorrect) - -- MODE :+ov Nick1 Nick2 - -- MODE +ov Nick1 Nick2 - mt = split(modes) + -- MODE foo :+ov Nick1 Nick2 + -- MODE foo +ov Nick1 Nick2 + local mt = split(modes) return Message("MODE", {target, unpack(mt)}) end diff --git a/util.lua b/util.lua index 0e0f5ff..3baab8e 100644 --- a/util.lua +++ b/util.lua @@ -10,49 +10,6 @@ local random = math.random module "irc" --- Protocol parsing -function parse(line) - local msg = Message() - - -- IRCv3 tags - if line:sub(1, 1) == "@" then - msg.tags = {} - local space = line:find(" ", 1, true) - -- For each semicolon-delimited section from after - -- the @ character to before the space character. - for tag in line:sub(2, space - 1):gmatch("([^;]+)") do - local eq = tag:find("=", 1, true) - if eq then - msg.tags[tag:sub(1, eq - 1)] = tag:sub(eq + 1) - else - msg.tags[tag] = true - end - end - line = line:sub(space + 1) - end - - if line:sub(1, 1) == ":" then - local space = line:find(" ", 1, true) - msg.prefix = line:sub(2, space - 1) - msg.user = parsePrefix(msg.prefix) - line = line:sub(space + 1) - end - - local pos - msg.command, pos = line:match("(%S+)()") - line = line:sub(pos) - - for pos, param in line:gmatch("()(%S+)") do - if param:sub(1, 1) == ":" then - param = line:sub(pos + 1) - table.insert(msg.args, param) - break - end - table.insert(msg.args, param) - end - - return msg -end function parseNick(conn, nick) local access = {} @@ -73,15 +30,6 @@ function parseNick(conn, nick) return access, name end -function parsePrefix(prefix) - local user = {} - user.nick, user.username, user.host = prefix:match("^(.+)!(.+)@(.+)$") - if not user.nick and prefix:find(".", 1, true) then - user.server = prefix - end - return user -end - function updatePrefixModes(conn) if conn.prefixmode and conn.modeprefix then return