diff --git a/downstream.go b/downstream.go index a713bef..5e57cb3 100644 --- a/downstream.go +++ b/downstream.go @@ -81,6 +81,33 @@ var needAllDownstreamCaps = map[string]string{ "multi-prefix": "", } +// passthroughIsupport is the set of ISUPPORT tokens that are directly passed +// through from the upstream server to downstream clients. +// +// This is only effective in single-upstream mode. +var passthroughIsupport = map[string]bool{ + "AWAYLEN": true, + "CHANLIMIT": true, + "CHANMODES": true, + "CHANNELLEN": true, + "CHANTYPES": true, + "EXCEPTS": true, + "EXTBAN": true, + "HOSTLEN": true, + "INVEX": true, + "KICKLEN": true, + "MAXLIST": true, + "MAXTARGETS": true, + "MODES": true, + "NETWORK": true, + "NICKLEN": true, + "PREFIX": true, + "SAFELIST": true, + "TARGMAX": true, + "TOPICLEN": true, + "USERLEN": true, +} + type downstreamConn struct { conn @@ -880,8 +907,18 @@ func (dc *downstreamConn) welcome() error { fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit), } - if uc := dc.upstream(); uc != nil && uc.isupport["NETWORK"] != nil { - isupport = append(isupport, fmt.Sprintf("NETWORK=%v", *uc.isupport["NETWORK"])) + if uc := dc.upstream(); uc != nil { + for k := range passthroughIsupport { + v, ok := uc.isupport[k] + if !ok { + continue + } + if v != nil { + isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v)) + } else { + isupport = append(isupport, k) + } + } } dc.SendMessage(&irc.Message{ @@ -904,12 +941,9 @@ func (dc *downstreamConn) welcome() error { Command: irc.RPL_MYINFO, Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"}, }) - // TODO: other RPL_ISUPPORT tokens - dc.SendMessage(&irc.Message{ - Prefix: dc.srv.prefix(), - Command: irc.RPL_ISUPPORT, - Params: append(append([]string{dc.nick}, isupport...), "are supported"), - }) + for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) { + dc.SendMessage(msg) + } dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), Command: irc.ERR_NOMOTD, diff --git a/irc.go b/irc.go index 9a09da4..94fdc7f 100644 --- a/irc.go +++ b/irc.go @@ -17,7 +17,10 @@ const ( err_invalidcapcmd = "410" ) -const maxMessageLength = 512 +const ( + maxMessageLength = 512 + maxMessageParams = 15 +) // The server-time layout, as defined in the IRCv3 spec. const serverTimeLayout = "2006-01-02T15:04:05.000Z" @@ -348,6 +351,30 @@ func join(channels, keys []string) []*irc.Message { return msgs } +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 diff --git a/upstream.go b/upstream.go index c0c86b8..91071de 100644 --- a/upstream.go +++ b/upstream.go @@ -613,6 +613,8 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { if err := parseMessageParams(msg, nil, nil); err != nil { return err } + + var downstreamIsupport []string for _, token := range msg.Params[1 : len(msg.Params)-1] { parameter := token var negate, hasValue bool @@ -658,7 +660,21 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { if err != nil { return err } + + if passthroughIsupport[parameter] { + downstreamIsupport = append(downstreamIsupport, token) + } } + + uc.forEachDownstream(func(dc *downstreamConn) { + if dc.network == nil { + return + } + msgs := generateIsupport(dc.srv.prefix(), dc.nick, downstreamIsupport) + for _, msg := range msgs { + dc.SendMessage(msg) + } + }) case "BATCH": var tag string if err := parseMessageParams(msg, &tag); err != nil {