start more function names/calls with x when they handle errors through panics

mostly the imapserver and smtpserver connection write and read methods.
This commit is contained in:
Mechiel Lukkien
2025-04-02 13:59:46 +02:00
parent deb57462a4
commit 00c8db98e6
10 changed files with 195 additions and 196 deletions

View File

@ -579,7 +579,7 @@ func (c *conn) lineChan() chan lineErr {
}
// readline from either the c.line channel, or otherwise read from connection.
func (c *conn) readline(readCmd bool) string {
func (c *conn) xreadline(readCmd bool) string {
var line string
var err error
if c.line != nil {
@ -593,7 +593,7 @@ func (c *conn) readline(readCmd bool) string {
if readCmd && errors.Is(err, os.ErrDeadlineExceeded) {
err := c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
c.log.Check(err, "setting write deadline")
c.writelinef("* BYE inactive")
c.xwritelinef("* BYE inactive")
}
if !errors.Is(err, errIO) && !errors.Is(err, errProtocol) {
c.xbrokenf("%s (%w)", err, errIO)
@ -618,13 +618,13 @@ func (c *conn) readline(readCmd bool) string {
}
// write tagged command response, but first write pending changes.
func (c *conn) writeresultf(format string, args ...any) {
c.bwriteresultf(format, args...)
func (c *conn) xwriteresultf(format string, args ...any) {
c.xbwriteresultf(format, args...)
c.xflush()
}
// write buffered tagged command response, but first write pending changes.
func (c *conn) bwriteresultf(format string, args ...any) {
func (c *conn) xbwriteresultf(format string, args ...any) {
switch c.cmd {
case "fetch", "store", "search":
// ../rfc/9051:5862 ../rfc/7162:2033
@ -633,16 +633,16 @@ func (c *conn) bwriteresultf(format string, args ...any) {
c.applyChanges(c.comm.Get(), false)
}
}
c.bwritelinef(format, args...)
c.xbwritelinef(format, args...)
}
func (c *conn) writelinef(format string, args ...any) {
c.bwritelinef(format, args...)
func (c *conn) xwritelinef(format string, args ...any) {
c.xbwritelinef(format, args...)
c.xflush()
}
// Buffer line for write.
func (c *conn) bwritelinef(format string, args ...any) {
func (c *conn) xbwritelinef(format string, args ...any) {
format += "\r\n"
fmt.Fprintf(c.xbw, format, args...)
}
@ -671,7 +671,7 @@ func (c *conn) xflush() {
}
func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
line := c.readline(true)
line := c.xreadline(true)
p = newParser(line, c)
p.context("tag")
*tag = p.xtag()
@ -683,7 +683,7 @@ func (c *conn) readCommand(tag *string) (cmd string, p *parser) {
func (c *conn) xreadliteral(size int64, sync bool) []byte {
if sync {
c.writelinef("+ ")
c.xwritelinef("+ ")
}
buf := make([]byte, size)
if size > 0 {
@ -817,13 +817,13 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
select {
case <-mox.Shutdown.Done():
// ../rfc/9051:5381
c.writelinef("* BYE mox shutting down")
c.xwritelinef("* BYE mox shutting down")
return
default:
}
if !limiterConnectionrate.Add(c.remoteIP, time.Now(), 1) {
c.writelinef("* BYE connection rate from your ip or network too high, slow down please")
c.xwritelinef("* BYE connection rate from your ip or network too high, slow down please")
return
}
@ -831,13 +831,13 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
if !mox.LimiterFailedAuth.CanAdd(c.remoteIP, time.Now(), 1) {
metrics.AuthenticationRatelimitedInc("imap")
c.log.Debug("refusing connection due to many auth failures", slog.Any("remoteip", c.remoteIP))
c.writelinef("* BYE too many auth failures")
c.xwritelinef("* BYE too many auth failures")
return
}
if !limiterConnections.Add(c.remoteIP, time.Now(), 1) {
c.log.Debug("refusing connection due to many open connections", slog.Any("remoteip", c.remoteIP))
c.writelinef("* BYE too many open connections from your ip or network")
c.xwritelinef("* BYE too many open connections from your ip or network")
return
}
defer limiterConnections.Add(c.remoteIP, time.Now(), -1)
@ -851,7 +851,7 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
acc, _, _, err := store.OpenEmail(c.log, preauthAddress, false)
if err != nil {
c.log.Debugx("open account for preauth address", err, slog.String("address", preauthAddress))
c.writelinef("* BYE open account for address: %s", err)
c.xwritelinef("* BYE open account for address: %s", err)
return
}
c.username = preauthAddress
@ -861,9 +861,9 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
if c.account != nil && !c.noPreauth {
c.state = stateAuthenticated
c.writelinef("* PREAUTH [CAPABILITY %s] mox imap welcomes %s", c.capabilities(), c.username)
c.xwritelinef("* PREAUTH [CAPABILITY %s] mox imap welcomes %s", c.capabilities(), c.username)
} else {
c.writelinef("* OK [CAPABILITY %s] mox imap", c.capabilities())
c.xwritelinef("* OK [CAPABILITY %s] mox imap", c.capabilities())
}
// Ensure any pending loginAttempt is written before we stop.
@ -1101,7 +1101,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
// Verify client after session resumption.
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
if err != nil {
c.writelinef("* BYE [ALERT] Error verifying client certificate after TLS session resumption: %s", err)
c.xwritelinef("* BYE [ALERT] Error verifying client certificate after TLS session resumption: %s", err)
c.xbrokenf("tls verify client certificate after resumption: %s (%w)", err, errIO)
}
}
@ -1181,7 +1181,7 @@ func (c *conn) command() {
// Other side is likely speaking something else than IMAP, send error message and
// stop processing because there is a good chance whatever they sent has multiple
// lines.
c.writelinef("* BYE please try again speaking imap")
c.xwritelinef("* BYE please try again speaking imap")
c.xbrokenf("not speaking imap (%w)", errIO)
}
c.log.Debugx("imap command syntax error", sxerr.err, logFields...)
@ -1192,13 +1192,13 @@ func (c *conn) command() {
c.log.Check(err, "setting write deadline")
}
if sxerr.line != "" {
c.bwritelinef("%s", sxerr.line)
c.xbwritelinef("%s", sxerr.line)
}
code := ""
if sxerr.code != "" {
code = "[" + sxerr.code + "] "
}
c.bwriteresultf("%s BAD %s%s unrecognized syntax/command: %v", tag, code, cmd, sxerr.errmsg)
c.xbwriteresultf("%s BAD %s%s unrecognized syntax/command: %v", tag, code, cmd, sxerr.errmsg)
if fatal {
c.xflush()
panic(fmt.Errorf("aborting connection after syntax error for command with non-sync literal: %w", errProtocol))
@ -1207,14 +1207,14 @@ func (c *conn) command() {
result = "servererror"
c.log.Errorx("imap command server error", err, logFields...)
debug.PrintStack()
c.bwriteresultf("%s NO %s %v", tag, cmd, err)
c.xbwriteresultf("%s NO %s %v", tag, cmd, err)
} else if errors.As(err, &uerr) {
result = "usererror"
c.log.Debugx("imap command user error", err, logFields...)
if uerr.code != "" {
c.bwriteresultf("%s NO [%s] %s %v", tag, uerr.code, cmd, err)
c.xbwriteresultf("%s NO [%s] %s %v", tag, uerr.code, cmd, err)
} else {
c.bwriteresultf("%s NO %s %v", tag, cmd, err)
c.xbwriteresultf("%s NO %s %v", tag, cmd, err)
}
} else {
// Other type of panic, we pass it on, aborting the connection.
@ -1234,7 +1234,7 @@ func (c *conn) command() {
select {
case <-mox.Shutdown.Done():
// ../rfc/9051:5375
c.writelinef("* BYE shutting down")
c.xwritelinef("* BYE shutting down")
c.xbrokenf("shutting down (%w)", errIO)
default:
}
@ -1527,7 +1527,7 @@ func (c *conn) xnumSetConditionUIDs(forDB, returnUIDs bool, isUID bool, nums num
}
func (c *conn) ok(tag, cmd string) {
c.bwriteresultf("%s OK %s done", tag, cmd)
c.xbwriteresultf("%s OK %s done", tag, cmd)
c.xflush()
}
@ -1646,14 +1646,14 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
// Write the exists, and the UID and flags as well. Hopefully the client waits for
// long enough after the EXISTS to see these messages, and doesn't request them
// again with a FETCH.
c.bwritelinef("* %d EXISTS", len(c.uids))
c.xbwritelinef("* %d EXISTS", len(c.uids))
for _, add := range adds {
seq := c.xsequence(add.UID)
var modseqStr string
if condstore {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", add.ModSeq.Client())
}
c.bwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, add.UID, flaglist(add.Flags, add.Keywords).pack(c), modseqStr)
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, add.UID, flaglist(add.Flags, add.Keywords).pack(c), modseqStr)
}
continue
}
@ -1679,14 +1679,14 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
if qresync {
vanishedUIDs.append(uint32(uid))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
}
if qresync {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
case store.ChangeFlags:
@ -1700,29 +1700,29 @@ func (c *conn) applyChanges(changes []store.Change, initial bool) {
if condstore {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", ch.ModSeq.Client())
}
c.bwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, ch.UID, flaglist(ch.Flags, ch.Keywords).pack(c), modseqStr)
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s%s)", seq, ch.UID, flaglist(ch.Flags, ch.Keywords).pack(c), modseqStr)
}
case store.ChangeRemoveMailbox:
// Only announce \NonExistent to modern clients, otherwise they may ignore the
// unrecognized \NonExistent and interpret this as a newly created mailbox, while
// the goal was to remove it...
if c.enabled[capIMAP4rev2] {
c.bwritelinef(`* LIST (\NonExistent) "/" %s`, mailboxt(ch.Name).pack(c))
c.xbwritelinef(`* LIST (\NonExistent) "/" %s`, mailboxt(ch.Name).pack(c))
}
case store.ChangeAddMailbox:
c.bwritelinef(`* LIST (%s) "/" %s`, strings.Join(ch.Flags, " "), mailboxt(ch.Mailbox.Name).pack(c))
c.xbwritelinef(`* LIST (%s) "/" %s`, strings.Join(ch.Flags, " "), mailboxt(ch.Mailbox.Name).pack(c))
case store.ChangeRenameMailbox:
// OLDNAME only with IMAP4rev2 or NOTIFY ../rfc/9051:2726 ../rfc/5465:628
var oldname string
if c.enabled[capIMAP4rev2] {
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(ch.OldName).pack(c))
}
c.bwritelinef(`* LIST (%s) "/" %s%s`, strings.Join(ch.Flags, " "), mailboxt(ch.NewName).pack(c), oldname)
c.xbwritelinef(`* LIST (%s) "/" %s%s`, strings.Join(ch.Flags, " "), mailboxt(ch.NewName).pack(c), oldname)
case store.ChangeAddSubscription:
c.bwritelinef(`* LIST (%s) "/" %s`, strings.Join(append([]string{`\Subscribed`}, ch.Flags...), " "), mailboxt(ch.Name).pack(c))
c.xbwritelinef(`* LIST (%s) "/" %s`, strings.Join(append([]string{`\Subscribed`}, ch.Flags...), " "), mailboxt(ch.Name).pack(c))
case store.ChangeAnnotation:
// ../rfc/5464:807 ../rfc/5464:788
c.bwritelinef(`* METADATA %s %s`, mailboxt(ch.MailboxName).pack(c), astring(ch.Key).pack(c))
c.xbwritelinef(`* METADATA %s %s`, mailboxt(ch.MailboxName).pack(c), astring(ch.Key).pack(c))
default:
panic(fmt.Sprintf("internal error, missing case for %#v", change))
}
@ -1742,7 +1742,7 @@ func (c *conn) cmdCapability(tag, cmd string, p *parser) {
caps := c.capabilities()
// Response syntax: ../rfc/9051:6427 ../rfc/3501:4655
c.bwritelinef("* CAPABILITY %s", caps)
c.xbwritelinef("* CAPABILITY %s", caps)
c.ok(tag, cmd)
}
@ -1790,7 +1790,7 @@ func (c *conn) cmdLogout(tag, cmd string, p *parser) {
c.unselect()
c.state = stateNotAuthenticated
// Response syntax: ../rfc/9051:6886 ../rfc/3501:4935
c.bwritelinef("* BYE thanks")
c.xbwritelinef("* BYE thanks")
c.ok(tag, cmd)
panic(cleanClose)
}
@ -1843,9 +1843,9 @@ func (c *conn) cmdID(tag, cmd string, p *parser) {
// Response syntax: ../rfc/2971:243
// We send our name, and only the version for authenticated users. ../rfc/2971:193
if c.state == stateAuthenticated || c.state == stateSelected {
c.bwritelinef(`* ID ("name" "mox" "version" %s)`, string0(moxvar.Version).pack(c))
c.xbwritelinef(`* ID ("name" "mox" "version" %s)`, string0(moxvar.Version).pack(c))
} else {
c.bwritelinef(`* ID ("name" "mox")`)
c.xbwritelinef(`* ID ("name" "mox")`)
}
c.ok(tag, cmd)
}
@ -1981,8 +1981,8 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
xreadInitial := func() []byte {
var line string
if p.empty() {
c.writelinef("+ ")
line = c.readline(false)
c.xwritelinef("+ ")
line = c.xreadline(false)
} else {
// ../rfc/9051:1407 ../rfc/4959:84
p.xspace()
@ -2005,7 +2005,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
}
xreadContinuation := func() []byte {
line := c.readline(false)
line := c.xreadline(false)
if line == "*" {
c.loginAttempt.Result = store.AuthAborted
xsyntaxErrorf("authenticate aborted by client")
@ -2074,7 +2074,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
// ../rfc/2195:82
chal := fmt.Sprintf("<%d.%d@%s>", uint64(mox.CryptoRandInt()), time.Now().UnixNano(), mox.Conf.Static.HostnameDomain.ASCII)
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(chal)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(chal)))
resp := xreadContinuation()
t := strings.Split(string(resp), " ")
@ -2202,14 +2202,14 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
})
s1, err := ss.ServerFirst(xscram.Iterations, xscram.Salt)
xcheckf(err, "scram first server step")
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s1)))
c2 := xreadContinuation()
s3, err := ss.Finish(c2, xscram.SaltedPassword)
if len(s3) > 0 {
c.writelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
c.xwritelinef("+ %s", base64.StdEncoding.EncodeToString([]byte(s3)))
}
if err != nil {
c.readline(false) // Should be "*" for cancellation.
c.xreadline(false) // Should be "*" for cancellation.
if errors.Is(err, scram.ErrInvalidProof) {
c.loginAttempt.Result = store.AuthBadCredentials
c.log.Info("failed authentication attempt", slog.String("username", username), slog.Any("remote", c.remoteIP))
@ -2304,7 +2304,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
c.loginAttempt.Result = store.AuthSuccess
c.authFailed = 0
c.state = stateAuthenticated
c.writeresultf("%s OK [CAPABILITY %s] authenticate done", tag, c.capabilities())
c.xwriteresultf("%s OK [CAPABILITY %s] authenticate done", tag, c.capabilities())
}
// Login logs in with username and password.
@ -2412,7 +2412,7 @@ func (c *conn) cmdLogin(tag, cmd string, p *parser) {
c.authFailed = 0
c.setSlow(false)
c.state = stateAuthenticated
c.writeresultf("%s OK [CAPABILITY %s] login done", tag, c.capabilities())
c.xwriteresultf("%s OK [CAPABILITY %s] login done", tag, c.capabilities())
}
// Enable explicitly opts in to an extension. A server can typically send new kinds
@ -2461,7 +2461,7 @@ func (c *conn) cmdEnable(tag, cmd string, p *parser) {
}
// Response syntax: ../rfc/9051:6520 ../rfc/5161:211
c.bwritelinef("* ENABLED%s", enabled)
c.xbwritelinef("* ENABLED%s", enabled)
c.ok(tag, cmd)
}
@ -2486,7 +2486,7 @@ func (c *conn) xensureCondstore(tx *bstore.Tx) {
} else {
mb = c.xmailboxID(tx, c.mailboxID)
}
c.bwritelinef("* OK [HIGHESTMODSEQ %d] after condstore-enabling command", mb.ModSeq.Client())
c.xbwritelinef("* OK [HIGHESTMODSEQ %d] after condstore-enabling command", mb.ModSeq.Client())
}
}
@ -2573,7 +2573,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
// ../rfc/9051:1809
if c.state == stateSelected {
// ../rfc/9051:1812 ../rfc/7162:2111
c.bwritelinef("* OK [CLOSED] x")
c.xbwritelinef("* OK [CLOSED] x")
c.unselect()
}
@ -2624,23 +2624,23 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
if len(mb.Keywords) > 0 {
flags = " " + strings.Join(mb.Keywords, " ")
}
c.bwritelinef(`* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s)`, flags)
c.bwritelinef(`* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x`)
c.xbwritelinef(`* FLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent%s)`, flags)
c.xbwritelinef(`* OK [PERMANENTFLAGS (\Seen \Answered \Flagged \Deleted \Draft $Forwarded $Junk $NotJunk $Phishing $MDNSent \*)] x`)
if !c.enabled[capIMAP4rev2] {
c.bwritelinef(`* 0 RECENT`)
c.xbwritelinef(`* 0 RECENT`)
}
c.bwritelinef(`* %d EXISTS`, len(c.uids))
c.xbwritelinef(`* %d EXISTS`, len(c.uids))
if !c.enabled[capIMAP4rev2] && firstUnseen > 0 {
// ../rfc/9051:8051 ../rfc/3501:1774
c.bwritelinef(`* OK [UNSEEN %d] x`, firstUnseen)
c.xbwritelinef(`* OK [UNSEEN %d] x`, firstUnseen)
}
c.bwritelinef(`* OK [UIDVALIDITY %d] x`, mb.UIDValidity)
c.bwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
c.bwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
c.xbwritelinef(`* OK [UIDVALIDITY %d] x`, mb.UIDValidity)
c.xbwritelinef(`* OK [UIDNEXT %d] x`, mb.UIDNext)
c.xbwritelinef(`* LIST () "/" %s`, mailboxt(mb.Name).pack(c))
if c.enabled[capCondstore] {
// ../rfc/7162:417
// ../rfc/7162-eid5055 ../rfc/7162:484 ../rfc/7162:1167
c.bwritelinef(`* OK [HIGHESTMODSEQ %d] x`, highestModSeq.Client())
c.xbwritelinef(`* OK [HIGHESTMODSEQ %d] x`, highestModSeq.Client())
}
// If QRESYNC uidvalidity matches, we send any changes. ../rfc/7162:1509
@ -2711,7 +2711,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
qrmodseq = m.ModSeq.Client() - 1
preVanished = 0
qrknownUIDs = nil
c.bwritelinef("* OK [ALERT] Synchronization inconsistency in client detected. Client tried to sync with a UID that was removed at or after the MODSEQ it sent in the request. Sending all historic message removals for selected mailbox. Full synchronization recommended.")
c.xbwritelinef("* OK [ALERT] Synchronization inconsistency in client detected. Client tried to sync with a UID that was removed at or after the MODSEQ it sent in the request. Sending all historic message removals for selected mailbox. Full synchronization recommended.")
}
} else if err != bstore.ErrAbsent {
xcheckf(err, "checking old client uid")
@ -2738,7 +2738,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
}
msgseq := c.sequence(m.UID)
if msgseq > 0 {
c.bwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", msgseq, m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", msgseq, m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
}
return nil
})
@ -2774,16 +2774,16 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) {
l := slices.Sorted(maps.Keys(vanishedUIDs))
// ../rfc/7162:1985
for _, s := range compactUIDSet(l).Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED (EARLIER) %s", s)
c.xbwritelinef("* VANISHED (EARLIER) %s", s)
}
}
}
if isselect {
c.bwriteresultf("%s OK [READ-WRITE] x", tag)
c.xbwriteresultf("%s OK [READ-WRITE] x", tag)
c.readonly = false
} else {
c.bwriteresultf("%s OK [READ-ONLY] x", tag)
c.xbwriteresultf("%s OK [READ-ONLY] x", tag)
c.readonly = true
}
c.mailboxID = mb.ID
@ -2870,7 +2870,7 @@ func (c *conn) cmdCreate(tag, cmd string, p *parser) {
if c.enabled[capIMAP4rev2] && n == name && name != origName && !(name == "Inbox" || strings.HasPrefix(name, "Inbox/")) {
oldname = fmt.Sprintf(` ("OLDNAME" (%s))`, mailboxt(origName).pack(c))
}
c.bwritelinef(`* LIST (\Subscribed) "/" %s%s`, mailboxt(n).pack(c), oldname)
c.xbwritelinef(`* LIST (\Subscribed) "/" %s%s`, mailboxt(n).pack(c), oldname)
}
c.ok(tag, cmd)
}
@ -3145,7 +3145,7 @@ func (c *conn) cmdLsub(tag, cmd string, p *parser) {
// Response syntax: ../rfc/3501:4833 ../rfc/3501:4837
for _, line := range lines {
c.bwritelinef("%s", line)
c.xbwritelinef("%s", line)
}
c.ok(tag, cmd)
}
@ -3163,7 +3163,7 @@ func (c *conn) cmdNamespace(tag, cmd string, p *parser) {
p.xempty()
// Response syntax: ../rfc/9051:6778 ../rfc/2342:415
c.bwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
c.xbwritelinef(`* NAMESPACE (("" "/")) NIL NIL`)
c.ok(tag, cmd)
}
@ -3200,7 +3200,7 @@ func (c *conn) cmdStatus(tag, cmd string, p *parser) {
})
})
c.bwritelinef("%s", responseLine)
c.xbwritelinef("%s", responseLine)
c.ok(tag, cmd)
}
@ -3394,7 +3394,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
defer store.CloseRemoveTempFile(c.log, a.file, "temporary message file")
f = a.file
c.writelinef("+ ")
c.xwritelinef("+ ")
} else {
// We'll discard the message and return an error as soon as we can (possible
// synchronizing literal of next message, or after we've seen all messages).
@ -3422,7 +3422,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
}
totalSize += msize
line := c.readline(false)
line := c.xreadline(false)
p = newParser(line, c)
if utf8 {
p.xtake(")")
@ -3524,7 +3524,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
c.uidAppend(a.m.UID)
}
// todo spec: with condstore/qresync, is there a mechanism to let the client know the modseq for the appended uid? in theory an untagged fetch with the modseq after the OK APPENDUID could make sense, but this probably isn't allowed.
c.bwritelinef("* %d EXISTS", len(c.uids))
c.xbwritelinef("* %d EXISTS", len(c.uids))
}
// ../rfc/4315:289 ../rfc/3502:236 APPENDUID
@ -3535,7 +3535,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
} else {
uidset = fmt.Sprintf("%d:%d", appends[0].m.UID, appends[len(appends)-1].m.UID)
}
c.writeresultf("%s OK [APPENDUID %d %s] appended", tag, mb.UIDValidity, uidset)
c.xwriteresultf("%s OK [APPENDUID %d %s] appended", tag, mb.UIDValidity, uidset)
}
// Idle makes a client wait until the server sends untagged updates, e.g. about
@ -3550,7 +3550,7 @@ func (c *conn) cmdIdle(tag, cmd string, p *parser) {
// Request syntax: ../rfc/9051:6594 ../rfc/2177:163
p.xempty()
c.writelinef("+ waiting")
c.xwritelinef("+ waiting")
var line string
wait:
@ -3566,7 +3566,7 @@ wait:
c.xflush()
case <-mox.Shutdown.Done():
// ../rfc/9051:5375
c.writelinef("* BYE shutting down")
c.xwritelinef("* BYE shutting down")
c.xbrokenf("shutting down (%w)", errIO)
}
}
@ -3616,13 +3616,13 @@ func (c *conn) cmdGetquotaroot(tag, cmd string, p *parser) {
// We only have one per account quota, we name it "" like the examples in the RFC.
// Response syntax: ../rfc/9208:668 ../rfc/2087:242
c.bwritelinef(`* QUOTAROOT %s ""`, astring(name).pack(c))
c.xbwritelinef(`* QUOTAROOT %s ""`, astring(name).pack(c))
// We only write the quota response if there is a limit. The syntax doesn't allow
// an empty list, so we cannot send the current disk usage if there is no limit.
if quota > 0 {
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
c.bwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
c.xbwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
}
c.ok(tag, cmd)
}
@ -3660,7 +3660,7 @@ func (c *conn) cmdGetquota(tag, cmd string, p *parser) {
// an empty list, so we cannot send the current disk usage if there is no limit.
if quota > 0 {
// Response syntax: ../rfc/9208:666 ../rfc/2087:239
c.bwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
c.xbwritelinef(`* QUOTA "" (STORAGE %d %d)`, (size+1024-1)/1024, (quota+1024-1)/1024)
}
c.ok(tag, cmd)
}
@ -3830,18 +3830,18 @@ func (c *conn) cmdxExpunge(tag, cmd string, uidSet *numSet) {
if qresync {
vanishedUIDs.append(uint32(m.UID))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
if !vanishedUIDs.empty() {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
if c.enabled[capCondstore] {
c.writeresultf("%s OK [HIGHESTMODSEQ %d] expunged", tag, highestModSeq.Client())
c.xwriteresultf("%s OK [HIGHESTMODSEQ %d] expunged", tag, highestModSeq.Client())
} else {
c.ok(tag, cmd)
}
@ -4119,7 +4119,7 @@ func (c *conn) cmdxCopy(isUID bool, tag, cmd string, p *parser) {
})
// ../rfc/9051:6881 ../rfc/4315:183
c.writeresultf("%s OK [COPYUID %d %s %s] copied", tag, mbDst.UIDValidity, compactUIDSet(origUIDs).String(), compactUIDSet(newUIDs).String())
c.xwriteresultf("%s OK [COPYUID %d %s %s] copied", tag, mbDst.UIDValidity, compactUIDSet(origUIDs).String(), compactUIDSet(newUIDs).String())
}
// Move moves messages from the currently selected/active mailbox to a named mailbox.
@ -4197,7 +4197,7 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
// ../rfc/9051:4708 ../rfc/6851:254
// ../rfc/9051:4713
newUIDs := numSet{ranges: []numRange{{setNumber{number: uint32(uidFirst)}, &setNumber{number: uint32(mbDst.UIDNext - 1)}}}}
c.bwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String())
c.xbwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String())
qresync := c.enabled[capQresync]
var vanishedUIDs numSet
for i := range uids {
@ -4206,19 +4206,19 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
if qresync {
vanishedUIDs.append(uint32(uids[i]))
} else {
c.bwritelinef("* %d EXPUNGE", seq)
c.xbwritelinef("* %d EXPUNGE", seq)
}
}
if !vanishedUIDs.empty() {
// VANISHED without EARLIER. ../rfc/7162:2004
for _, s := range vanishedUIDs.Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED %s", s)
c.xbwritelinef("* VANISHED %s", s)
}
}
if qresync {
// ../rfc/9051:6744 ../rfc/7162:1334
c.writeresultf("%s OK [HIGHESTMODSEQ %d] move", tag, modseq.Client())
c.xwriteresultf("%s OK [HIGHESTMODSEQ %d] move", tag, modseq.Client())
} else {
c.ok(tag, cmd)
}
@ -4570,7 +4570,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
modseqStr = fmt.Sprintf(" MODSEQ (%d)", m.ModSeq.Client())
}
// ../rfc/9051:6749 ../rfc/3501:4869 ../rfc/7162:2490
c.bwritelinef("* %d FETCH (UID %d%s%s)", c.xsequence(m.UID), m.UID, flags, modseqStr)
c.xbwritelinef("* %d FETCH (UID %d%s%s)", c.xsequence(m.UID), m.UID, flags, modseqStr)
}
}
@ -4588,7 +4588,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
// Also gather UIDs or sequences for the MODIFIED response below. ../rfc/7162:571
var mnums []store.UID
for _, m := range changed {
c.bwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", c.xsequence(m.UID), m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
c.xbwritelinef("* %d FETCH (UID %d FLAGS %s MODSEQ (%d))", c.xsequence(m.UID), m.UID, flaglist(m.Flags, m.Keywords).pack(c), m.ModSeq.Client())
if isUID {
mnums = append(mnums, m.UID)
} else {
@ -4599,5 +4599,5 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
slices.Sort(mnums)
set := compactUIDSet(mnums)
// ../rfc/7162:2506
c.writeresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String())
c.xwriteresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String())
}