diff --git a/hid/main.go b/hid/main.go index 1c72fca..eec67d9 100644 --- a/hid/main.go +++ b/hid/main.go @@ -296,17 +296,72 @@ func ircFnmatch(pattern string, s string) bool { return matched } -// TODO: We will need to add support for IRCv3 tags. var reMsg = regexp.MustCompile( - `^(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`) + `^(?:@[^ ]* +)(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`) var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`) type message struct { - nick string // optional nickname - user string // optional username - host string // optional hostname or IP address - command string // command name - params []string // arguments + tags map[string]string // IRC 3.2 message tags + nick string // optional nickname + user string // optional username + host string // optional hostname or IP address + command string // command name + params []string // arguments +} + +func ircUnescapeMessageTag(value string) string { + var buf []byte + escape := false + for i := 0; i < len(value); i++ { + if escape { + switch value[i] { + case ':': + buf = append(buf, ';') + case 's': + buf = append(buf, ' ') + case 'r': + buf = append(buf, '\r') + case 'n': + buf = append(buf, '\n') + default: + buf = append(buf, value[i]) + } + escape = false + } else if value[i] == '\\' { + escape = true + } else { + buf = append(buf, value[i]) + } + } + return string(buf) +} + +func ircParseMessageTags(tags string, out map[string]string) { + for _, tag := range splitString(tags, ";", true /* ignoreEmpty */) { + if equal := strings.IndexByte(tag, '='); equal < 0 { + out[tag] = "" + } else { + out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:]) + } + } +} + +func ircParseMessage(line string) *message { + m := reMsg.FindStringSubmatch(line) + if m == nil { + return nil + } + + msg := message{nil, m[2], m[3], m[4], m[5], nil} + if m[1] != "" { + msg.tags = make(map[string]string) + ircParseMessageTags(m[1], msg.tags) + } + for _, x := range reArgs.FindAllString(m[6], -1) { + msg.params = append(msg.params, x[1:]) + } + return &msg + } // Everything as per RFC 2812 @@ -2627,23 +2682,16 @@ func (c *client) onRead(data []byte, readErr error) { break } + // XXX: And since it accepts LF, we miscalculate receivedBytes within. c.recvQ = c.recvQ[advance:] line := string(token) log.Printf("-> %s\n", line) - m := reMsg.FindStringSubmatch(line) - if m == nil { + if msg := ircParseMessage(line); msg == nil { log.Println("error: invalid line") - continue + } else { + ircProcessMessage(c, msg, line) } - - msg := message{m[1], m[2], m[3], m[4], nil} - for _, x := range reArgs.FindAllString(m[5], -1) { - msg.params = append(msg.params, x[1:]) - } - - // XXX: And since it accepts LF, we miscalculate receivedBytes within. - ircProcessMessage(c, &msg, line) } if readErr != nil {