imapserver: implement MULTIAPPEND extension, rfc 3502

MULTIAPPEND modifies the existing APPEND command to allow multiple messages. it
is somewhat more involved than a regular append of a single message since the
operation (of adding multiple messages) must be atomic. either all are added,
or none are.

we check as early as possible if the messages won't cause an over-quota error.
This commit is contained in:
Mechiel Lukkien
2025-02-24 15:47:47 +01:00
parent b56d6c4061
commit 78e0c0255f
10 changed files with 286 additions and 106 deletions

View File

@ -196,7 +196,8 @@ func (c *Conn) TLSConnectionState() *tls.ConnectionState {
return nil
}
// Commandf writes a free-form IMAP command to the server.
// Commandf writes a free-form IMAP command to the server. An ending \r\n is
// written too.
// If tag is empty, a next unique tag is assigned.
func (c *Conn) Commandf(tag string, format string, args ...any) (rerr error) {
defer c.recover(&rerr)

View File

@ -205,8 +205,8 @@ func (c *Conn) xrespCode() (string, CodeArg) {
c.xspace()
destUIDValidity := c.xnzuint32()
c.xspace()
uid := c.xnzuint32()
codeArg = CodeAppendUID{destUIDValidity, uid}
uids := c.xuidrange()
codeArg = CodeAppendUID{destUIDValidity, uids}
case "COPYUID":
c.xspace()
destUIDValidity := c.xnzuint32()

View File

@ -38,6 +38,7 @@ const (
CapCreateSpecialUse Capability = "CREATE-SPECIAL-USE" // ../rfc/6154:296
CapCompressDeflate Capability = "COMPRESS=DEFLATE" // ../rfc/4978:65
CapListMetadata Capability = "LIST-METADTA" // ../rfc/9590:73
CapMultiAppend Capability = "MULTIAPPEND" // ../rfc/3502:33
)
// Status is the tagged final result of a command.
@ -111,11 +112,11 @@ func (c CodeUint) CodeString() string {
// "APPENDUID" response code.
type CodeAppendUID struct {
UIDValidity uint32
UID uint32
UIDs NumRange
}
func (c CodeAppendUID) CodeString() string {
return fmt.Sprintf("APPENDUID %d %d", c.UIDValidity, c.UID)
return fmt.Sprintf("APPENDUID %d %s", c.UIDValidity, c.UIDs.String())
}
// "COPYUID" response code.
@ -391,6 +392,13 @@ func ParseNumSet(s string) (ns NumSet, rerr error) {
return
}
func ParseUIDRange(s string) (nr NumRange, rerr error) {
c := Conn{br: bufio.NewReader(strings.NewReader(s))}
defer c.recover(&rerr)
nr = c.xuidrange()
return
}
// NumRange is a single number or range.
type NumRange struct {
First uint32 // 0 for "*".