From bcf737cbec4d4d834b88da88d862edbaa83cf40f Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Mon, 11 Mar 2024 15:22:41 +0100 Subject: [PATCH] fix the Status command on imapclient.Conn it needs at least 1 attribute. also make types for those attributes, so its harder to get them wrong. nothing was using this function. --- imapclient/cmds.go | 11 +++++++--- imapclient/parse.go | 4 ++-- imapclient/protocol.go | 19 ++++++++++++++++- imapserver/condstore_test.go | 8 +++---- imapserver/list_test.go | 18 ++++++++-------- imapserver/quota_test.go | 4 ++-- imapserver/server_test.go | 2 +- imapserver/status_test.go | 41 +++++++++++++++++++++++++++++++++--- imapserver/unselect_test.go | 2 +- 9 files changed, 83 insertions(+), 26 deletions(-) diff --git a/imapclient/cmds.go b/imapclient/cmds.go index 1d9c7ea..76a42e2 100644 --- a/imapclient/cmds.go +++ b/imapclient/cmds.go @@ -206,10 +206,15 @@ func (c *Conn) Namespace() (untagged []Untagged, result Result, rerr error) { return c.Transactf("namespace") } -// Status requests information about a mailbox, such as number of messages, size, etc. -func (c *Conn) Status(mailbox string) (untagged []Untagged, result Result, rerr error) { +// Status requests information about a mailbox, such as number of messages, size, +// etc. At least one attribute required. +func (c *Conn) Status(mailbox string, attrs ...StatusAttr) (untagged []Untagged, result Result, rerr error) { defer c.recover(&rerr) - return c.Transactf("status %s", astring(mailbox)) + l := make([]string, len(attrs)) + for i, a := range attrs { + l[i] = string(a) + } + return c.Transactf("status %s (%s)", astring(mailbox), strings.Join(l, " ")) } // Append adds message to mailbox with flags and optional receive time. diff --git a/imapclient/parse.go b/imapclient/parse.go index e229c48..b9f5eb3 100644 --- a/imapclient/parse.go +++ b/imapclient/parse.go @@ -363,14 +363,14 @@ func (c *Conn) xuntagged() Untagged { mailbox := c.xastring() c.xspace() c.xtake("(") - attrs := map[string]int64{} + attrs := map[StatusAttr]int64{} for !c.take(')') { if len(attrs) > 0 { c.xspace() } s := c.xatom() c.xspace() - S := strings.ToUpper(s) + S := StatusAttr(strings.ToUpper(s)) var num int64 // ../rfc/9051:7059 switch S { diff --git a/imapclient/protocol.go b/imapclient/protocol.go index f29c6cd..4e03670 100644 --- a/imapclient/protocol.go +++ b/imapclient/protocol.go @@ -224,8 +224,25 @@ type UntaggedSearchModSeq struct { } type UntaggedStatus struct { Mailbox string - Attrs map[string]int64 // Upper case status attributes. ../rfc/9051:7059 + Attrs map[StatusAttr]int64 // Upper case status attributes. } + +// ../rfc/9051:7059 ../9208:712 +type StatusAttr string + +const ( + StatusMessages StatusAttr = "MESSAGES" + StatusUIDNext StatusAttr = "UIDNEXT" + StatusUIDValidity StatusAttr = "UIDVALIDITY" + StatusUnseen StatusAttr = "UNSEEN" + StatusDeleted StatusAttr = "DELETED" + StatusSize StatusAttr = "SIZE" + StatusRecent StatusAttr = "RECENT" + StatusAppendLimit StatusAttr = "APPENDLIMIT" + StatusHighestModSeq StatusAttr = "HIGHESTMODSEQ" + StatusDeletedStorage StatusAttr = "DELETED-STORAGE" +) + type UntaggedNamespace struct { Personal, Other, Shared []NamespaceDescr } diff --git a/imapserver/condstore_test.go b/imapserver/condstore_test.go index d07bebd..716e4c4 100644 --- a/imapserver/condstore_test.go +++ b/imapserver/condstore_test.go @@ -42,7 +42,7 @@ func testCondstoreQresync(t *testing.T, qresync bool) { // First some tests without any messages. tc.transactf("ok", "Status inbox (Highestmodseq)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"HIGHESTMODSEQ": 1}}) + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusHighestModSeq: 1}}) // No messages, no matches. tc.transactf("ok", "Uid Fetch 1:* (Flags) (Changedsince 12345)") @@ -160,10 +160,10 @@ func testCondstoreQresync(t *testing.T, qresync bool) { // Check highestmodseq for mailboxes. tc.transactf("ok", "Status inbox (highestmodseq)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"HIGHESTMODSEQ": clientModseq}}) + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusHighestModSeq: clientModseq}}) tc.transactf("ok", "Status otherbox (highestmodseq)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "otherbox", Attrs: map[string]int64{"HIGHESTMODSEQ": 3}}) + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "otherbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusHighestModSeq: 3}}) // Check highestmodseq when we select. tc.transactf("ok", "Examine otherbox") @@ -297,7 +297,7 @@ func testCondstoreQresync(t *testing.T, qresync bool) { // Again after expunge: status, select, conditional store/fetch/search tc.transactf("ok", "Status inbox (Highestmodseq Messages Unseen Deleted)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 4, "UNSEEN": 4, "DELETED": 0, "HIGHESTMODSEQ": clientModseq}}) + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 4, imapclient.StatusUnseen: 4, imapclient.StatusDeleted: 0, imapclient.StatusHighestModSeq: clientModseq}}) tc.transactf("ok", "Close") tc.transactf("ok", "Select inbox") diff --git a/imapserver/list_test.go b/imapserver/list_test.go index 83365e0..87f4731 100644 --- a/imapserver/list_test.go +++ b/imapserver/list_test.go @@ -90,15 +90,15 @@ func TestListExtended(t *testing.T) { } ustatus := func(name string) imapclient.UntaggedStatus { - attrs := map[string]int64{ - "MESSAGES": 0, - "UIDNEXT": 1, - "UIDVALIDITY": int64(uidval(name)), - "UNSEEN": 0, - "DELETED": 0, - "SIZE": 0, - "RECENT": 0, - "APPENDLIMIT": 0, + attrs := map[imapclient.StatusAttr]int64{ + imapclient.StatusMessages: 0, + imapclient.StatusUIDNext: 1, + imapclient.StatusUIDValidity: int64(uidval(name)), + imapclient.StatusUnseen: 0, + imapclient.StatusDeleted: 0, + imapclient.StatusSize: 0, + imapclient.StatusRecent: 0, + imapclient.StatusAppendLimit: 0, } return imapclient.UntaggedStatus{Mailbox: name, Attrs: attrs} } diff --git a/imapserver/quota_test.go b/imapserver/quota_test.go index e862758..fb2e7dc 100644 --- a/imapserver/quota_test.go +++ b/imapserver/quota_test.go @@ -32,7 +32,7 @@ func TestQuota1(t *testing.T) { // Check that we get a DELETED-STORAGE status attribute with value 0, also if // messages are marked deleted. We don't go through the trouble. tc.transactf("ok", "status inbox (DELETED-STORAGE)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"DELETED-STORAGE": 0}}) + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusDeletedStorage: 0}}) // tclimit does have a limit. tclimit := startArgs(t, false, false, true, true, "limit") @@ -50,5 +50,5 @@ func TestQuota1(t *testing.T) { tclimit.xuntagged(imapclient.UntaggedQuota{Root: "", Resources: []imapclient.QuotaResource{{Name: imapclient.QuotaResourceStorage, Usage: 0, Limit: 1}}}) tclimit.transactf("ok", "status inbox (DELETED-STORAGE)") - tclimit.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"DELETED-STORAGE": 0}}) + tclimit.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusDeletedStorage: 0}}) } diff --git a/imapserver/server_test.go b/imapserver/server_test.go index d687689..6fc290e 100644 --- a/imapserver/server_test.go +++ b/imapserver/server_test.go @@ -692,7 +692,7 @@ func DisabledTestReference(t *testing.T) { defer tc3.close() tc3.client.Login("mjl@mox.example", password0) tc3.transactf("ok", `list "" "inbox" return (status (messages))`) - tc3.xuntagged(imapclient.UntaggedList{Separator: '/', Mailbox: "Inbox"}, imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 0}}) + tc3.xuntagged(imapclient.UntaggedList{Separator: '/', Mailbox: "Inbox"}, imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0}}) tc2.transactf("ok", "fetch 1 rfc822.size") tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchRFC822Size(len(exampleMsg))}}) diff --git a/imapserver/status_test.go b/imapserver/status_test.go index fc84108..350bd76 100644 --- a/imapserver/status_test.go +++ b/imapserver/status_test.go @@ -20,15 +20,50 @@ func TestStatus(t *testing.T) { tc.transactf("bad", "status inbox (unknown)") // Unknown attribute. tc.transactf("ok", "status inbox (messages uidnext uidvalidity unseen deleted size recent appendlimit)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 0, "UIDVALIDITY": 1, "UIDNEXT": 1, "UNSEEN": 0, "DELETED": 0, "SIZE": 0, "RECENT": 0, "APPENDLIMIT": 0}}) + tc.xuntagged(imapclient.UntaggedStatus{ + Mailbox: "Inbox", + Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 0, + imapclient.StatusUIDValidity: 1, + imapclient.StatusUIDNext: 1, + imapclient.StatusUnseen: 0, + imapclient.StatusDeleted: 0, + imapclient.StatusSize: 0, + imapclient.StatusRecent: 0, + imapclient.StatusAppendLimit: 0, + }, + }) // Again, now with a message in the mailbox. tc.transactf("ok", "append inbox {4+}\r\ntest") tc.transactf("ok", "status inbox (messages uidnext uidvalidity unseen deleted size recent appendlimit)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 1, "UIDVALIDITY": 1, "UIDNEXT": 2, "UNSEEN": 1, "DELETED": 0, "SIZE": 4, "RECENT": 0, "APPENDLIMIT": 0}}) + + tc.xuntagged(imapclient.UntaggedStatus{ + Mailbox: "Inbox", + Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, + imapclient.StatusUIDValidity: 1, + imapclient.StatusUIDNext: 2, + imapclient.StatusUnseen: 1, + imapclient.StatusDeleted: 0, + imapclient.StatusSize: 4, + imapclient.StatusRecent: 0, + imapclient.StatusAppendLimit: 0, + }, + }) tc.client.Select("inbox") tc.client.StoreFlagsSet("1", true, `\Deleted`) tc.transactf("ok", "status inbox (messages uidnext uidvalidity unseen deleted size recent appendlimit)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 1, "UIDVALIDITY": 1, "UIDNEXT": 2, "UNSEEN": 1, "DELETED": 1, "SIZE": 4, "RECENT": 0, "APPENDLIMIT": 0}}) + tc.xuntagged(imapclient.UntaggedStatus{ + Mailbox: "Inbox", + Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1, + imapclient.StatusUIDValidity: 1, + imapclient.StatusUIDNext: 2, + imapclient.StatusUnseen: 1, + imapclient.StatusDeleted: 1, + imapclient.StatusSize: 4, + imapclient.StatusRecent: 0, + imapclient.StatusAppendLimit: 0, + }, + }) + } diff --git a/imapserver/unselect_test.go b/imapserver/unselect_test.go index 0e04d56..8cb10b9 100644 --- a/imapserver/unselect_test.go +++ b/imapserver/unselect_test.go @@ -22,5 +22,5 @@ func TestUnselect(t *testing.T) { tc.client.StoreFlagsAdd("1", true, `\Deleted`) tc.transactf("ok", "unselect") tc.transactf("ok", "status inbox (messages)") - tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[string]int64{"MESSAGES": 1}}) // Message not removed. + tc.xuntagged(imapclient.UntaggedStatus{Mailbox: "Inbox", Attrs: map[imapclient.StatusAttr]int64{imapclient.StatusMessages: 1}}) // Message not removed. }