From 33e0f6776684109f262c620a1b2af60333accf6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken-H=C3=A5vard=20Lieng?= Date: Sun, 29 Apr 2018 03:49:02 +0200 Subject: [PATCH] Add IRCv3 tag parsing --- irc/message.go | 78 ++++++++++++++++++++++++++++++++++----------- irc/message_test.go | 50 +++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/irc/message.go b/irc/message.go index c9db40ed..982e300c 100644 --- a/irc/message.go +++ b/irc/message.go @@ -8,6 +8,7 @@ import ( ) type Message struct { + Tags map[string]string Prefix string Nick string Command string @@ -24,17 +25,40 @@ func (m *Message) LastParam() string { func parseMessage(line string) *Message { line = strings.Trim(line, "\r\n ") msg := Message{} - cmdStart := 0 - cmdEnd := len(line) - if strings.HasPrefix(line, ":") { - cmdStart = strings.Index(line, " ") + 1 - - if cmdStart > 0 { - msg.Prefix = line[1 : cmdStart-1] - } else { + if strings.HasPrefix(line, "@") { + next := strings.Index(line, " ") + if next == -1 { return nil } + tags := strings.Split(line[1:next], ";") + + if len(tags) > 0 { + msg.Tags = map[string]string{} + } + + for _, tag := range tags { + key, val := splitParam(tag) + if key == "" { + continue + } + + if val != "" { + msg.Tags[key] = unescapeTag(val) + } else { + msg.Tags[key] = "" + } + } + + line = line[next+1:] + } + + if strings.HasPrefix(line, ":") { + next := strings.Index(line, " ") + if next == -1 { + return nil + } + msg.Prefix = line[1:next] if i := strings.Index(msg.Prefix, "!"); i > 0 { msg.Nick = msg.Prefix[:i] @@ -43,16 +67,18 @@ func parseMessage(line string) *Message { } else { msg.Nick = msg.Prefix } + + line = line[next+1:] } - var trailing string - + cmdEnd := len(line) + trailing := "" if i := strings.Index(line, " :"); i > 0 { cmdEnd = i trailing = line[i+2:] } - cmd := strings.Fields(line[cmdStart:cmdEnd]) + cmd := strings.Fields(line[:cmdEnd]) if len(cmd) == 0 { return nil } @@ -61,7 +87,6 @@ func parseMessage(line string) *Message { if len(cmd) > 1 { msg.Params = cmd[1:] } - if cmdEnd != len(line) { msg.Params = append(msg.Params, trailing) } @@ -83,17 +108,15 @@ func newISupport() *iSupport { func (i *iSupport) parse(params []string) { i.lock.Lock() for _, param := range params[1 : len(params)-1] { - parts := strings.SplitN(param, "=", 2) - if parts[0] == "" { + key, val := splitParam(param) + if key == "" { continue } - if parts[0][0] == '-' { - delete(i.support, parts[0][1:]) - } else if len(parts) == 2 { - i.support[parts[0]] = parts[1] + if key[0] == '-' { + delete(i.support, key[1:]) } else { - i.support[param] = "" + i.support[key] = val } } i.lock.Unlock() @@ -119,3 +142,20 @@ func (i *iSupport) GetInt(key string) int { i.lock.Unlock() return v } + +func splitParam(param string) (string, string) { + parts := strings.SplitN(param, "=", 2) + if len(parts) == 2 { + return parts[0], parts[1] + } + return parts[0], "" +} + +func unescapeTag(s string) string { + s = strings.Replace(s, "\\:", ";", -1) + s = strings.Replace(s, "\\s", " ", -1) + s = strings.Replace(s, "\\\\", "\\", -1) + s = strings.Replace(s, "\\r", "\r", -1) + s = strings.Replace(s, "\\n", "\n", -1) + return s +} diff --git a/irc/message_test.go b/irc/message_test.go index 4556a2a0..cb923964 100644 --- a/irc/message_test.go +++ b/irc/message_test.go @@ -100,6 +100,53 @@ func TestParseMessage(t *testing.T) { Command: "CMD", Params: []string{"#cake", "pie!"}, }, + }, { + "@x=y CMD\r\n", + &Message{ + Tags: map[string]string{ + "x": "y", + }, + Command: "CMD", + }, + }, { + "@x=y :nick!user@host.com CMD\r\n", + &Message{ + Tags: map[string]string{ + "x": "y", + }, + Prefix: "nick!user@host.com", + Nick: "nick", + Command: "CMD", + }, + }, { + "@x=y :nick!user@host.com CMD :pie and cake\r\n", + &Message{ + Tags: map[string]string{ + "x": "y", + }, + Prefix: "nick!user@host.com", + Nick: "nick", + Command: "CMD", + Params: []string{"pie and cake"}, + }, + }, { + "@x=y;a=b CMD\r\n", + &Message{ + Tags: map[string]string{ + "x": "y", + "a": "b", + }, + Command: "CMD", + }, + }, { + "@x=y;a=\\\\\\:\\s\\r\\n CMD\r\n", + &Message{ + Tags: map[string]string{ + "x": "y", + "a": "\\; \r\n", + }, + Command: "CMD", + }, }, } @@ -114,6 +161,9 @@ func TestLastParam(t *testing.T) { } func TestBadMessagePanic(t *testing.T) { + parseMessage("@\r\n") + parseMessage("@ :\r\n") + parseMessage("@ :\r\n") parseMessage(":user\r\n") parseMessage(":\r\n") parseMessage(":")