9	"github.com/mjl-/bstore"
 
10	"github.com/mjl-/mox/store"
 
13// LIST command, for listing mailboxes with various attributes, including about subscriptions and children.
 
14// We don't have flags Marked, Unmarked, NoSelect and NoInferiors and we don't have REMOTE mailboxes.
 
16// State: Authenticated and selected.
 
17func (c *conn) cmdList(tag, cmd string, p *parser) {
 
24	var listSubscribed bool
 
25	var listRecursive bool
 
29		selectOptions := map[string]bool{}
 
32			if len(selectOptions) > 0 {
 
36			W := strings.ToUpper(w)
 
39			case "RECURSIVEMATCH":
 
46				xsyntaxErrorf("bad list selection option %q", w)
 
49			selectOptions[W] = true
 
51		if listRecursive && nbase == 0 {
 
53			xsyntaxErrorf("cannot have RECURSIVEMATCH selection option without other (base) selection option")
 
57	reference := p.xmailbox()
 
59	patterns, isList := p.xmboxOrPat()
 
60	isExtended = isExtended || isList
 
61	var retSubscribed, retChildren bool
 
62	var retStatusAttrs []string
 
63	if p.take(" RETURN (") {
 
73			W := strings.ToUpper(w)
 
82				// We always include special-use mailbox flags. Mac OS X Mail 16.0 (sept 2023) does
 
88				retStatusAttrs = []string{p.xstatusAtt()}
 
90					retStatusAttrs = append(retStatusAttrs, p.xstatusAtt())
 
95				xsyntaxErrorf("bad list return option %q", w)
 
101	if !isExtended && reference == "" && patterns[0] == "" {
 
103		c.bwritelinef(`* LIST () "/" ""`)
 
110		n := make([]string, 0, len(patterns))
 
111		for _, p := range patterns {
 
118	re := xmailboxPatternMatcher(reference, patterns)
 
119	var responseLines []string
 
121	c.account.WithRLock(func() {
 
122		c.xdbread(func(tx *bstore.Tx) {
 
124				mailbox    *store.Mailbox
 
127			names := map[string]info{}
 
128			hasSubscribedChild := map[string]bool{}
 
129			hasChild := map[string]bool{}
 
130			var nameList []string
 
132			q := bstore.QueryTx[store.Mailbox](tx)
 
133			err := q.ForEach(func(mb store.Mailbox) error {
 
134				names[mb.Name] = info{mailbox: &mb}
 
135				nameList = append(nameList, mb.Name)
 
136				for p := path.Dir(mb.Name); p != "."; p = path.Dir(p) {
 
141			xcheckf(err, "listing mailboxes")
 
143			qs := bstore.QueryTx[store.Subscription](tx)
 
144			err = qs.ForEach(func(sub store.Subscription) error {
 
145				info, ok := names[sub.Name]
 
146				info.subscribed = true
 
147				names[sub.Name] = info
 
149					nameList = append(nameList, sub.Name)
 
151				for p := path.Dir(sub.Name); p != "."; p = path.Dir(p) {
 
152					hasSubscribedChild[p] = true
 
156			xcheckf(err, "listing subscriptions")
 
158			sort.Strings(nameList) // For predictable order in tests.
 
160			for _, name := range nameList {
 
161				if !re.MatchString(name) {
 
167				var extended listspace
 
168				if listRecursive && hasSubscribedChild[name] {
 
169					extended = listspace{bare("CHILDINFO"), listspace{dquote("SUBSCRIBED")}}
 
171				if listSubscribed && info.subscribed {
 
172					flags = append(flags, bare(`\Subscribed`))
 
173					if info.mailbox == nil {
 
174						flags = append(flags, bare(`\NonExistent`))
 
177				if (info.mailbox == nil || listSubscribed) && flags == nil && extended == nil {
 
188					flags = append(flags, bare(f))
 
190				if !listSubscribed && retSubscribed && info.subscribed {
 
191					flags = append(flags, bare(`\Subscribed`))
 
193				if info.mailbox != nil {
 
194					if info.mailbox.Archive {
 
195						flags = append(flags, bare(`\Archive`))
 
197					if info.mailbox.Draft {
 
198						flags = append(flags, bare(`\Drafts`))
 
200					if info.mailbox.Junk {
 
201						flags = append(flags, bare(`\Junk`))
 
203					if info.mailbox.Sent {
 
204						flags = append(flags, bare(`\Sent`))
 
206					if info.mailbox.Trash {
 
207						flags = append(flags, bare(`\Trash`))
 
213					extStr = " " + extended.pack(c)
 
215				line := fmt.Sprintf(`* LIST %s "/" %s%s`, flags.pack(c), astring(c.encodeMailbox(name)).pack(c), extStr)
 
216				responseLines = append(responseLines, line)
 
218				if retStatusAttrs != nil && info.mailbox != nil {
 
219					responseLines = append(responseLines, c.xstatusLine(tx, *info.mailbox, retStatusAttrs))
 
225	for _, line := range responseLines {
 
226		c.bwritelinef("%s", line)