aboutsummaryrefslogtreecommitdiffstats
path: root/worker/imap
diff options
context:
space:
mode:
authorTim Culverhouse <tim@timculverhouse.com>2022-11-28 21:15:35 -0600
committerRobin Jarry <robin@jarry.cc>2022-12-02 22:59:44 +0100
commita92d0c4c2e6e2b997a2512a2773958b842ac230c (patch)
treec06efde1ce254112f415d744d8cb64197bc67501 /worker/imap
parentdc7816aa8c6a1e023a33fe15b2ef99a0c5ac551f (diff)
downloadaerc-a92d0c4c2e6e2b997a2512a2773958b842ac230c.tar.gz
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 <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'worker/imap')
-rw-r--r--worker/imap/extensions/liststatus.go149
1 files changed, 149 insertions, 0 deletions
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
+}