diff --git a/irc/client.go b/irc/client.go index 8465fa6b..b5204b3d 100644 --- a/irc/client.go +++ b/irc/client.go @@ -146,6 +146,7 @@ type MultilineBatch struct { target string responseLabel string // this is the value of the labeled-response tag sent with BATCH message utils.SplitMessage + lenBytes int tags map[string]string } @@ -168,14 +169,14 @@ func (s *Session) EndMultilineBatch(label string) (batch MultilineBatch, err err s.fakelag.Unsuspend() // heuristics to estimate how much data they used while fakelag was suspended - fakelagBill := (batch.message.LenBytes() / 512) + 1 + fakelagBill := (batch.lenBytes / 512) + 1 fakelagBillLines := (batch.message.LenLines() * 60) / 512 if fakelagBill < fakelagBillLines { fakelagBill = fakelagBillLines } s.deferredFakelagCount = fakelagBill - if batch.label == "" || batch.label != label || batch.message.LenLines() == 0 { + if batch.label == "" || batch.label != label || !batch.message.ValidMultiline() { err = errInvalidMultilineBatch return } @@ -1357,9 +1358,14 @@ func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, session.SendRawMessage(msg, blocking) } } else { - for i, messagePair := range message.Split { + msgidSent := false // send msgid on the first nonblank line + for _, messagePair := range message.Split { + if len(messagePair.Message) == 0 { + continue + } var msgid string - if i == 0 { + if !msgidSent { + msgidSent = true msgid = message.Msgid } session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, tags, command, target, messagePair.Message) diff --git a/irc/handlers.go b/irc/handlers.go index 4427bdf9..467881e8 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1793,33 +1793,39 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp // helper to store a batched PRIVMSG in the session object func absorbBatchedMessage(server *Server, client *Client, msg ircmsg.IrcMessage, batchTag string, histType history.ItemType, rb *ResponseBuffer) { + var errorCode, errorMessage string + defer func() { + if errorCode != "" { + if histType != history.Notice { + rb.Add(nil, server.name, "FAIL", "BATCH", errorCode, errorMessage) + } + rb.session.EndMultilineBatch("") + } + }() + if batchTag != rb.session.batch.label { - if histType != history.Notice { - rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_INVALID", client.t("Incorrect batch tag sent")) - } - rb.session.EndMultilineBatch("") + errorCode, errorMessage = "MULTILINE_INVALID", client.t("Incorrect batch tag sent") return - } else if len(msg.Params) < 2 || msg.Params[1] == "" { - if histType != history.Notice { - rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_INVALID", client.t("Invalid multiline batch")) - } - rb.session.EndMultilineBatch("") + } else if len(msg.Params) < 2 { + errorCode, errorMessage = "MULTILINE_INVALID", client.t("Invalid multiline batch") return } rb.session.batch.command = msg.Command isConcat, _ := msg.GetTag(caps.MultilineConcatTag) + if isConcat && len(msg.Params[1]) == 0 { + errorCode, errorMessage = "MULTILINE_INVALID", client.t("Cannot send a blank line with the multiline concat tag") + return + } + if !isConcat && len(rb.session.batch.message.Split) != 0 { + rb.session.batch.lenBytes++ // bill for the newline + } rb.session.batch.message.Append(msg.Params[1], isConcat) + rb.session.batch.lenBytes += len(msg.Params[1]) config := server.Config() - if config.Limits.Multiline.MaxBytes < rb.session.batch.message.LenBytes() { - if histType != history.Notice { - rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_MAX_BYTES", strconv.Itoa(config.Limits.Multiline.MaxBytes)) - } - rb.session.EndMultilineBatch("") + if config.Limits.Multiline.MaxBytes < rb.session.batch.lenBytes { + errorCode, errorMessage = "MULTILINE_MAX_BYTES", strconv.Itoa(config.Limits.Multiline.MaxBytes) } else if config.Limits.Multiline.MaxLines != 0 && config.Limits.Multiline.MaxLines < rb.session.batch.message.LenLines() { - if histType != history.Notice { - rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_MAX_LINES", strconv.Itoa(config.Limits.Multiline.MaxLines)) - } - rb.session.EndMultilineBatch("") + errorCode, errorMessage = "MULTILINE_MAX_LINES", strconv.Itoa(config.Limits.Multiline.MaxLines) } } diff --git a/irc/utils/text.go b/irc/utils/text.go index 5d46b140..62c49eb5 100644 --- a/irc/utils/text.go +++ b/irc/utils/text.go @@ -67,14 +67,14 @@ func (sm *SplitMessage) LenLines() int { return len(sm.Split) } -func (sm *SplitMessage) LenBytes() (result int) { - if sm.Split == nil { - return len(sm.Message) - } +func (sm *SplitMessage) ValidMultiline() bool { + // must contain at least one nonblank line for i := 0; i < len(sm.Split); i++ { - result += len(sm.Split[i].Message) + if len(sm.Split[i].Message) != 0 { + return true + } } - return + return false } func (sm *SplitMessage) IsRestrictedCTCPMessage() bool {