9 "github.com/mjl-/bstore"
11 "github.com/mjl-/mox/mox-"
12 "github.com/mjl-/mox/store"
15// LIST command, for listing mailboxes with various attributes, including about subscriptions and children.
16// We don't have flags Marked, Unmarked, NoSelect and NoInferiors and we don't have REMOTE mailboxes.
18// State: Authenticated and selected.
19func (c *conn) cmdList(tag, cmd string, p *parser) {
26 var listSubscribed bool
27 var listRecursive bool
31 selectOptions := map[string]bool{}
34 if len(selectOptions) > 0 {
38 W := strings.ToUpper(w)
41 case "RECURSIVEMATCH":
48 xsyntaxErrorf("bad list selection option %q", w)
51 selectOptions[W] = true
53 if listRecursive && nbase == 0 {
55 xsyntaxErrorf("cannot have RECURSIVEMATCH selection option without other (base) selection option")
59 reference := p.xmailbox()
61 patterns, isList := p.xmboxOrPat()
62 isExtended = isExtended || isList
63 var retSubscribed, retChildren bool
64 var retStatusAttrs []string
65 var retMetadata []string
66 if p.take(" RETURN (") {
76 W := strings.ToUpper(w)
85 // We always include special-use mailbox flags. Mac OS X Mail 16.0 (sept 2023) does
91 retStatusAttrs = []string{p.xstatusAtt()}
93 retStatusAttrs = append(retStatusAttrs, p.xstatusAtt())
101 s := p.xmetadataKey()
102 retMetadata = append(retMetadata, s)
110 xsyntaxErrorf("bad list return option %q", w)
116 if !isExtended && reference == "" && patterns[0] == "" {
118 c.bwritelinef(`* LIST () "/" ""`)
125 n := make([]string, 0, len(patterns))
126 for _, p := range patterns {
133 re := xmailboxPatternMatcher(reference, patterns)
134 var responseLines []string
135 var respMetadata []concatspace
137 c.account.WithRLock(func() {
138 c.xdbread(func(tx *bstore.Tx) {
140 mailbox *store.Mailbox
143 names := map[string]info{}
144 hasSubscribedChild := map[string]bool{}
145 hasChild := map[string]bool{}
146 var nameList []string
148 q := bstore.QueryTx[store.Mailbox](tx)
149 q.FilterEqual("Expunged", false)
150 err := q.ForEach(func(mb store.Mailbox) error {
151 names[mb.Name] = info{mailbox: &mb}
152 nameList = append(nameList, mb.Name)
153 for p := mox.ParentMailboxName(mb.Name); p != ""; p = mox.ParentMailboxName(p) {
158 xcheckf(err, "listing mailboxes")
160 qs := bstore.QueryTx[store.Subscription](tx)
161 err = qs.ForEach(func(sub store.Subscription) error {
162 info, ok := names[sub.Name]
163 info.subscribed = true
164 names[sub.Name] = info
166 nameList = append(nameList, sub.Name)
168 for p := mox.ParentMailboxName(sub.Name); p != ""; p = mox.ParentMailboxName(p) {
169 hasSubscribedChild[p] = true
173 xcheckf(err, "listing subscriptions")
175 sort.Strings(nameList) // For predictable order in tests.
177 for _, name := range nameList {
178 if !re.MatchString(name) {
184 var extended listspace
185 if listRecursive && hasSubscribedChild[name] {
186 extended = listspace{bare("CHILDINFO"), listspace{dquote("SUBSCRIBED")}}
188 if listSubscribed && info.subscribed {
189 flags = append(flags, bare(`\Subscribed`))
190 if info.mailbox == nil {
191 flags = append(flags, bare(`\NonExistent`))
194 if (info.mailbox == nil || listSubscribed) && flags == nil && extended == nil {
205 flags = append(flags, bare(f))
207 if !listSubscribed && retSubscribed && info.subscribed {
208 flags = append(flags, bare(`\Subscribed`))
210 if info.mailbox != nil {
211 add := func(b bool, v string) {
213 flags = append(flags, bare(v))
217 add(mb.Archive, `\Archive`)
218 add(mb.Draft, `\Drafts`)
219 add(mb.Junk, `\Junk`)
220 add(mb.Sent, `\Sent`)
221 add(mb.Trash, `\Trash`)
226 extStr = " " + extended.pack(c)
228 line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), mailboxt(name).pack(c), extStr)
229 responseLines = append(responseLines, line)
231 if retStatusAttrs != nil && info.mailbox != nil {
232 responseLines = append(responseLines, c.xstatusLine(tx, *info.mailbox, retStatusAttrs))
236 if info.mailbox != nil && len(retMetadata) > 0 {
238 for _, k := range retMetadata {
239 q := bstore.QueryTx[store.Annotation](tx)
240 q.FilterNonzero(store.Annotation{MailboxID: info.mailbox.ID, Key: k})
241 q.FilterEqual("Expunged", false)
244 if err == bstore.ErrAbsent {
247 xcheckf(err, "get annotation")
249 v = string0(string(a.Value))
251 v = readerSizeSyncliteral{bytes.NewReader(a.Value), int64(len(a.Value)), true}
254 meta = append(meta, astring(k), v)
256 line := concatspace{bare("*"), bare("METADATA"), mailboxt(info.mailbox.Name), meta}
257 respMetadata = append(respMetadata, line)
263 for _, line := range responseLines {
264 c.bwritelinef("%s", line)
266 for _, meta := range respMetadata {
267 meta.writeTo(c, c.xbw)