Changeset 478 in code for trunk/downstream.go


Ignore:
Timestamp:
Mar 24, 2021, 5:15:52 PM (4 years ago)
Author:
hubert
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/downstream.go

    r464 r478  
    117117        user        *user
    118118        nick        string
     119        nickCM      string
    119120        rawUsername string
    120121        networkName string
     
    193194        // TODO: this doesn't account for nick changes
    194195        if net.conn != nil {
    195                 return nick == net.conn.nick
     196                return net.casemap(nick) == net.conn.nickCM
    196197        }
    197198        // We're not currently connected to the upstream connection, so we don't
     
    199200        // configured nickname and hope it was the one being used when we were
    200201        // connected.
    201         return nick == net.Nick
     202        return net.casemap(nick) == net.casemap(net.Nick)
    202203}
    203204
     
    211212                return dc.nick
    212213        }
     214        name = partialCasemap(net.casemap, name)
    213215        if dc.network != nil {
    214216                if dc.network != net {
     
    224226                return dc.prefix()
    225227        }
     228        prefix.Name = partialCasemap(net.casemap, prefix.Name)
    226229        if dc.network != nil {
    227230                if dc.network != net {
     
    359362        }
    360363
    361         delivered, ok := network.delivered[entity]
    362         if !ok {
     364        delivered := network.delivered.Value(entity)
     365        if delivered == nil {
    363366                return
    364367        }
     
    446449                        }}
    447450                }
    448                 if nick == serviceNick {
     451                nickCM := casemapASCII(nick)
     452                if nickCM == serviceNickCM {
    449453                        return ircError{&irc.Message{
    450454                                Command: irc.ERR_NICKNAMEINUSE,
     
    453457                }
    454458                dc.nick = nick
     459                dc.nickCM = nickCM
    455460        case "USER":
    456461                if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
     
    767772                })
    768773                dc.nick = uc.nick
     774                dc.nickCM = casemapASCII(dc.nick)
    769775        }
    770776}
     
    912918        isupport := []string{
    913919                fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
     920                "CASEMAPPING=ascii",
    914921        }
    915922
     
    961968
    962969        dc.forEachUpstream(func(uc *upstreamConn) {
    963                 for _, ch := range uc.channels {
     970                for _, entry := range uc.channels.innerMap {
     971                        ch := entry.value.(*upstreamChannel)
    964972                        if !ch.complete {
    965973                                continue
    966974                        }
    967                         if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
     975                        record := uc.network.channels.Value(ch.Name)
     976                        if record != nil && record.Detached {
    968977                                continue
    969978                        }
     
    988997
    989998                // Fast-forward history to last message
    990                 for target, delivered := range net.delivered {
    991                         if ch, ok := net.channels[target]; ok && ch.Detached {
     999                for target, entry := range net.delivered.innerMap {
     1000                        delivered := entry.value.(map[string]string)
     1001                        ch := net.channels.Value(target)
     1002                        if ch != nil && ch.Detached {
    9921003                                continue
    9931004                        }
     
    10201031
    10211032func (dc *downstreamConn) sendNetworkBacklog(net *network) {
    1022         for target := range net.delivered {
     1033        for _, entry := range net.delivered.innerMap {
     1034                target := entry.originalKey
    10231035                dc.sendTargetBacklog(net, target)
    10241036        }
     
    10291041                return
    10301042        }
    1031         if ch, ok := net.channels[target]; ok && ch.Detached {
     1043        if ch := net.channels.Value(target); ch != nil && ch.Detached {
    10321044                return
    10331045        }
    1034         delivered, ok := net.delivered[target]
    1035         if !ok {
     1046        delivered := net.delivered.Value(target)
     1047        if delivered == nil {
    10361048                return
    10371049        }
     
    11591171                        }}
    11601172                }
    1161                 if nick == serviceNick {
     1173                if casemapASCII(nick) == serviceNickCM {
    11621174                        return ircError{&irc.Message{
    11631175                                Command: irc.ERR_NICKNAMEINUSE,
     
    11951207                        })
    11961208                        dc.nick = nick
     1209                        dc.nickCM = casemapASCII(dc.nick)
    11971210                }
    11981211        case "JOIN":
     
    12271240                        })
    12281241
    1229                         var ch *Channel
    1230                         var ok bool
    1231                         if ch, ok = uc.network.channels[upstreamName]; ok {
     1242                        ch := uc.network.channels.Value(upstreamName)
     1243                        if ch != nil {
    12321244                                // Don't clear the channel key if there's one set
    12331245                                // TODO: add a way to unset the channel key
     
    12411253                                        Key:  key,
    12421254                                }
    1243                                 uc.network.channels[upstreamName] = ch
     1255                                uc.network.channels.SetValue(upstreamName, ch)
    12441256                        }
    12451257                        if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
     
    12651277
    12661278                        if strings.EqualFold(reason, "detach") {
    1267                                 var ch *Channel
    1268                                 var ok bool
    1269                                 if ch, ok = uc.network.channels[upstreamName]; ok {
     1279                                ch := uc.network.channels.Value(upstreamName)
     1280                                if ch != nil {
    12701281                                        uc.network.detach(ch)
    12711282                                } else {
     
    12741285                                                Detached: true,
    12751286                                        }
    1276                                         uc.network.channels[upstreamName] = ch
     1287                                        uc.network.channels.SetValue(upstreamName, ch)
    12771288                                }
    12781289                                if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
     
    13611372                }
    13621373
    1363                 if name == dc.nick {
     1374                if casemapASCII(name) == dc.nickCM {
    13641375                        if modeStr != "" {
    13651376                                dc.forEachUpstream(func(uc *upstreamConn) {
     
    13991410                        })
    14001411                } else {
    1401                         ch, ok := uc.channels[upstreamName]
    1402                         if !ok {
     1412                        ch := uc.channels.Value(upstreamName)
     1413                        if ch == nil {
    14031414                                return ircError{&irc.Message{
    14041415                                        Command: irc.ERR_NOSUCHCHANNEL,
     
    14361447                }
    14371448
    1438                 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
     1449                uc, upstreamName, err := dc.unmarshalEntity(channel)
    14391450                if err != nil {
    14401451                        return err
     
    14451456                        uc.SendMessageLabeled(dc.id, &irc.Message{
    14461457                                Command: "TOPIC",
    1447                                 Params:  []string{upstreamChannel, topic},
     1458                                Params:  []string{upstreamName, topic},
    14481459                        })
    14491460                } else { // getting topic
    1450                         ch, ok := uc.channels[upstreamChannel]
    1451                         if !ok {
     1461                        ch := uc.channels.Value(upstreamName)
     1462                        if ch == nil {
    14521463                                return ircError{&irc.Message{
    14531464                                        Command: irc.ERR_NOSUCHCHANNEL,
    1454                                         Params:  []string{dc.nick, upstreamChannel, "No such channel"},
     1465                                        Params:  []string{dc.nick, upstreamName, "No such channel"},
    14551466                                }}
    14561467                        }
     
    15141525                channels := strings.Split(msg.Params[0], ",")
    15151526                for _, channel := range channels {
    1516                         uc, upstreamChannel, err := dc.unmarshalEntity(channel)
     1527                        uc, upstreamName, err := dc.unmarshalEntity(channel)
    15171528                        if err != nil {
    15181529                                return err
    15191530                        }
    15201531
    1521                         ch, ok := uc.channels[upstreamChannel]
    1522                         if ok {
     1532                        ch := uc.channels.Value(upstreamName)
     1533                        if ch != nil {
    15231534                                sendNames(dc, ch)
    15241535                        } else {
     
    15261537                                uc.SendMessageLabeled(dc.id, &irc.Message{
    15271538                                        Command: "NAMES",
    1528                                         Params:  []string{upstreamChannel},
     1539                                        Params:  []string{upstreamName},
    15291540                                })
    15301541                        }
     
    15431554                // TODO: support WHO masks
    15441555                entity := msg.Params[0]
    1545 
    1546                 if entity == dc.nick {
     1556                entityCM := casemapASCII(entity)
     1557
     1558                if entityCM == dc.nickCM {
    15471559                        // TODO: support AWAY (H/G) in self WHO reply
    15481560                        dc.SendMessage(&irc.Message{
     
    15581570                        return nil
    15591571                }
    1560                 if entity == serviceNick {
     1572                if entityCM == serviceNickCM {
    15611573                        dc.SendMessage(&irc.Message{
    15621574                                Prefix:  dc.srv.prefix(),
     
    16091621                }
    16101622
    1611                 if mask == dc.nick {
     1623                if casemapASCII(mask) == dc.nickCM {
    16121624                        dc.SendMessage(&irc.Message{
    16131625                                Prefix:  dc.srv.prefix(),
Note: See TracChangeset for help on using the changeset viewer.