From a92d0c4c2e6e2b997a2512a2773958b842ac230c Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Mon, 28 Nov 2022 21:15:35 -0600 Subject: imap: add LIST-STATUS extension Add IMAP extension LIST-STATUS handling to IMAP worker. Reference: https://www.rfc-editor.org/rfc/rfc5819.html Signed-off-by: Tim Culverhouse Acked-by: Robin Jarry --- worker/imap/extensions/liststatus.go | 149 +++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 worker/imap/extensions/liststatus.go (limited to 'worker/imap') diff --git a/worker/imap/extensions/liststatus.go b/worker/imap/extensions/liststatus.go new file mode 100644 index 00000000..44b93a76 --- /dev/null +++ b/worker/imap/extensions/liststatus.go @@ -0,0 +1,149 @@ +package extensions + +import ( + "fmt" + "strings" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/client" + "github.com/emersion/go-imap/responses" + "github.com/emersion/go-imap/utf7" +) + +// A LIST-STATUS client +type ListStatusClient struct { + c *client.Client +} + +func NewListStatusClient(c *client.Client) *ListStatusClient { + return &ListStatusClient{c} +} + +// SupportListStatus checks if the server supports the LIST-STATUS extension. +func (c *ListStatusClient) SupportListStatus() (bool, error) { + return c.c.Support("LIST-STATUS") +} + +// ListStatus performs a LIST-STATUS command, listing mailboxes and also +// retrieving the requested status items. A nil channel can be passed in order +// to only retrieve the STATUS responses +func (c *ListStatusClient) ListStatus( + ref string, + name string, + items []imap.StatusItem, + ch chan *imap.MailboxInfo, +) ([]*imap.MailboxStatus, error) { + if ch != nil { + defer close(ch) + } + + if c.c.State() != imap.AuthenticatedState && c.c.State() != imap.SelectedState { + return nil, client.ErrNotLoggedIn + } + + cmd := &ListStatusCommand{ + Reference: ref, + Mailbox: name, + Items: items, + } + res := &ListStatusResponse{Mailboxes: ch} + + status, err := c.c.Execute(cmd, res) + if err != nil { + return nil, err + } + return res.Statuses, status.Err() +} + +// ListStatusCommand is a LIST command, as defined in RFC 3501 section 6.3.8. If +// Subscribed is set to true, LSUB will be used instead. Mailbox statuses will +// be returned if Items is not nil +type ListStatusCommand struct { + Reference string + Mailbox string + + Subscribed bool + Items []imap.StatusItem +} + +func (cmd *ListStatusCommand) Command() *imap.Command { + name := "LIST" + if cmd.Subscribed { + name = "LSUB" + } + + enc := utf7.Encoding.NewEncoder() + ref, _ := enc.String(cmd.Reference) + mailbox, _ := enc.String(cmd.Mailbox) + + items := make([]string, len(cmd.Items)) + if cmd.Items != nil { + for i, item := range cmd.Items { + items[i] = string(item) + } + } + + args := fmt.Sprintf("RETURN (STATUS (%s))", strings.Join(items, " ")) + return &imap.Command{ + Name: name, + Arguments: []interface{}{ref, mailbox, imap.RawString(args)}, + } +} + +// A LIST-STATUS response +type ListStatusResponse struct { + Mailboxes chan *imap.MailboxInfo + Subscribed bool + Statuses []*imap.MailboxStatus +} + +func (r *ListStatusResponse) Name() string { + if r.Subscribed { + return "LSUB" + } else { + return "LIST" + } +} + +func (r *ListStatusResponse) Handle(resp imap.Resp) error { + name, _, ok := imap.ParseNamedResp(resp) + if !ok { + return responses.ErrUnhandled + } + switch name { + case "LIST": + if r.Mailboxes == nil { + return nil + } + res := responses.List{Mailboxes: r.Mailboxes} + return res.Handle(resp) + case "STATUS": + res := responses.Status{ + Mailbox: new(imap.MailboxStatus), + } + err := res.Handle(resp) + if err != nil { + return err + } + r.Statuses = append(r.Statuses, res.Mailbox) + default: + return responses.ErrUnhandled + } + + return nil +} + +func (r *ListStatusResponse) WriteTo(w *imap.Writer) error { + respName := r.Name() + + for mbox := range r.Mailboxes { + fields := []interface{}{imap.RawString(respName)} + fields = append(fields, mbox.Format()...) + + resp := imap.NewUntaggedResp(fields) + if err := resp.WriteTo(w); err != nil { + return err + } + } + return nil +} -- cgit