From 53ba996ec9c5c8fc64f66934d8c98509bd7ed06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Tue, 2 Apr 2024 16:44:01 +0200 Subject: Add a simple IRC notifier utility --- xS/irc.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ xS/xS.go | 126 ----------------------------------------------------------- 2 files changed, 132 insertions(+), 126 deletions(-) create mode 100644 xS/irc.go (limited to 'xS') diff --git a/xS/irc.go b/xS/irc.go new file mode 100644 index 0000000..71bf961 --- /dev/null +++ b/xS/irc.go @@ -0,0 +1,132 @@ +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 +} diff --git a/xS/xS.go b/xS/xS.go index 25db005..69a9b36 100644 --- a/xS/xS.go +++ b/xS/xS.go @@ -456,132 +456,6 @@ func (fd *floodDetector) check() bool { return count <= fd.limit } -// --- IRC protocol ------------------------------------------------------------ - -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 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 - -} - // --- IRC token validation ---------------------------------------------------- // Everything as per RFC 2812 -- cgit v1.2.3