133 lines
2.8 KiB
Go
133 lines
2.8 KiB
Go
|
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
|
||
|
}
|