Changeset 478 in code for trunk/upstream.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/upstream.go

    r469 r478  
    4949        modes        channelModes
    5050        creationTime string
    51         Members      map[string]*memberships
     51        Members      membershipsCasemapMap
    5252        complete     bool
    5353        detachTimer  *time.Timer
     
    8787        registered    bool
    8888        nick          string
     89        nickCM        string
    8990        username      string
    9091        realname      string
    9192        modes         userModes
    92         channels      map[string]*upstreamChannel
     93        channels      upstreamChannelCasemapMap
    9394        supportedCaps map[string]string
    9495        caps          map[string]bool
     
    99100        saslClient  sasl.Client
    100101        saslStarted bool
     102
     103        casemapIsSet bool
    101104
    102105        // set of LIST commands in progress, per downstream
     
    187190                network:                  network,
    188191                user:                     network.user,
    189                 channels:                 make(map[string]*upstreamChannel),
     192                channels:                 upstreamChannelCasemapMap{newCasemapMap(0)},
    190193                supportedCaps:            make(map[string]string),
    191194                caps:                     make(map[string]bool),
     
    214217
    215218func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
    216         ch, ok := uc.channels[name]
    217         if !ok {
     219        ch := uc.channels.Value(name)
     220        if ch == nil {
    218221                return nil, fmt.Errorf("unknown channel %q", name)
    219222        }
     
    223226func (uc *upstreamConn) isChannel(entity string) bool {
    224227        return strings.ContainsRune(uc.availableChannelTypes, rune(entity[0]))
     228}
     229
     230func (uc *upstreamConn) isOurNick(nick string) bool {
     231        return uc.nickCM == uc.network.casemap(nick)
    225232}
    226233
     
    414421                } else { // regular user message
    415422                        target := entity
    416                         if target == uc.nick {
     423                        if uc.isOurNick(target) {
    417424                                target = msg.Prefix.Name
    418425                        }
    419426
    420                         if ch, ok := uc.network.channels[target]; ok {
     427                        ch := uc.network.channels.Value(target)
     428                        if ch != nil {
    421429                                if ch.Detached {
    422430                                        uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
     
    591599                uc.logger.Printf("connection registered")
    592600
    593                 if len(uc.network.channels) > 0 {
     601                if uc.network.channels.Len() > 0 {
    594602                        var channels, keys []string
    595                         for _, ch := range uc.network.channels {
     603                        for _, entry := range uc.network.channels.innerMap {
     604                                ch := entry.value.(*Channel)
    596605                                channels = append(channels, ch.Name)
    597606                                keys = append(keys, ch.Key)
     
    635644                        var err error
    636645                        switch parameter {
     646                        case "CASEMAPPING":
     647                                casemap, ok := parseCasemappingToken(value)
     648                                if !ok {
     649                                        casemap = casemapRFC1459
     650                                }
     651                                uc.network.updateCasemapping(casemap)
     652                                uc.nickCM = uc.network.casemap(uc.nick)
     653                                uc.casemapIsSet = true
    637654                        case "CHANMODES":
    638655                                if !negate {
     
    672689                        }
    673690                })
     691        case irc.ERR_NOMOTD, irc.RPL_ENDOFMOTD:
     692                if !uc.casemapIsSet {
     693                        // upstream did not send any CASEMAPPING token, thus
     694                        // we assume it implements the old RFCs with rfc1459.
     695                        uc.casemapIsSet = true
     696                        uc.network.updateCasemapping(casemapRFC1459)
     697                        uc.nickCM = uc.network.casemap(uc.nick)
     698                }
    674699        case "BATCH":
    675700                var tag string
     
    717742
    718743                me := false
    719                 if msg.Prefix.Name == uc.nick {
     744                if uc.isOurNick(msg.Prefix.Name) {
    720745                        uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
    721746                        me = true
    722747                        uc.nick = newNick
    723                 }
    724 
    725                 for _, ch := range uc.channels {
    726                         if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
    727                                 delete(ch.Members, msg.Prefix.Name)
    728                                 ch.Members[newNick] = memberships
     748                        uc.nickCM = uc.network.casemap(uc.nick)
     749                }
     750
     751                for _, entry := range uc.channels.innerMap {
     752                        ch := entry.value.(*upstreamChannel)
     753                        memberships := ch.Members.Value(msg.Prefix.Name)
     754                        if memberships != nil {
     755                                ch.Members.Delete(msg.Prefix.Name)
     756                                ch.Members.SetValue(newNick, memberships)
    729757                                uc.appendLog(ch.Name, msg)
    730758                        }
     
    751779
    752780                for _, ch := range strings.Split(channels, ",") {
    753                         if msg.Prefix.Name == uc.nick {
     781                        if uc.isOurNick(msg.Prefix.Name) {
    754782                                uc.logger.Printf("joined channel %q", ch)
    755                                 uc.channels[ch] = &upstreamChannel{
     783                                members := membershipsCasemapMap{newCasemapMap(0)}
     784                                members.casemap = uc.network.casemap
     785                                uc.channels.SetValue(ch, &upstreamChannel{
    756786                                        Name:    ch,
    757787                                        conn:    uc,
    758                                         Members: make(map[string]*memberships),
    759                                 }
     788                                        Members: members,
     789                                })
    760790                                uc.updateChannelAutoDetach(ch)
    761791
     
    769799                                        return err
    770800                                }
    771                                 ch.Members[msg.Prefix.Name] = &memberships{}
     801                                ch.Members.SetValue(msg.Prefix.Name, &memberships{})
    772802                        }
    773803
     
    787817
    788818                for _, ch := range strings.Split(channels, ",") {
    789                         if msg.Prefix.Name == uc.nick {
     819                        if uc.isOurNick(msg.Prefix.Name) {
    790820                                uc.logger.Printf("parted channel %q", ch)
    791                                 if uch, ok := uc.channels[ch]; ok {
    792                                         delete(uc.channels, ch)
     821                                uch := uc.channels.Value(ch)
     822                                if uch != nil {
     823                                        uc.channels.Delete(ch)
    793824                                        uch.updateAutoDetach(0)
    794825                                }
     
    798829                                        return err
    799830                                }
    800                                 delete(ch.Members, msg.Prefix.Name)
     831                                ch.Members.Delete(msg.Prefix.Name)
    801832                        }
    802833
     
    815846                }
    816847
    817                 if user == uc.nick {
     848                if uc.isOurNick(user) {
    818849                        uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
    819                         delete(uc.channels, channel)
     850                        uc.channels.Delete(channel)
    820851                } else {
    821852                        ch, err := uc.getChannel(channel)
     
    823854                                return err
    824855                        }
    825                         delete(ch.Members, user)
     856                        ch.Members.Delete(user)
    826857                }
    827858
     
    832863                }
    833864
    834                 if msg.Prefix.Name == uc.nick {
     865                if uc.isOurNick(msg.Prefix.Name) {
    835866                        uc.logger.Printf("quit")
    836867                }
    837868
    838                 for _, ch := range uc.channels {
    839                         if _, ok := ch.Members[msg.Prefix.Name]; ok {
    840                                 delete(ch.Members, msg.Prefix.Name)
     869                for _, entry := range uc.channels.innerMap {
     870                        ch := entry.value.(*upstreamChannel)
     871                        if ch.Members.Has(msg.Prefix.Name) {
     872                                ch.Members.Delete(msg.Prefix.Name)
    841873
    842874                                uc.appendLog(ch.Name, msg)
     
    909941                        uc.appendLog(ch.Name, msg)
    910942
    911                         if ch, ok := uc.network.channels[name]; !ok || !ch.Detached {
     943                        c := uc.network.channels.Value(name)
     944                        if c == nil || !c.Detached {
    912945                                uc.forEachDownstream(func(dc *downstreamConn) {
    913946                                        params := make([]string, len(msg.Params))
     
    965998                }
    966999                if firstMode {
    967                         if c, ok := uc.network.channels[channel]; !ok || !c.Detached {
     1000                        c := uc.network.channels.Value(channel)
     1001                        if c == nil || !c.Detached {
    9681002                                modeStr, modeParams := ch.modes.Format()
    9691003
     
    10621096                }
    10631097
    1064                 ch, ok := uc.channels[name]
    1065                 if !ok {
     1098                ch := uc.channels.Value(name)
     1099                if ch == nil {
    10661100                        // NAMES on a channel we have not joined, forward to downstream
    10671101                        uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
     
    10911125                for _, s := range splitSpace(members) {
    10921126                        memberships, nick := uc.parseMembershipPrefix(s)
    1093                         ch.Members[nick] = memberships
     1127                        ch.Members.SetValue(nick, memberships)
    10941128                }
    10951129        case irc.RPL_ENDOFNAMES:
     
    10991133                }
    11001134
    1101                 ch, ok := uc.channels[name]
    1102                 if !ok {
     1135                ch := uc.channels.Value(name)
     1136                if ch == nil {
    11031137                        // NAMES on a channel we have not joined, forward to downstream
    11041138                        uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
     
    11191153                ch.complete = true
    11201154
    1121                 if c, ok := uc.network.channels[name]; !ok || !c.Detached {
     1155                c := uc.network.channels.Value(name)
     1156                if c == nil || !c.Detached {
    11221157                        uc.forEachDownstream(func(dc *downstreamConn) {
    11231158                                forwardChannel(dc, ch)
     
    12731308                }
    12741309
    1275                 weAreInvited := nick == uc.nick
     1310                weAreInvited := uc.isOurNick(nick)
    12761311
    12771312                uc.forEachDownstream(func(dc *downstreamConn) {
     
    13961431        case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
    13971432                // Ignore
    1398         case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
     1433        case irc.RPL_MOTDSTART, irc.RPL_MOTD:
    13991434                // Ignore
    14001435        case irc.RPL_LISTSTART:
     
    16021637func (uc *upstreamConn) register() {
    16031638        uc.nick = uc.network.Nick
     1639        uc.nickCM = uc.network.casemap(uc.nick)
    16041640        uc.username = uc.network.GetUsername()
    16051641        uc.realname = uc.network.GetRealname()
     
    17061742
    17071743        detached := false
    1708         if ch, ok := uc.network.channels[entity]; ok {
     1744        if ch := uc.network.channels.Value(entity); ch != nil {
    17091745                detached = ch.Detached
    17101746        }
    17111747
    1712         delivered, ok := uc.network.delivered[entity]
    1713         if !ok {
    1714                 lastID, err := uc.user.msgStore.LastMsgID(uc.network, entity, time.Now())
     1748        delivered := uc.network.delivered.Value(entity)
     1749        entityCM := uc.network.casemap(entity)
     1750        if delivered == nil {
     1751                lastID, err := uc.user.msgStore.LastMsgID(uc.network, entityCM, time.Now())
    17151752                if err != nil {
    17161753                        uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
     
    17191756
    17201757                delivered = make(map[string]string)
    1721                 uc.network.delivered[entity] = delivered
     1758                uc.network.delivered.SetValue(entity, delivered)
    17221759
    17231760                for clientName, _ := range uc.network.offlineClients {
     
    17341771        }
    17351772
    1736         msgID, err := uc.user.msgStore.Append(uc.network, entity, msg)
     1773        msgID, err := uc.user.msgStore.Append(uc.network, entityCM, msg)
    17371774        if err != nil {
    17381775                uc.logger.Printf("failed to log message: %v", err)
     
    17551792
    17561793        // Don't forward messages if it's a detached channel
    1757         if ch, ok := uc.network.channels[target]; ok && ch.Detached {
     1794        ch := uc.network.channels.Value(target)
     1795        if ch != nil && ch.Detached {
    17581796                return
    17591797        }
     
    17901828
    17911829func (uc *upstreamConn) updateChannelAutoDetach(name string) {
    1792         if uch, ok := uc.channels[name]; ok {
    1793                 if ch, ok := uc.network.channels[name]; ok && !ch.Detached {
    1794                         uch.updateAutoDetach(ch.DetachAfter)
    1795                 }
    1796         }
    1797 }
     1830        uch := uc.channels.Value(name)
     1831        if uch == nil {
     1832                return
     1833        }
     1834        ch := uc.network.channels.Value(name)
     1835        if ch == nil || ch.Detached {
     1836                return
     1837        }
     1838        uch.updateAutoDetach(ch.DetachAfter)
     1839}
Note: See TracChangeset for help on using the changeset viewer.