fix #2411: split long CAP ACK/NAK across multiple lines (#2414)

Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Co-authored-by: Shivaram Lingamneni <slingamn@cs.stanford.edu>
This commit is contained in:
Sai Asish Y
2026-07-07 21:29:36 -07:00
committed by GitHub
parent 5c69888479
commit 9a7bdda6a0
+27 -5
View File
@@ -676,7 +676,6 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
subCommand := strings.ToUpper(msg.Params[0])
toAdd := caps.NewSet()
toRemove := caps.NewSet()
var capString string
config := server.Config()
supportedCaps := config.Server.supportedCaps
@@ -687,10 +686,12 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
}
badCaps := false
var capString string
var capFields []string
if len(msg.Params) > 1 {
capString = msg.Params[1]
strs := strings.Fields(capString)
for _, str := range strs {
capFields = strings.Fields(capString)
for _, str := range capFields {
remove := false
if str[0] == '-' {
str = str[1:]
@@ -726,6 +727,27 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
}
}
// CAP ACK / CAP NAK may also overflow the 512-byte limit, so we may need
// to split it. Unlike LS / LIST, each ACK and NAK is processed independently
// and there is no line continuation form
sendCapReqResponse := func(response string) {
// :server.name CAP nickname ACK :list of caps\r\n
maxLen := (MaxLineLen - 2) - 1 - len(server.name) - 5 - len(details.nick) - 6
if len(capString) <= maxLen || len(capFields) == 0 {
rb.Add(nil, server.name, "CAP", details.nick, response, capString)
return
}
var t utils.TokenLineBuilder
t.Initialize(maxLen, " ")
for _, token := range capFields {
t.Add(token)
}
capLines := t.Lines()
for _, capStr := range capLines {
rb.Add(nil, server.name, "CAP", details.nick, response, capStr)
}
}
switch subCommand {
case "LS":
if !client.registered {
@@ -754,7 +776,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
// every offered capability. during registration, requesting it produces a quit,
// otherwise just a CAP NAK
if badCaps || (toAdd.Has(caps.Nope) && client.registered) {
rb.Add(nil, server.name, "CAP", details.nick, "NAK", capString)
sendCapReqResponse("NAK")
return false
} else if toAdd.Has(caps.Nope) && !client.registered {
client.Quit(fmt.Sprintf(client.t("Requesting the %s client capability is forbidden"), caps.Nope.Name()), rb.session, nil)
@@ -763,7 +785,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
rb.session.capabilities.Union(toAdd)
rb.session.capabilities.Subtract(toRemove)
rb.Add(nil, server.name, "CAP", details.nick, "ACK", capString)
sendCapReqResponse("ACK")
// XXX compatibility hack, if draft/metadata-2 was requested, silently enable draft/metadata-3
// this way we only have to check for one cap at runtime