260 lines
5.8 KiB
Go
260 lines
5.8 KiB
Go
package imapclient
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/emersion/go-imap/v2"
|
|
"github.com/emersion/go-imap/v2/internal"
|
|
"github.com/emersion/go-imap/v2/internal/imapwire"
|
|
)
|
|
|
|
func getSelectOpts(options *imap.ListOptions) []string {
|
|
if options == nil {
|
|
return nil
|
|
}
|
|
|
|
var l []string
|
|
if options.SelectSubscribed {
|
|
l = append(l, "SUBSCRIBED")
|
|
}
|
|
if options.SelectRemote {
|
|
l = append(l, "REMOTE")
|
|
}
|
|
if options.SelectRecursiveMatch {
|
|
l = append(l, "RECURSIVEMATCH")
|
|
}
|
|
if options.SelectSpecialUse {
|
|
l = append(l, "SPECIAL-USE")
|
|
}
|
|
return l
|
|
}
|
|
|
|
func getReturnOpts(options *imap.ListOptions) []string {
|
|
if options == nil {
|
|
return nil
|
|
}
|
|
|
|
var l []string
|
|
if options.ReturnSubscribed {
|
|
l = append(l, "SUBSCRIBED")
|
|
}
|
|
if options.ReturnChildren {
|
|
l = append(l, "CHILDREN")
|
|
}
|
|
if options.ReturnStatus != nil {
|
|
l = append(l, "STATUS")
|
|
}
|
|
if options.ReturnSpecialUse {
|
|
l = append(l, "SPECIAL-USE")
|
|
}
|
|
return l
|
|
}
|
|
|
|
// List sends a LIST command.
|
|
//
|
|
// The caller must fully consume the ListCommand. A simple way to do so is to
|
|
// defer a call to ListCommand.Close.
|
|
//
|
|
// A nil options pointer is equivalent to a zero options value.
|
|
//
|
|
// A non-zero options value requires support for IMAP4rev2 or the LIST-EXTENDED
|
|
// extension.
|
|
func (c *Client) List(ref, pattern string, options *imap.ListOptions) *ListCommand {
|
|
cmd := &ListCommand{
|
|
mailboxes: make(chan *imap.ListData, 64),
|
|
returnStatus: options != nil && options.ReturnStatus != nil,
|
|
}
|
|
enc := c.beginCommand("LIST", cmd)
|
|
if selectOpts := getSelectOpts(options); len(selectOpts) > 0 {
|
|
enc.SP().List(len(selectOpts), func(i int) {
|
|
enc.Atom(selectOpts[i])
|
|
})
|
|
}
|
|
enc.SP().Mailbox(ref).SP().Mailbox(pattern)
|
|
if returnOpts := getReturnOpts(options); len(returnOpts) > 0 {
|
|
enc.SP().Atom("RETURN").SP().List(len(returnOpts), func(i int) {
|
|
opt := returnOpts[i]
|
|
enc.Atom(opt)
|
|
if opt == "STATUS" {
|
|
returnStatus := statusItems(options.ReturnStatus)
|
|
enc.SP().List(len(returnStatus), func(j int) {
|
|
enc.Atom(returnStatus[j])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
enc.end()
|
|
return cmd
|
|
}
|
|
|
|
func (c *Client) handleList() error {
|
|
data, err := readList(c.dec)
|
|
if err != nil {
|
|
return fmt.Errorf("in LIST: %v", err)
|
|
}
|
|
|
|
cmd := c.findPendingCmdFunc(func(cmd command) bool {
|
|
switch cmd := cmd.(type) {
|
|
case *ListCommand:
|
|
return true // TODO: match pattern, check if already handled
|
|
case *SelectCommand:
|
|
return cmd.mailbox == data.Mailbox && cmd.data.List == nil
|
|
default:
|
|
return false
|
|
}
|
|
})
|
|
switch cmd := cmd.(type) {
|
|
case *ListCommand:
|
|
if cmd.returnStatus {
|
|
if cmd.pendingData != nil {
|
|
cmd.mailboxes <- cmd.pendingData
|
|
}
|
|
cmd.pendingData = data
|
|
} else {
|
|
cmd.mailboxes <- data
|
|
}
|
|
case *SelectCommand:
|
|
cmd.data.List = data
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListCommand is a LIST command.
|
|
type ListCommand struct {
|
|
commandBase
|
|
mailboxes chan *imap.ListData
|
|
|
|
returnStatus bool
|
|
pendingData *imap.ListData
|
|
}
|
|
|
|
// Next advances to the next mailbox.
|
|
//
|
|
// On success, the mailbox LIST data is returned. On error or if there are no
|
|
// more mailboxes, nil is returned.
|
|
func (cmd *ListCommand) Next() *imap.ListData {
|
|
return <-cmd.mailboxes
|
|
}
|
|
|
|
// Close releases the command.
|
|
//
|
|
// Calling Close unblocks the IMAP client decoder and lets it read the next
|
|
// responses. Next will always return nil after Close.
|
|
func (cmd *ListCommand) Close() error {
|
|
for cmd.Next() != nil {
|
|
// ignore
|
|
}
|
|
return cmd.wait()
|
|
}
|
|
|
|
// Collect accumulates mailboxes into a list.
|
|
//
|
|
// This is equivalent to calling Next repeatedly and then Close.
|
|
func (cmd *ListCommand) Collect() ([]*imap.ListData, error) {
|
|
var l []*imap.ListData
|
|
for {
|
|
data := cmd.Next()
|
|
if data == nil {
|
|
break
|
|
}
|
|
l = append(l, data)
|
|
}
|
|
return l, cmd.Close()
|
|
}
|
|
|
|
func readList(dec *imapwire.Decoder) (*imap.ListData, error) {
|
|
var data imap.ListData
|
|
|
|
var err error
|
|
data.Attrs, err = internal.ExpectMailboxAttrList(dec)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("in mbx-list-flags: %w", err)
|
|
}
|
|
|
|
if !dec.ExpectSP() {
|
|
return nil, dec.Err()
|
|
}
|
|
|
|
data.Delim, err = readDelim(dec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !dec.ExpectSP() || !dec.ExpectMailbox(&data.Mailbox) {
|
|
return nil, dec.Err()
|
|
}
|
|
|
|
if dec.SP() {
|
|
err := dec.ExpectList(func() error {
|
|
var tag string
|
|
if !dec.ExpectAString(&tag) || !dec.ExpectSP() {
|
|
return dec.Err()
|
|
}
|
|
var err error
|
|
switch strings.ToUpper(tag) {
|
|
case "CHILDINFO":
|
|
data.ChildInfo, err = readChildInfoExtendedItem(dec)
|
|
if err != nil {
|
|
return fmt.Errorf("in childinfo-extended-item: %v", err)
|
|
}
|
|
case "OLDNAME":
|
|
data.OldName, err = readOldNameExtendedItem(dec)
|
|
if err != nil {
|
|
return fmt.Errorf("in oldname-extended-item: %v", err)
|
|
}
|
|
default:
|
|
if !dec.DiscardValue() {
|
|
return fmt.Errorf("in tagged-ext-val: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("in mbox-list-extended: %v", err)
|
|
}
|
|
}
|
|
|
|
return &data, nil
|
|
}
|
|
|
|
func readChildInfoExtendedItem(dec *imapwire.Decoder) (*imap.ListDataChildInfo, error) {
|
|
var childInfo imap.ListDataChildInfo
|
|
err := dec.ExpectList(func() error {
|
|
var opt string
|
|
if !dec.ExpectAString(&opt) {
|
|
return dec.Err()
|
|
}
|
|
if strings.ToUpper(opt) == "SUBSCRIBED" {
|
|
childInfo.Subscribed = true
|
|
}
|
|
return nil
|
|
})
|
|
return &childInfo, err
|
|
}
|
|
|
|
func readOldNameExtendedItem(dec *imapwire.Decoder) (string, error) {
|
|
var name string
|
|
if !dec.ExpectSpecial('(') || !dec.ExpectMailbox(&name) || !dec.ExpectSpecial(')') {
|
|
return "", dec.Err()
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
func readDelim(dec *imapwire.Decoder) (rune, error) {
|
|
var delimStr string
|
|
if dec.Quoted(&delimStr) {
|
|
delim, size := utf8.DecodeRuneInString(delimStr)
|
|
if delim == utf8.RuneError || size != len(delimStr) {
|
|
return 0, fmt.Errorf("mailbox delimiter must be a single rune")
|
|
}
|
|
return delim, nil
|
|
} else if !dec.ExpectNIL() {
|
|
return 0, dec.Err()
|
|
} else {
|
|
return 0, nil
|
|
}
|
|
}
|