package main import ( "path/filepath" "regexp" "strings" ) func ircToLower(c byte) byte { switch c { case '[': return '{' case ']': return '}' case '\\': return '|' case '~': return '^' } if c >= 'A' && c <= 'Z' { return c + ('a' - 'A') } return c } func ircToUpper(c byte) byte { switch c { case '{': return '[' case '}': return ']' case '|': return '\\' case '^': return '~' } if c >= 'a' && c <= 'z' { return c - ('a' - 'A') } return c } // Convert identifier to a canonical form for case-insensitive comparisons. // ircToUpper is used so that statically initialized maps can be in uppercase. func ircToCanon(ident string) string { var canon []byte for _, c := range []byte(ident) { canon = append(canon, ircToUpper(c)) } return string(canon) } func ircEqual(s1, s2 string) bool { return ircToCanon(s1) == ircToCanon(s2) } func ircFnmatch(pattern string, s string) bool { pattern, s = ircToCanon(pattern), ircToCanon(s) // FIXME: This should not support [] ranges and handle '/' specially. // We could translate the pattern to a regular expression. matched, _ := filepath.Match(pattern, s) return matched } var reMsg = regexp.MustCompile( `^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`) var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`) type message struct { 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 strings.Split(tags, ";") { if tag == "" { // Ignore empty. } else 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 }