soju/irc.go

640 lines
14 KiB
Go
Raw Normal View History

2020-03-13 17:13:03 +00:00
package soju
2020-02-06 18:24:32 +00:00
import (
"fmt"
"sort"
2020-02-06 18:24:32 +00:00
"strings"
2021-04-13 16:54:58 +00:00
"unicode"
"unicode/utf8"
2020-02-07 11:36:02 +00:00
"gopkg.in/irc.v3"
2020-02-06 18:24:32 +00:00
)
const (
rpl_statsping = "246"
rpl_localusers = "265"
rpl_globalusers = "266"
2020-03-26 04:51:47 +00:00
rpl_creationtime = "329"
rpl_topicwhotime = "333"
err_invalidcapcmd = "410"
2020-02-06 18:24:32 +00:00
)
2021-03-15 22:41:37 +00:00
const (
maxMessageLength = 512
maxMessageParams = 15
)
// The server-time layout, as defined in the IRCv3 spec.
const serverTimeLayout = "2006-01-02T15:04:05.000Z"
type userModes string
2020-02-06 18:24:32 +00:00
func (ms userModes) Has(c byte) bool {
2020-02-06 18:24:32 +00:00
return strings.IndexByte(string(ms), c) >= 0
}
func (ms *userModes) Add(c byte) {
2020-02-06 18:24:32 +00:00
if !ms.Has(c) {
*ms += userModes(c)
2020-02-06 18:24:32 +00:00
}
}
func (ms *userModes) Del(c byte) {
2020-02-06 18:24:32 +00:00
i := strings.IndexByte(string(*ms), c)
if i >= 0 {
*ms = (*ms)[:i] + (*ms)[i+1:]
}
}
func (ms *userModes) Apply(s string) error {
2020-02-06 18:24:32 +00:00
var plusMinus byte
for i := 0; i < len(s); i++ {
switch c := s[i]; c {
case '+', '-':
plusMinus = c
default:
switch plusMinus {
case '+':
ms.Add(c)
case '-':
ms.Del(c)
default:
return fmt.Errorf("malformed modestring %q: missing plus/minus", s)
}
}
}
return nil
}
type channelModeType byte
// standard channel mode types, as explained in https://modern.ircdocs.horse/#mode-message
const (
// modes that add or remove an address to or from a list
modeTypeA channelModeType = iota
// modes that change a setting on a channel, and must always have a parameter
modeTypeB
// modes that change a setting on a channel, and must have a parameter when being set, and no parameter when being unset
modeTypeC
// modes that change a setting on a channel, and must not have a parameter
modeTypeD
)
var stdChannelModes = map[byte]channelModeType{
'b': modeTypeA, // ban list
'e': modeTypeA, // ban exception list
'I': modeTypeA, // invite exception list
'k': modeTypeB, // channel key
'l': modeTypeC, // channel user limit
'i': modeTypeD, // channel is invite-only
'm': modeTypeD, // channel is moderated
'n': modeTypeD, // channel has no external messages
's': modeTypeD, // channel is secret
't': modeTypeD, // channel has protected topic
}
type channelModes map[byte]string
// applyChannelModes parses a mode string and mode arguments from a MODE message,
// and applies the corresponding channel mode and user membership changes on that channel.
//
// If ch.modes is nil, channel modes are not updated.
//
// needMarshaling is a list of indexes of mode arguments that represent entities
// that must be marshaled when sent downstream.
func applyChannelModes(ch *upstreamChannel, modeStr string, arguments []string) (needMarshaling map[int]struct{}, err error) {
needMarshaling = make(map[int]struct{}, len(arguments))
nextArgument := 0
var plusMinus byte
outer:
for i := 0; i < len(modeStr); i++ {
mode := modeStr[i]
if mode == '+' || mode == '-' {
plusMinus = mode
continue
}
if plusMinus != '+' && plusMinus != '-' {
return nil, fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
}
for _, membership := range ch.conn.availableMemberships {
if membership.Mode == mode {
if nextArgument >= len(arguments) {
return nil, fmt.Errorf("malformed modestring %q: missing mode argument for %c%c", modeStr, plusMinus, mode)
}
member := arguments[nextArgument]
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
m := ch.Members.Value(member)
if m != nil {
if plusMinus == '+' {
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
m.Add(ch.conn.availableMemberships, membership)
} else {
// TODO: for upstreams without multi-prefix, query the user modes again
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
m.Remove(membership)
}
}
needMarshaling[nextArgument] = struct{}{}
nextArgument++
continue outer
}
}
mt, ok := ch.conn.availableChannelModes[mode]
if !ok {
continue
}
if mt == modeTypeB || (mt == modeTypeC && plusMinus == '+') {
if plusMinus == '+' {
var argument string
// some sentitive arguments (such as channel keys) can be omitted for privacy
// (this will only happen for RPL_CHANNELMODEIS, never for MODE messages)
if nextArgument < len(arguments) {
argument = arguments[nextArgument]
}
if ch.modes != nil {
ch.modes[mode] = argument
}
} else {
delete(ch.modes, mode)
}
nextArgument++
} else if mt == modeTypeC || mt == modeTypeD {
if plusMinus == '+' {
if ch.modes != nil {
ch.modes[mode] = ""
}
} else {
delete(ch.modes, mode)
}
}
}
return needMarshaling, nil
}
func (cm channelModes) Format() (modeString string, parameters []string) {
var modesWithValues strings.Builder
var modesWithoutValues strings.Builder
parameters = make([]string, 0, 16)
for mode, value := range cm {
if value != "" {
modesWithValues.WriteString(string(mode))
parameters = append(parameters, value)
} else {
modesWithoutValues.WriteString(string(mode))
}
}
modeString = "+" + modesWithValues.String() + modesWithoutValues.String()
return
}
const stdChannelTypes = "#&+!"
2020-02-06 18:24:32 +00:00
type channelStatus byte
const (
channelPublic channelStatus = '='
channelSecret channelStatus = '@'
channelPrivate channelStatus = '*'
)
func parseChannelStatus(s string) (channelStatus, error) {
if len(s) > 1 {
return 0, fmt.Errorf("invalid channel status %q: more than one character", s)
}
switch cs := channelStatus(s[0]); cs {
case channelPublic, channelSecret, channelPrivate:
return cs, nil
default:
return 0, fmt.Errorf("invalid channel status %q: unknown status", s)
}
}
type membership struct {
Mode byte
Prefix byte
}
2020-02-06 18:24:32 +00:00
var stdMemberships = []membership{
{'q', '~'}, // founder
{'a', '&'}, // protected
{'o', '@'}, // operator
{'h', '%'}, // halfop
{'v', '+'}, // voice
2020-03-20 01:15:23 +00:00
}
// memberships always sorted by descending membership rank
type memberships []membership
func (m *memberships) Add(availableMemberships []membership, newMembership membership) {
l := *m
i := 0
for _, availableMembership := range availableMemberships {
if i >= len(l) {
break
}
if l[i] == availableMembership {
if availableMembership == newMembership {
// we already have this membership
return
}
i++
continue
}
if availableMembership == newMembership {
break
}
}
// insert newMembership at i
l = append(l, membership{})
copy(l[i+1:], l[i:])
l[i] = newMembership
*m = l
}
func (m *memberships) Remove(oldMembership membership) {
l := *m
for i, currentMembership := range l {
if currentMembership == oldMembership {
*m = append(l[:i], l[i+1:]...)
return
}
}
}
func (m memberships) Format(dc *downstreamConn) string {
if !dc.caps["multi-prefix"] {
if len(m) == 0 {
return ""
}
return string(m[0].Prefix)
}
prefixes := make([]byte, len(m))
for i, membership := range m {
prefixes[i] = membership.Prefix
2020-02-06 18:24:32 +00:00
}
return string(prefixes)
2020-02-06 18:24:32 +00:00
}
2020-02-07 11:36:02 +00:00
func parseMessageParams(msg *irc.Message, out ...*string) error {
if len(msg.Params) < len(out) {
return newNeedMoreParamsError(msg.Command)
}
for i := range out {
if out[i] != nil {
*out[i] = msg.Params[i]
}
}
return nil
}
2020-03-23 02:18:16 +00:00
func copyClientTags(tags irc.Tags) irc.Tags {
t := make(irc.Tags, len(tags))
for k, v := range tags {
if strings.HasPrefix(k, "+") {
t[k] = v
}
}
return t
}
2020-03-23 02:18:16 +00:00
type batch struct {
Type string
Params []string
Outer *batch // if not-nil, this batch is nested in Outer
Label string
2020-03-23 02:18:16 +00:00
}
func join(channels, keys []string) []*irc.Message {
// Put channels with a key first
js := joinSorter{channels, keys}
sort.Sort(&js)
// Two spaces because there are three words (JOIN, channels and keys)
maxLength := maxMessageLength - (len("JOIN") + 2)
var msgs []*irc.Message
var channelsBuf, keysBuf strings.Builder
for i, channel := range channels {
key := keys[i]
n := channelsBuf.Len() + keysBuf.Len() + 1 + len(channel)
if key != "" {
n += 1 + len(key)
}
if channelsBuf.Len() > 0 && n > maxLength {
// No room for the new channel in this message
params := []string{channelsBuf.String()}
if keysBuf.Len() > 0 {
params = append(params, keysBuf.String())
}
msgs = append(msgs, &irc.Message{Command: "JOIN", Params: params})
channelsBuf.Reset()
keysBuf.Reset()
}
if channelsBuf.Len() > 0 {
channelsBuf.WriteByte(',')
}
channelsBuf.WriteString(channel)
if key != "" {
if keysBuf.Len() > 0 {
keysBuf.WriteByte(',')
}
keysBuf.WriteString(key)
}
}
if channelsBuf.Len() > 0 {
params := []string{channelsBuf.String()}
if keysBuf.Len() > 0 {
params = append(params, keysBuf.String())
}
msgs = append(msgs, &irc.Message{Command: "JOIN", Params: params})
}
return msgs
}
2021-03-15 22:41:37 +00:00
func generateIsupport(prefix *irc.Prefix, nick string, tokens []string) []*irc.Message {
maxTokens := maxMessageParams - 2 // 2 reserved params: nick + text
var msgs []*irc.Message
for len(tokens) > 0 {
var msgTokens []string
if len(tokens) > maxTokens {
msgTokens = tokens[:maxTokens]
tokens = tokens[maxTokens:]
} else {
msgTokens = tokens
tokens = nil
}
msgs = append(msgs, &irc.Message{
Prefix: prefix,
Command: irc.RPL_ISUPPORT,
Params: append(append([]string{nick}, msgTokens...), "are supported"),
})
}
return msgs
}
type joinSorter struct {
channels []string
keys []string
}
func (js *joinSorter) Len() int {
return len(js.channels)
}
func (js *joinSorter) Less(i, j int) bool {
if (js.keys[i] != "") != (js.keys[j] != "") {
// Only one of the channels has a key
return js.keys[i] != ""
}
return js.channels[i] < js.channels[j]
}
func (js *joinSorter) Swap(i, j int) {
js.channels[i], js.channels[j] = js.channels[j], js.channels[i]
js.keys[i], js.keys[j] = js.keys[j], js.keys[i]
}
2020-08-17 13:01:53 +00:00
// parseCTCPMessage parses a CTCP message. CTCP is defined in
// https://tools.ietf.org/html/draft-oakley-irc-ctcp-02
func parseCTCPMessage(msg *irc.Message) (cmd string, params string, ok bool) {
if (msg.Command != "PRIVMSG" && msg.Command != "NOTICE") || len(msg.Params) < 2 {
return "", "", false
}
text := msg.Params[1]
if !strings.HasPrefix(text, "\x01") {
return "", "", false
}
text = strings.Trim(text, "\x01")
words := strings.SplitN(text, " ", 2)
cmd = strings.ToUpper(words[0])
if len(words) > 1 {
params = words[1]
}
return cmd, params, true
}
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
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 {
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
if 'A' <= r && r <= 'Z' {
nameBytes[i] = r + 'a' - 'A'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
}
return string(nameBytes)
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
// 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 {
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
if 'A' <= r && r <= 'Z' {
nameBytes[i] = r + 'a' - 'A'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '{' {
nameBytes[i] = '['
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '}' {
nameBytes[i] = ']'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '\\' {
nameBytes[i] = '|'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '~' {
nameBytes[i] = '^'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
}
return string(nameBytes)
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
// 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 {
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
if 'A' <= r && r <= 'Z' {
nameBytes[i] = r + 'a' - 'A'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '{' {
nameBytes[i] = '['
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '}' {
nameBytes[i] = ']'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
} else if r == '\\' {
nameBytes[i] = '|'
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
}
return string(nameBytes)
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
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
}
func partialCasemap(higher casemapping, name string) string {
nameFullyCM := []byte(higher(name))
nameBytes := []byte(name)
for i, r := range nameBytes {
if !('A' <= r && r <= 'Z') && !('a' <= r && r <= 'z') {
nameBytes[i] = nameFullyCM[i]
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
}
return string(nameBytes)
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
type casemapMap struct {
innerMap map[string]casemapEntry
casemap casemapping
}
type casemapEntry struct {
originalKey string
value interface{}
}
func newCasemapMap(size int) casemapMap {
return casemapMap{
innerMap: make(map[string]casemapEntry, size),
casemap: casemapNone,
}
}
func (cm *casemapMap) OriginalKey(name string) (key string, ok bool) {
entry, ok := cm.innerMap[cm.casemap(name)]
if !ok {
return "", false
}
return entry.originalKey, true
}
func (cm *casemapMap) Has(name string) bool {
_, ok := cm.innerMap[cm.casemap(name)]
return ok
}
func (cm *casemapMap) Len() int {
return len(cm.innerMap)
}
func (cm *casemapMap) SetValue(name string, value interface{}) {
nameCM := cm.casemap(name)
entry, ok := cm.innerMap[nameCM]
if !ok {
cm.innerMap[nameCM] = casemapEntry{
originalKey: name,
value: value,
}
return
}
entry.value = value
cm.innerMap[nameCM] = entry
}
func (cm *casemapMap) Delete(name string) {
delete(cm.innerMap, cm.casemap(name))
}
func (cm *casemapMap) SetCasemapping(newCasemap casemapping) {
cm.casemap = newCasemap
newInnerMap := make(map[string]casemapEntry, len(cm.innerMap))
for _, entry := range cm.innerMap {
newInnerMap[cm.casemap(entry.originalKey)] = entry
}
cm.innerMap = newInnerMap
}
type upstreamChannelCasemapMap struct{ casemapMap }
func (cm *upstreamChannelCasemapMap) Value(name string) *upstreamChannel {
entry, ok := cm.innerMap[cm.casemap(name)]
if !ok {
return nil
}
return entry.value.(*upstreamChannel)
}
type channelCasemapMap struct{ casemapMap }
func (cm *channelCasemapMap) Value(name string) *Channel {
entry, ok := cm.innerMap[cm.casemap(name)]
if !ok {
return nil
}
return entry.value.(*Channel)
}
type membershipsCasemapMap struct{ casemapMap }
func (cm *membershipsCasemapMap) Value(name string) *memberships {
entry, ok := cm.innerMap[cm.casemap(name)]
if !ok {
return nil
}
return entry.value.(*memberships)
}
type deliveredCasemapMap struct{ casemapMap }
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
func (cm *deliveredCasemapMap) Value(name string) deliveredClientMap {
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
entry, ok := cm.innerMap[cm.casemap(name)]
if !ok {
return nil
}
return entry.value.(deliveredClientMap)
Implement casemapping TL;DR: supports for casemapping, now logs are saved in casemapped/canonical/tolower form (eg. in the #channel directory instead of #Channel... or something) == What is casemapping? == see <https://modern.ircdocs.horse/#casemapping-parameter> == Casemapping and multi-upstream == Since each upstream does not necessarily use the same casemapping, and since casemappings cannot coexist [0], 1. soju must also update the database accordingly to upstreams' casemapping, otherwise it will end up inconsistent, 2. soju must "normalize" entity names and expose only one casemapping that is a subset of all supported casemappings (here, ascii). [0] On some upstreams, "emersion[m]" and "emersion{m}" refer to the same user (upstreams that advertise rfc1459 for example), while on others (upstreams that advertise ascii) they don't. Once upstream's casemapping is known (default to rfc1459), entity names in map keys are made into casemapped form, for upstreamConn, upstreamChannel and network. downstreamConn advertises "CASEMAPPING=ascii", and always casemap map keys with ascii. Some functions require the caller to casemap their argument (to avoid needless calls to casemapping functions). == Message forwarding and casemapping == downstream message handling (joins and parts basically): When relaying entity names from downstreams to upstreams, soju uses the upstream casemapping, in order to not get in the way of the user. This does not brings any issue, as long as soju replies with the ascii casemapping in mind (solves point 1.). marshalEntity/marshalUserPrefix: When relaying entity names from upstreams with non-ascii casemappings, soju *partially* casemap them: it only change the case of characters which are not ascii letters. ASCII case is thus kept intact, while special symbols like []{} are the same every time soju sends them to downstreams (solves point 2.). == Casemapping changes == Casemapping changes are not fully supported by this patch and will result in loss of history. This is a limitation of the protocol and should be solved by the RENAME spec.
2021-03-16 09:00:34 +00:00
}
2021-04-13 16:54:58 +00:00
func isWordBoundary(r rune) bool {
switch r {
case '-', '_', '|':
return false
case '\u00A0':
return true
default:
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}
}
func isHighlight(text, nick string) bool {
for {
i := strings.Index(text, nick)
if i < 0 {
return false
}
// Detect word boundaries
var left, right rune
if i > 0 {
left, _ = utf8.DecodeLastRuneInString(text[:i])
}
if i < len(text) {
right, _ = utf8.DecodeRuneInString(text[i+len(nick):])
}
if isWordBoundary(left) && isWordBoundary(right) {
return true
}
text = text[i+len(nick):]
}
}