implement the imap metadata extension, rfc 5464

this allows setting per-mailbox and per-server annotations (metadata). we have
a fixed maximum for total number of annotations (1000) and their total size
(1000000 bytes). this size isn't held against the regular quota for simplicity.
we send unsolicited metadata responses when a connection is in the idle
command and a change to a metadata item is made.

we currently only implement the /private/ namespace.  we should implement the
/shared/ namespace, for mox-global metadata annotations.  only the admin should
be able to configure those, probably through the config file, cli, or admin web
interface.

for issue #290
This commit is contained in:
Mechiel Lukkien
2025-02-17 22:44:51 +01:00
parent 9dff879164
commit f30c44eddb
17 changed files with 820 additions and 41 deletions

View File

@ -305,10 +305,10 @@ func (p *parser) xstring() (r string) {
p.xerrorf("missing closing dquote in string")
}
size, sync := p.xliteralSize(false, true)
s := p.conn.xreadliteral(size, sync)
buf := p.conn.xreadliteral(size, sync)
line := p.conn.readline(false)
p.orig, p.upper, p.o = line, toUpper(line), 0
return s
return string(buf)
}
func (p *parser) xnil() {
@ -974,3 +974,53 @@ func (p *parser) xdate() time.Time {
}
return time.Date(year, mon, day, 0, 0, 0, 0, time.UTC)
}
// Parse and validate a metadata key (entry name), returned as lower-case.
//
// ../rfc/5464:190
func (p *parser) xmetadataKey() string {
// ../rfc/5464:772
s := p.xastring()
// ../rfc/5464:192
if strings.Contains(s, "//") {
p.xerrorf("entry name must not contain two slashes")
}
// We allow a single slash, so it can be used with option "(depth infinity)" to get
// all annotations.
if s != "/" && strings.HasSuffix(s, "/") {
p.xerrorf("entry name must not end with slash")
}
// ../rfc/5464:202
if strings.Contains(s, "*") || strings.Contains(s, "%") {
p.xerrorf("entry name must not contain * or %%")
}
for _, c := range s {
if c < ' ' || c >= 0x7f {
p.xerrorf("entry name must only contain non-control ascii characters")
}
}
return strings.ToLower(s)
}
// ../rfc/5464:776
func (p *parser) xmetadataKeyValue() (key string, isString bool, value []byte) {
key = p.xmetadataKey()
p.xspace()
if p.hasPrefix("~{") {
size, sync := p.xliteralSize(true, true)
value = p.conn.xreadliteral(size, sync)
line := p.conn.readline(false)
p.orig, p.upper, p.o = line, toUpper(line), 0
} else if p.hasPrefix(`"`) {
value = []byte(p.xstring())
isString = true
} else if p.take("NIL") {
value = nil
} else {
p.xerrorf("expected metadata value")
}
return
}