mirror of
https://github.com/mjl-/mox.git
synced 2025-07-13 07:34:37 +03:00
implement IMAP CREATE-SPECIAL-USE extension for the mailbox create command, part of rfc 6154
we already supported special-use flags. settable through the webmail interface, and new accounts already got standard mailboxes with special-use flags predefined. but now the IMAP "CREATE" command implements creating mailboxes with special-use flags.
This commit is contained in:
@ -96,7 +96,7 @@ func testCondstoreQresync(t *testing.T, qresync bool) {
|
||||
err = tc.account.DB.Update(ctxbg, &store.SyncState{ID: 1, LastModSeq: 1})
|
||||
tcheck(t, err, "resetting modseq state")
|
||||
|
||||
tc.client.Create("otherbox")
|
||||
tc.client.Create("otherbox", nil)
|
||||
|
||||
// tc2 is a client without condstore, so no modseq responses.
|
||||
tc2 := startNoSwitchboard(t)
|
||||
|
@ -72,6 +72,14 @@ func TestCreate(t *testing.T) {
|
||||
tc.transactf("no", `create "#"`) // Leading hash not allowed.
|
||||
tc.transactf("ok", `create "test#"`)
|
||||
|
||||
// Create with flags.
|
||||
tc.transactf("no", `create "newwithflags" (use (\unknown))`)
|
||||
tc.transactf("no", `create "newwithflags" (use (\all))`)
|
||||
tc.transactf("ok", `create "newwithflags" (use (\archive))`)
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged()
|
||||
tc.transactf("ok", `create "newwithflags2" (use (\archive) use (\drafts \sent))`)
|
||||
|
||||
// UTF-7 checks are only for IMAP4 before rev2 and without UTF8=ACCEPT.
|
||||
tc.transactf("ok", `create "&"`) // Interpreted as UTF-8, no UTF-7.
|
||||
tc2.transactf("bad", `create "&"`) // Bad UTF-7.
|
||||
|
@ -28,7 +28,7 @@ func TestDelete(t *testing.T) {
|
||||
tc.client.Subscribe("x")
|
||||
tc.transactf("no", "delete x") // Subscription does not mean there is a mailbox that can be deleted.
|
||||
|
||||
tc.client.Create("a/b")
|
||||
tc.client.Create("a/b", nil)
|
||||
tc2.transactf("ok", "noop") // Drain changes.
|
||||
tc3.transactf("ok", "noop")
|
||||
|
||||
@ -53,12 +53,12 @@ func TestDelete(t *testing.T) {
|
||||
)
|
||||
|
||||
// Let's try again with a message present.
|
||||
tc.client.Create("msgs")
|
||||
tc.client.Create("msgs", nil)
|
||||
tc.client.Append("msgs", nil, nil, []byte(exampleMsg))
|
||||
tc.transactf("ok", "delete msgs")
|
||||
|
||||
// Delete for inbox/* is allowed.
|
||||
tc.client.Create("inbox/a")
|
||||
tc.client.Create("inbox/a", nil)
|
||||
tc.transactf("ok", "delete inbox/a")
|
||||
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func TestListBasic(t *testing.T) {
|
||||
tc.last(tc.client.List("A*"))
|
||||
tc.xuntagged(ulist("Archive", `\Archive`))
|
||||
|
||||
tc.client.Create("Inbox/todo")
|
||||
tc.client.Create("Inbox/todo", nil)
|
||||
|
||||
tc.last(tc.client.List("Inbox*"))
|
||||
tc.xuntagged(ulist("Inbox"), ulist("Inbox/todo"))
|
||||
@ -146,7 +146,7 @@ func TestListExtended(t *testing.T) {
|
||||
tc.last(tc.client.ListFull(false, "A*", "Junk"))
|
||||
tc.xuntagged(xlist("Archive", Farchive), ustatus("Archive"), xlist("Junk", Fjunk), ustatus("Junk"))
|
||||
|
||||
tc.client.Create("Inbox/todo")
|
||||
tc.client.Create("Inbox/todo", nil)
|
||||
|
||||
tc.last(tc.client.ListFull(false, "Inbox*"))
|
||||
tc.xuntagged(ulist("Inbox", Fhaschildren, Fsubscribed), ustatus("Inbox"), xlist("Inbox/todo"), ustatus("Inbox/todo"))
|
||||
@ -204,7 +204,7 @@ func TestListExtended(t *testing.T) {
|
||||
tc.transactf("ok", `list (remote) "inbox" "a"`)
|
||||
tc.xuntagged()
|
||||
|
||||
tc.client.Create("inbox/a")
|
||||
tc.client.Create("inbox/a", nil)
|
||||
tc.transactf("ok", `list (remote) "inbox" "a"`)
|
||||
tc.xuntagged(ulist("Inbox/a"))
|
||||
|
||||
|
@ -26,9 +26,9 @@ func TestRename(t *testing.T) {
|
||||
tc.transactf("no", `rename "Sent" "Trash"`) // Already exists.
|
||||
tc.xcode("ALREADYEXISTS")
|
||||
|
||||
tc.client.Create("x")
|
||||
tc.client.Create("x", nil)
|
||||
tc.client.Subscribe("sub")
|
||||
tc.client.Create("a/b/c")
|
||||
tc.client.Create("a/b/c", nil)
|
||||
tc.client.Subscribe("x/y/c") // For later rename, but not affected by rename of x.
|
||||
tc2.transactf("ok", "noop") // Drain.
|
||||
|
||||
@ -58,7 +58,7 @@ func TestRename(t *testing.T) {
|
||||
tc2.transactf("ok", "noop")
|
||||
tc2.xuntagged(imapclient.UntaggedList{Flags: []string{`\Subscribed`}, Separator: '/', Mailbox: "x"}, imapclient.UntaggedList{Separator: '/', Mailbox: "x/y", OldName: "a/b"}, imapclient.UntaggedList{Flags: []string{`\Subscribed`}, Separator: '/', Mailbox: "x/y/c", OldName: "a/b/c"})
|
||||
|
||||
tc.client.Create("k/l")
|
||||
tc.client.Create("k/l", nil)
|
||||
tc.transactf("ok", "rename k/l k/l/m") // With "l" renamed, a new "k" will be created.
|
||||
tc.transactf("ok", `list "" "k*" return (subscribed)`)
|
||||
tc.xuntagged(imapclient.UntaggedList{Flags: []string{`\Subscribed`}, Separator: '/', Mailbox: "k"}, imapclient.UntaggedList{Flags: []string{`\Subscribed`}, Separator: '/', Mailbox: "k/l"}, imapclient.UntaggedList{Separator: '/', Mailbox: "k/l/m"})
|
||||
|
@ -28,7 +28,7 @@ non-ASCII UTF-8. Until that's enabled, we do use UTF-7 for mailbox names. See
|
||||
- todo: do not return binary data for a fetch body. at least not for imap4rev1. we should be encoding it as base64?
|
||||
- todo: on expunge we currently remove the message even if other sessions still have a reference to the uid. if they try to query the uid, they'll get an error. we could be nicer and only actually remove the message when the last reference has gone. we could add a new flag to store.Message marking the message as expunged, not give new session access to such messages, and make store remove them at startup, and clean them when the last session referencing the session goes. however, it will get much more complicated. renaming messages would need special handling. and should we do the same for removed mailboxes?
|
||||
- todo: try to recover from syntax errors when the last command line ends with a }, i.e. a literal. we currently abort the entire connection. we may want to read some amount of literal data and continue with a next command.
|
||||
- todo future: more extensions: OBJECTID, MULTISEARCH, REPLACE, NOTIFY, CATENATE, MULTIAPPEND, SORT, THREAD, CREATE-SPECIAL-USE.
|
||||
- todo future: more extensions: OBJECTID, MULTISEARCH, REPLACE, NOTIFY, CATENATE, MULTIAPPEND, SORT, THREAD.
|
||||
*/
|
||||
|
||||
import (
|
||||
@ -146,7 +146,7 @@ var authFailDelay = time.Second // After authentication failure.
|
||||
// MOVE: ../rfc/6851
|
||||
// UTF8=ONLY: ../rfc/6855
|
||||
// LIST-EXTENDED: ../rfc/5258
|
||||
// SPECIAL-USE: ../rfc/6154
|
||||
// SPECIAL-USE CREATE-SPECIAL-USE: ../rfc/6154
|
||||
// LIST-STATUS: ../rfc/5819
|
||||
// ID: ../rfc/2971
|
||||
// AUTH=EXTERNAL: ../rfc/4422:1575
|
||||
@ -165,7 +165,7 @@ var authFailDelay = time.Second // After authentication failure.
|
||||
// TLS. The client should not be selecting PLUS variants on non-TLS connections,
|
||||
// instead opting to do the bare SCRAM variant without indicating the server claims
|
||||
// to support the PLUS variant (skipping the server downgrade detection check).
|
||||
const serverCapabilities = "IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ACCEPT LIST-EXTENDED SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256-PLUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1-PLUS AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC STATUS=SIZE QUOTA QUOTA=RES-STORAGE METADATA SAVEDATE"
|
||||
const serverCapabilities = "IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ACCEPT LIST-EXTENDED SPECIAL-USE CREATE-SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256-PLUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1-PLUS AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC STATUS=SIZE QUOTA QUOTA=RES-STORAGE METADATA SAVEDATE"
|
||||
|
||||
type conn struct {
|
||||
cid int64
|
||||
@ -2710,13 +2710,50 @@ func (c *conn) cmdCreate(tag, cmd string, p *parser) {
|
||||
// Request syntax: ../rfc/9051:6484 ../rfc/6154:468 ../rfc/4466:500 ../rfc/3501:4687
|
||||
p.xspace()
|
||||
name := p.xmailbox()
|
||||
// todo: support CREATE-SPECIAL-USE ../rfc/6154:296
|
||||
// Optional parameters. ../rfc/4466:501 ../rfc/4466:511
|
||||
var useAttrs []string // Special-use attributes without leading \.
|
||||
if p.space() {
|
||||
p.xtake("(")
|
||||
// We only support "USE", and there don't appear to be more types of parameters.
|
||||
for {
|
||||
p.xtake("USE (")
|
||||
for {
|
||||
p.xtake(`\`)
|
||||
useAttrs = append(useAttrs, p.xatom())
|
||||
if !p.space() {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.xtake(")")
|
||||
if !p.space() {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.xtake(")")
|
||||
}
|
||||
p.xempty()
|
||||
|
||||
origName := name
|
||||
name = strings.TrimRight(name, "/") // ../rfc/9051:1930
|
||||
name = xcheckmailboxname(name, false)
|
||||
|
||||
var specialUse store.SpecialUse
|
||||
specialUseBools := map[string]*bool{
|
||||
"archive": &specialUse.Archive,
|
||||
"drafts": &specialUse.Draft,
|
||||
"junk": &specialUse.Junk,
|
||||
"sent": &specialUse.Sent,
|
||||
"trash": &specialUse.Trash,
|
||||
}
|
||||
for _, s := range useAttrs {
|
||||
p, ok := specialUseBools[strings.ToLower(s)]
|
||||
if !ok {
|
||||
// ../rfc/6154:287
|
||||
xusercodeErrorf("USEATTR", `cannot create mailbox with special-use attribute \%s`, s)
|
||||
}
|
||||
*p = true
|
||||
}
|
||||
|
||||
var changes []store.Change
|
||||
var created []string // Created mailbox names.
|
||||
|
||||
@ -2724,7 +2761,7 @@ func (c *conn) cmdCreate(tag, cmd string, p *parser) {
|
||||
c.xdbwrite(func(tx *bstore.Tx) {
|
||||
var exists bool
|
||||
var err error
|
||||
changes, created, exists, err = c.account.MailboxCreate(tx, name)
|
||||
changes, created, exists, err = c.account.MailboxCreate(tx, name, specialUse)
|
||||
if exists {
|
||||
// ../rfc/9051:1914
|
||||
xuserErrorf("mailbox already exists")
|
||||
|
@ -513,7 +513,7 @@ func TestLiterals(t *testing.T) {
|
||||
defer tc.close()
|
||||
|
||||
tc.client.Login("mjl@mox.example", password0)
|
||||
tc.client.Create("tmpbox")
|
||||
tc.client.Create("tmpbox", nil)
|
||||
|
||||
tc.transactf("ok", "rename {6+}\r\ntmpbox {7+}\r\nntmpbox")
|
||||
|
||||
@ -642,7 +642,7 @@ func TestMailboxDeleted(t *testing.T) {
|
||||
tc.client.Login("mjl@mox.example", password0)
|
||||
tc2.client.Login("mjl@mox.example", password0)
|
||||
|
||||
tc.client.Create("testbox")
|
||||
tc.client.Create("testbox", nil)
|
||||
tc2.client.Select("testbox")
|
||||
tc.client.Delete("testbox")
|
||||
|
||||
@ -663,7 +663,7 @@ func TestMailboxDeleted(t *testing.T) {
|
||||
|
||||
tc2.transactf("ok", "unselect")
|
||||
|
||||
tc.client.Create("testbox")
|
||||
tc.client.Create("testbox", nil)
|
||||
tc2.client.Select("testbox")
|
||||
tc.client.Delete("testbox")
|
||||
tc2.transactf("ok", "close")
|
||||
|
Reference in New Issue
Block a user