diff --git a/downstream.go b/downstream.go index c463344..861ae11 100644 --- a/downstream.go +++ b/downstream.go @@ -365,7 +365,7 @@ func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn { monitored: newCasemapMap[struct{}](), registration: new(downstreamRegistration), } - dc.monitored.SetCasemapping(casemapASCII) + dc.monitored.SetCasemapping(xirc.CaseMappingASCII) if host, _, err := net.SplitHostPort(remoteAddr); err == nil { dc.hostname = host } else { @@ -1110,7 +1110,7 @@ func (dc *downstreamConn) updateNick() { Params: []string{nick}, }) dc.nick = nick - dc.nickCM = casemapASCII(dc.nick) + dc.nickCM = xirc.CaseMappingASCII(dc.nick) } func (dc *downstreamConn) updateHost() { @@ -1197,7 +1197,7 @@ func (dc *downstreamConn) updateAccount() { } func (dc *downstreamConn) updateCasemapping() { - cm := casemapASCII + cm := xirc.CaseMappingASCII if dc.network != nil { cm = dc.network.casemap } @@ -1397,7 +1397,7 @@ func (dc *downstreamConn) loadNetwork(ctx context.Context) error { Params: []string{dc.nick, dc.registration.nick, "Nickname contains illegal characters"}, }} } - if casemapASCII(nick) == serviceNickCM { + if xirc.CaseMappingASCII(nick) == serviceNickCM { return ircError{&irc.Message{ Command: irc.ERR_NICKNAMEINUSE, Params: []string{dc.nick, dc.registration.nick, "Nickname reserved for bouncer service"}, @@ -1446,7 +1446,7 @@ func (dc *downstreamConn) welcome(ctx context.Context) error { } else { dc.nick = dc.user.Username } - dc.nickCM = casemapASCII(dc.nick) + dc.nickCM = xirc.CaseMappingASCII(dc.nick) var isupport []string if dc.network != nil { @@ -1761,7 +1761,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. Params: []string{dc.nick, nick, "Nickname contains illegal characters"}, }} } - if casemapASCII(nick) == serviceNickCM { + if xirc.CaseMappingASCII(nick) == serviceNickCM { return ircError{&irc.Message{ Command: irc.ERR_NICKNAMEINUSE, Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"}, @@ -2000,7 +2000,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. modeStr = msg.Params[1] } - if casemapASCII(name) == dc.nickCM { + if xirc.CaseMappingASCII(name) == dc.nickCM { if modeStr != "" { if uc := dc.upstream(); uc != nil { uc.SendMessageLabeled(ctx, dc.id, &irc.Message{ @@ -2165,7 +2165,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. fields, whoxToken := xirc.ParseWHOXOptions(options) // TODO: support mixed bouncer/upstream WHO queries - maskCM := casemapASCII(mask) + maskCM := xirc.CaseMappingASCII(mask) if dc.network == nil && maskCM == dc.nickCM { // TODO: support AWAY (H/G) in self WHO reply flags := "H" @@ -2274,7 +2274,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. mask = mask[:i] } - if dc.network == nil && casemapASCII(mask) == dc.nickCM { + if dc.network == nil && xirc.CaseMappingASCII(mask) == dc.nickCM { dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), Command: irc.RPL_WHOISUSER, @@ -2304,7 +2304,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. }) return nil } - if casemapASCII(mask) == serviceNickCM { + if xirc.CaseMappingASCII(mask) == serviceNickCM { dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), Command: irc.RPL_WHOISUSER, @@ -2391,7 +2391,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. continue } - if dc.network == nil && casemapASCII(name) == dc.nickCM { + if dc.network == nil && xirc.CaseMappingASCII(name) == dc.nickCM { dc.SendMessage(&irc.Message{ Tags: msg.Tags.Copy(), Prefix: dc.prefix(), @@ -2401,7 +2401,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. continue } - if casemapASCII(name) == serviceNickCM { + if xirc.CaseMappingASCII(name) == serviceNickCM { if dc.caps.IsEnabled("echo-message") { echoTags := tags.Copy() echoTags["time"] = dc.user.FormatServerTime(time.Now()) @@ -2724,7 +2724,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. } // We don't save history for our service - if casemapASCII(target) == serviceNickCM { + if xirc.CaseMappingASCII(target) == serviceNickCM { dc.SendBatch("chathistory", []string{target}, nil, func(batchRef string) {}) return nil } @@ -2849,7 +2849,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc. } // We don't save read receipts for our service - if casemapASCII(target) == serviceNickCM { + if xirc.CaseMappingASCII(target) == serviceNickCM { dc.SendMessage(&irc.Message{ Prefix: dc.prefix(), Command: msg.Command, diff --git a/irc.go b/irc.go index 25a7dce..fc29aab 100644 --- a/irc.go +++ b/irc.go @@ -216,79 +216,11 @@ func copyClientTags(tags irc.Tags) irc.Tags { return t } -type casemapping func(string) string - -func casemapNone(name string) string { - return name -} - -// CasemapASCII of name is the canonical representation of name according to the -// ascii casemapping. -func casemapASCII(name string) string { - nameBytes := []byte(name) - for i, r := range nameBytes { - if 'A' <= r && r <= 'Z' { - nameBytes[i] = r + 'a' - 'A' - } - } - return string(nameBytes) -} - -// casemapRFC1459 of name is the canonical representation of name according to the -// rfc1459 casemapping. -func casemapRFC1459(name string) string { - nameBytes := []byte(name) - for i, r := range nameBytes { - if 'A' <= r && r <= 'Z' { - nameBytes[i] = r + 'a' - 'A' - } else if r == '{' { - nameBytes[i] = '[' - } else if r == '}' { - nameBytes[i] = ']' - } else if r == '\\' { - nameBytes[i] = '|' - } else if r == '~' { - nameBytes[i] = '^' - } - } - return string(nameBytes) -} - -// casemapRFC1459Strict of name is the canonical representation of name -// according to the rfc1459-strict casemapping. -func casemapRFC1459Strict(name string) string { - nameBytes := []byte(name) - for i, r := range nameBytes { - if 'A' <= r && r <= 'Z' { - nameBytes[i] = r + 'a' - 'A' - } else if r == '{' { - nameBytes[i] = '[' - } else if r == '}' { - nameBytes[i] = ']' - } else if r == '\\' { - nameBytes[i] = '|' - } - } - return string(nameBytes) -} - -func parseCasemappingToken(tokenValue string) (casemap casemapping, ok bool) { - switch tokenValue { - case "ascii": - casemap = casemapASCII - case "rfc1459": - casemap = casemapRFC1459 - case "rfc1459-strict": - casemap = casemapRFC1459Strict - default: - return nil, false - } - return casemap, true -} +var stdCaseMapping = xirc.CaseMappingRFC1459 type casemapMap[V interface{}] struct { m map[string]casemapEntry[V] - casemap casemapping + casemap xirc.CaseMapping } type casemapEntry[V interface{}] struct { @@ -299,7 +231,7 @@ type casemapEntry[V interface{}] struct { func newCasemapMap[V interface{}]() casemapMap[V] { return casemapMap[V]{ m: make(map[string]casemapEntry[V]), - casemap: casemapNone, + casemap: xirc.CaseMappingNone, } } @@ -345,7 +277,7 @@ func (cm *casemapMap[V]) ForEach(f func(string, V)) { } } -func (cm *casemapMap[V]) SetCasemapping(newCasemap casemapping) { +func (cm *casemapMap[V]) SetCasemapping(newCasemap xirc.CaseMapping) { cm.casemap = newCasemap m := make(map[string]casemapEntry[V], len(cm.m)) for _, entry := range cm.m { diff --git a/upstream.go b/upstream.go index 8b3d5b3..6588745 100644 --- a/upstream.go +++ b/upstream.go @@ -942,9 +942,9 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err var err error switch parameter { case "CASEMAPPING": - casemap, ok := parseCasemappingToken(value) - if !ok { - casemap = casemapRFC1459 + casemap := xirc.ParseCaseMapping(value) + if casemap == nil { + casemap = xirc.CaseMappingRFC1459 } uc.network.updateCasemapping(casemap) case "CHANMODES": @@ -992,7 +992,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err // If upstream did not send any CASEMAPPING token, assume it // implements the old RFCs with rfc1459. if uc.isupport["CASEMAPPING"] == nil { - uc.network.updateCasemapping(casemapRFC1459) + uc.network.updateCasemapping(stdCaseMapping) } // If the server doesn't support MONITOR, periodically try to diff --git a/user.go b/user.go index d038cb6..b77ba86 100644 --- a/user.go +++ b/user.go @@ -151,7 +151,7 @@ type network struct { delivered deliveredStore pushTargets casemapMap[time.Time] lastError error - casemap casemapping + casemap xirc.CaseMapping } func newNetwork(user *user, record *database.Network, channels []database.Channel) *network { @@ -171,7 +171,7 @@ func newNetwork(user *user, record *database.Network, channels []database.Channe channels: m, delivered: newDeliveredStore(), pushTargets: newCasemapMap[time.Time](), - casemap: casemapRFC1459, + casemap: stdCaseMapping, } } @@ -387,7 +387,7 @@ func (net *network) deleteChannel(ctx context.Context, name string) error { return nil } -func (net *network) updateCasemapping(newCasemap casemapping) { +func (net *network) updateCasemapping(newCasemap xirc.CaseMapping) { net.casemap = newCasemap net.channels.SetCasemapping(newCasemap) net.delivered.m.SetCasemapping(newCasemap) diff --git a/xirc/casemapping.go b/xirc/casemapping.go new file mode 100644 index 0000000..c2f5c4f --- /dev/null +++ b/xirc/casemapping.go @@ -0,0 +1,77 @@ +package xirc + +func casemapNone(name string) string { + return name +} + +// CasemapASCII of name is the canonical representation of name according to the +// ascii casemapping. +func casemapASCII(name string) string { + nameBytes := []byte(name) + for i, r := range nameBytes { + if 'A' <= r && r <= 'Z' { + nameBytes[i] = r + 'a' - 'A' + } + } + return string(nameBytes) +} + +// casemapRFC1459 of name is the canonical representation of name according to the +// rfc1459 casemapping. +func casemapRFC1459(name string) string { + nameBytes := []byte(name) + for i, r := range nameBytes { + if 'A' <= r && r <= 'Z' { + nameBytes[i] = r + 'a' - 'A' + } else if r == '{' { + nameBytes[i] = '[' + } else if r == '}' { + nameBytes[i] = ']' + } else if r == '\\' { + nameBytes[i] = '|' + } else if r == '~' { + nameBytes[i] = '^' + } + } + return string(nameBytes) +} + +// casemapRFC1459Strict of name is the canonical representation of name +// according to the rfc1459-strict casemapping. +func casemapRFC1459Strict(name string) string { + nameBytes := []byte(name) + for i, r := range nameBytes { + if 'A' <= r && r <= 'Z' { + nameBytes[i] = r + 'a' - 'A' + } else if r == '{' { + nameBytes[i] = '[' + } else if r == '}' { + nameBytes[i] = ']' + } else if r == '\\' { + nameBytes[i] = '|' + } + } + return string(nameBytes) +} + +type CaseMapping func(string) string + +var ( + CaseMappingNone CaseMapping = casemapNone + CaseMappingASCII CaseMapping = casemapASCII + CaseMappingRFC1459 CaseMapping = casemapRFC1459 + CaseMappingRFC1459Strict CaseMapping = casemapRFC1459Strict +) + +func ParseCaseMapping(s string) CaseMapping { + var cm CaseMapping + switch s { + case "ascii": + cm = CaseMappingASCII + case "rfc1459": + cm = CaseMappingRFC1459 + case "rfc1459-strict": + cm = CaseMappingRFC1459Strict + } + return cm +}