mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
229 lines
5.7 KiB
Go
229 lines
5.7 KiB
Go
|
package imapserver
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"path/filepath"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/mjl-/bstore"
|
||
|
"github.com/mjl-/mox/store"
|
||
|
)
|
||
|
|
||
|
// LIST command, for listing mailboxes with various attributes, including about subscriptions and children.
|
||
|
// We don't have flags Marked, Unmarked, NoSelect and NoInferiors and we don't have REMOTE mailboxes.
|
||
|
//
|
||
|
// State: Authenticated and selected.
|
||
|
func (c *conn) cmdList(tag, cmd string, p *parser) {
|
||
|
// Command: ../rfc/9051:2224 ../rfc/6154:144 ../rfc/5258:193 ../rfc/3501:2191
|
||
|
// Examples: ../rfc/9051:2755 ../rfc/6154:347 ../rfc/5258:679 ../rfc/3501:2359
|
||
|
|
||
|
// Request syntax: ../rfc/9051:6600 ../rfc/6154:478 ../rfc/5258:1095 ../rfc/3501:4793
|
||
|
p.xspace()
|
||
|
var isExtended bool
|
||
|
var listSubscribed bool
|
||
|
var listRecursive bool
|
||
|
if p.take("(") {
|
||
|
// ../rfc/9051:6633
|
||
|
isExtended = true
|
||
|
selectOptions := map[string]bool{}
|
||
|
var nbase int
|
||
|
for !p.take(")") {
|
||
|
if len(selectOptions) > 0 {
|
||
|
p.xspace()
|
||
|
}
|
||
|
w := p.xatom()
|
||
|
W := strings.ToUpper(w)
|
||
|
switch W {
|
||
|
case "REMOTE":
|
||
|
case "RECURSIVEMATCH":
|
||
|
listRecursive = true
|
||
|
case "SUBSCRIBED":
|
||
|
nbase++
|
||
|
listSubscribed = true
|
||
|
default:
|
||
|
// ../rfc/9051:2398
|
||
|
xsyntaxErrorf("bad list selection option %q", w)
|
||
|
}
|
||
|
// Duplicates must be accepted. ../rfc/9051:2399
|
||
|
selectOptions[W] = true
|
||
|
}
|
||
|
if listRecursive && nbase == 0 {
|
||
|
// ../rfc/9051:6640
|
||
|
xsyntaxErrorf("cannot have RECURSIVEMATCH selection option without other (base) selection option")
|
||
|
}
|
||
|
p.xspace()
|
||
|
}
|
||
|
reference := p.xmailbox()
|
||
|
p.xspace()
|
||
|
patterns, isList := p.xmboxOrPat()
|
||
|
isExtended = isExtended || isList
|
||
|
var retSubscribed, retChildren, retSpecialUse bool
|
||
|
var retStatusAttrs []string
|
||
|
if p.take(" RETURN (") {
|
||
|
isExtended = true
|
||
|
// ../rfc/9051:6613 ../rfc/9051:6915 ../rfc/9051:7072 ../rfc/9051:6821 ../rfc/5819:95
|
||
|
n := 0
|
||
|
for !p.take(")") {
|
||
|
if n > 0 {
|
||
|
p.xspace()
|
||
|
}
|
||
|
n++
|
||
|
w := p.xatom()
|
||
|
W := strings.ToUpper(w)
|
||
|
switch W {
|
||
|
case "SUBSCRIBED":
|
||
|
retSubscribed = true
|
||
|
case "CHILDREN":
|
||
|
// ../rfc/3348:44
|
||
|
retChildren = true
|
||
|
case "SPECIAL-USE":
|
||
|
// ../rfc/6154:478
|
||
|
retSpecialUse = true
|
||
|
case "STATUS":
|
||
|
// ../rfc/9051:7072 ../rfc/5819:181
|
||
|
p.xspace()
|
||
|
p.xtake("(")
|
||
|
retStatusAttrs = []string{p.xstatusAtt()}
|
||
|
for p.take(" ") {
|
||
|
retStatusAttrs = append(retStatusAttrs, p.xstatusAtt())
|
||
|
}
|
||
|
p.xtake(")")
|
||
|
default:
|
||
|
// ../rfc/9051:2398
|
||
|
xsyntaxErrorf("bad list return option %q", w)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
p.xempty()
|
||
|
|
||
|
if !isExtended && reference == "" && patterns[0] == "" {
|
||
|
// ../rfc/9051:2277 ../rfc/3501:2221
|
||
|
c.bwritelinef(`* LIST () "/" ""`)
|
||
|
c.ok(tag, cmd)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if isExtended {
|
||
|
// ../rfc/9051:2286
|
||
|
n := make([]string, 0, len(patterns))
|
||
|
for _, p := range patterns {
|
||
|
if p != "" {
|
||
|
n = append(n, p)
|
||
|
}
|
||
|
}
|
||
|
patterns = n
|
||
|
}
|
||
|
re := xmailboxPatternMatcher(reference, patterns)
|
||
|
var responseLines []string
|
||
|
|
||
|
c.account.WithRLock(func() {
|
||
|
c.xdbread(func(tx *bstore.Tx) {
|
||
|
type info struct {
|
||
|
mailbox *store.Mailbox
|
||
|
subscribed bool
|
||
|
}
|
||
|
names := map[string]info{}
|
||
|
hasSubscribedChild := map[string]bool{}
|
||
|
hasChild := map[string]bool{}
|
||
|
var nameList []string
|
||
|
|
||
|
q := bstore.QueryTx[store.Mailbox](tx)
|
||
|
err := q.ForEach(func(mb store.Mailbox) error {
|
||
|
names[mb.Name] = info{mailbox: &mb}
|
||
|
nameList = append(nameList, mb.Name)
|
||
|
for p := filepath.Dir(mb.Name); p != "."; p = filepath.Dir(p) {
|
||
|
hasChild[p] = true
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
xcheckf(err, "listing mailboxes")
|
||
|
|
||
|
qs := bstore.QueryTx[store.Subscription](tx)
|
||
|
err = qs.ForEach(func(sub store.Subscription) error {
|
||
|
info, ok := names[sub.Name]
|
||
|
info.subscribed = true
|
||
|
names[sub.Name] = info
|
||
|
if !ok {
|
||
|
nameList = append(nameList, sub.Name)
|
||
|
}
|
||
|
for p := filepath.Dir(sub.Name); p != "."; p = filepath.Dir(p) {
|
||
|
hasSubscribedChild[p] = true
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
xcheckf(err, "listing subscriptions")
|
||
|
|
||
|
sort.Strings(nameList) // For predictable order in tests.
|
||
|
|
||
|
for _, name := range nameList {
|
||
|
if !re.MatchString(name) {
|
||
|
continue
|
||
|
}
|
||
|
info := names[name]
|
||
|
|
||
|
var flags listspace
|
||
|
var extended listspace
|
||
|
if listRecursive && hasSubscribedChild[name] {
|
||
|
extended = listspace{bare("CHILDINFO"), listspace{dquote("SUBSCRIBED")}}
|
||
|
}
|
||
|
if listSubscribed && info.subscribed {
|
||
|
flags = append(flags, bare(`\Subscribed`))
|
||
|
if info.mailbox == nil {
|
||
|
flags = append(flags, bare(`\NonExistent`))
|
||
|
}
|
||
|
}
|
||
|
if (info.mailbox == nil || listSubscribed) && flags == nil && extended == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if retChildren {
|
||
|
var f string
|
||
|
if hasChild[name] {
|
||
|
f = `\HasChildren`
|
||
|
} else {
|
||
|
f = `\HasNoChildren`
|
||
|
}
|
||
|
flags = append(flags, bare(f))
|
||
|
}
|
||
|
if !listSubscribed && retSubscribed && info.subscribed {
|
||
|
flags = append(flags, bare(`\Subscribed`))
|
||
|
}
|
||
|
if retSpecialUse && info.mailbox != nil {
|
||
|
if info.mailbox.Archive {
|
||
|
flags = append(flags, bare(`\Archive`))
|
||
|
}
|
||
|
if info.mailbox.Draft {
|
||
|
flags = append(flags, bare(`\Draft`))
|
||
|
}
|
||
|
if info.mailbox.Junk {
|
||
|
flags = append(flags, bare(`\Junk`))
|
||
|
}
|
||
|
if info.mailbox.Sent {
|
||
|
flags = append(flags, bare(`\Sent`))
|
||
|
}
|
||
|
if info.mailbox.Trash {
|
||
|
flags = append(flags, bare(`\Trash`))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var extStr string
|
||
|
if extended != nil {
|
||
|
extStr = " " + extended.pack(c)
|
||
|
}
|
||
|
line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), astring(name).pack(c), extStr)
|
||
|
responseLines = append(responseLines, line)
|
||
|
|
||
|
if retStatusAttrs != nil && info.mailbox != nil {
|
||
|
responseLines = append(responseLines, c.xstatusLine(tx, *info.mailbox, retStatusAttrs))
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
|
||
|
for _, line := range responseLines {
|
||
|
c.bwritelinef("%s", line)
|
||
|
}
|
||
|
c.ok(tag, cmd)
|
||
|
}
|