imapserver: implement rfc 9590, returning metadata in the extended list command

only with "return" including "metadata". so clients can quickly get certain
metadata (eg for display, such as a color) for mailboxes.

this also adds a protocol token type "mailboxt" that properly encodes to utf7
if required.
This commit is contained in:
Mechiel Lukkien
2025-02-23 22:12:18 +01:00
parent 2809136451
commit 0ed820e3b0
8 changed files with 120 additions and 24 deletions

View File

@ -1,6 +1,7 @@
package imapserver
import (
"bytes"
"fmt"
"path"
"sort"
@ -60,6 +61,7 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
isExtended = isExtended || isList
var retSubscribed, retChildren bool
var retStatusAttrs []string
var retMetadata []string
if p.take(" RETURN (") {
isExtended = true
// ../rfc/9051:6613 ../rfc/9051:6915 ../rfc/9051:7072 ../rfc/9051:6821 ../rfc/5819:95
@ -90,6 +92,18 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
retStatusAttrs = append(retStatusAttrs, p.xstatusAtt())
}
p.xtake(")")
case "METADATA":
// ../rfc/9590:167
p.xspace()
p.xtake("(")
for {
s := p.xmetadataKey()
retMetadata = append(retMetadata, s)
if !p.space() {
break
}
}
p.xtake(")")
default:
// ../rfc/9051:2398
xsyntaxErrorf("bad list return option %q", w)
@ -117,6 +131,7 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
}
re := xmailboxPatternMatcher(reference, patterns)
var responseLines []string
var respMetadata []concatspace
c.account.WithRLock(func() {
c.xdbread(func(tx *bstore.Tx) {
@ -208,12 +223,34 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
if extended != nil {
extStr = " " + extended.pack(c)
}
line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), astring(c.encodeMailbox(name)).pack(c), extStr)
line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), mailboxt(name).pack(c), extStr)
responseLines = append(responseLines, line)
if retStatusAttrs != nil && info.mailbox != nil {
responseLines = append(responseLines, c.xstatusLine(tx, *info.mailbox, retStatusAttrs))
}
// ../rfc/9590:101
if info.mailbox != nil && len(retMetadata) > 0 {
var meta listspace
for _, k := range retMetadata {
a, err := bstore.QueryTx[store.Annotation](tx).FilterNonzero(store.Annotation{MailboxID: info.mailbox.ID, Key: k}).Get()
var v token
if err == bstore.ErrAbsent {
v = nilt
} else {
xcheckf(err, "get annotation")
if a.IsString {
v = string0(string(a.Value))
} else {
v = readerSizeSyncliteral{bytes.NewReader(a.Value), int64(len(a.Value)), true}
}
}
meta = append(meta, astring(k), v)
}
line := concatspace{bare("*"), bare("METADATA"), mailboxt(info.mailbox.Name), meta}
respMetadata = append(respMetadata, line)
}
}
})
})
@ -221,5 +258,9 @@ func (c *conn) cmdList(tag, cmd string, p *parser) {
for _, line := range responseLines {
c.bwritelinef("%s", line)
}
for _, meta := range respMetadata {
meta.writeTo(c, c.bw)
c.bwritelinef("")
}
c.ok(tag, cmd)
}