imapclient: add a type Append for messages for the APPEND-command, and accept multiple for servers with MULTIAPPEND capability

and a few nits.
This commit is contained in:
Mechiel Lukkien
2025-02-25 23:24:37 +01:00
parent 88a68e9143
commit 1066eb4c9f
13 changed files with 83 additions and 38 deletions

View File

@ -342,7 +342,7 @@ func TestAuthenticateTLSClientCert(t *testing.T) {
tc.client.Enable("imap4rev2")
received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
tc.check(err, "parse time")
tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
tc.client.Select("inbox")
tc.close()

View File

@ -25,12 +25,12 @@ func TestCopy(t *testing.T) {
tc.transactf("bad", "copy 1 inbox ") // Leftover.
// Seqs 1,2 and UIDs 3,4.
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.StoreFlagsSet("1:2", true, `\Deleted`)
tc.client.Expunge()
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.transactf("no", "copy 1 nonexistent")
tc.xcode("TRYCREATE")

View File

@ -54,7 +54,7 @@ func TestDelete(t *testing.T) {
// Let's try again with a message present.
tc.client.Create("msgs", nil)
tc.client.Append("msgs", nil, nil, []byte(exampleMsg))
tc.client.Append("msgs", makeAppend(exampleMsg))
tc.transactf("ok", "delete msgs")
// Delete for inbox/* is allowed.

View File

@ -31,9 +31,9 @@ func TestExpunge(t *testing.T) {
tc.client.Unselect()
tc.client.Select("inbox")
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.transactf("ok", "expunge") // Still nothing to remove.
tc.xuntagged()
@ -51,9 +51,9 @@ func TestExpunge(t *testing.T) {
tc.xuntagged()
// Only UID 2 is still left. We'll add 3 more. Getting us to UIDs 2,4,5,6.
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.transactf("bad", "uid expunge") // Missing uid set.
tc.transactf("bad", "uid expunge 1 leftover") // Leftover data.

View File

@ -19,7 +19,7 @@ func TestFetch(t *testing.T) {
tc.client.Enable("imap4rev2")
received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
tc.check(err, "parse time")
tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
tc.client.Select("inbox")
uid1 := imapclient.FetchUID(1)
@ -288,7 +288,7 @@ func TestFetch(t *testing.T) {
Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-1"}}},
},
}
tc.client.Append("inbox", nil, &received, []byte(nestedMessage))
tc.client.Append("inbox", makeAppendTime(nestedMessage, received))
tc.transactf("ok", "fetch 2 bodystructure")
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})

View File

@ -31,12 +31,12 @@ func TestMove(t *testing.T) {
tc.transactf("bad", "move 1 inbox ") // Leftover.
// Seqs 1,2 and UIDs 3,4.
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.StoreFlagsSet("1:2", true, `\Deleted`)
tc.client.Expunge()
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Unselect()
tc.client.Examine("inbox")
@ -69,8 +69,8 @@ func TestMove(t *testing.T) {
tc3.xuntagged(imapclient.UntaggedExpunge(1), imapclient.UntaggedExpunge(1))
// UIDs 5,6
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc2.transactf("ok", "noop") // Drain.
tc3.transactf("ok", "noop") // Drain.

View File

@ -67,13 +67,13 @@ func TestSearch(t *testing.T) {
received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
saveDate := time.Now()
for i := 0; i < 5; i++ {
tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
}
tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
tc.client.Expunge()
received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
tc.client.Append("inbox", nil, &received, []byte(searchMsg))
tc.client.Append("inbox", makeAppendTime(searchMsg, received))
received = time.Date(2022, time.January, 1, 9, 0, 0, 0, time.UTC)
mostFlags := []string{
@ -90,7 +90,7 @@ func TestSearch(t *testing.T) {
`custom1`,
`Custom2`,
}
tc.client.Append("inbox", mostFlags, &received, []byte(searchMsg))
tc.client.Append("inbox", imapclient.Append{Flags: mostFlags, Received: &received, Size: int64(len(searchMsg)), Data: strings.NewReader(searchMsg)})
// We now have sequence numbers 1,2,3 and UIDs 5,6,7.

View File

@ -59,7 +59,7 @@ func testSelectExamine(t *testing.T, examine bool) {
tc.xcode(okcode)
// Append a message. It will be reported as UNSEEN.
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.transactf("ok", "%s inbox", cmd)
tc.xuntagged(uclosed, uflags, upermflags, urecent, uunseen, uexists1, uuidval1, uuidnext2, ulist)
tc.xcode(okcode)

View File

@ -2340,7 +2340,7 @@ func (c *conn) cmdLogin(tag, cmd string, p *parser) {
defer func() {
if account != nil {
err := account.Close()
c.log.Check(err, "close account")
c.xsanity(err, "close account")
}
}()
@ -2886,7 +2886,7 @@ func (c *conn) cmdDelete(tag, cmd string, p *parser) {
for _, mID := range removeMessageIDs {
p := c.account.MessagePath(mID)
err := os.Remove(p)
c.log.Check(err, "removing message file for mailbox delete", slog.String("path", p))
c.xsanity(err, "removing message file %q for mailbox delete", p)
}
c.ok(tag, cmd)
@ -3562,7 +3562,7 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
for _, a := range appends {
c.uidAppend(a.m.UID)
}
// todo spec: with condstore/qresync, is there a mechanism to the client know the modseq for the appended uid? in theory an untagged fetch with the modseq after the OK APPENDUID could make sense, but this probably isn't allowed.
// todo spec: with condstore/qresync, is there a mechanism to let the client know the modseq for the appended uid? in theory an untagged fetch with the modseq after the OK APPENDUID could make sense, but this probably isn't allowed.
c.bwritelinef("* %d EXISTS", len(c.uids))
}
@ -4382,7 +4382,7 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) {
}
}
if c.enabled[capQresync] {
if qresync {
// ../rfc/9051:6744 ../rfc/7162:1334
c.writeresultf("%s OK [HIGHESTMODSEQ %d] move", tag, modseq.Client())
} else {

View File

@ -341,6 +341,14 @@ func xparseUIDRange(s string) imapclient.NumRange {
return nr
}
func makeAppend(msg string) imapclient.Append {
return imapclient.Append{Size: int64(len(msg)), Data: strings.NewReader(msg)}
}
func makeAppendTime(msg string, tm time.Time) imapclient.Append {
return imapclient.Append{Received: &tm, Size: int64(len(msg)), Data: strings.NewReader(msg)}
}
var connCounter int64
func start(t *testing.T) *testconn {
@ -732,8 +740,8 @@ func TestSequence(t *testing.T) {
tc.transactf("ok", "uid fetch 1 all") // non-existing messages are OK for uids.
tc.transactf("ok", "uid fetch * all") // * is like uidnext, a non-existing message.
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.transactf("ok", "fetch 2:1,1 uid") // We reorder 2:1 to 1:2, but we don't deduplicate numbers.
tc.xuntagged(
imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(1)}},
@ -753,7 +761,7 @@ func DisabledTestReference(t *testing.T) {
defer tc.close()
tc.client.Login("mjl@mox.example", password0)
tc.client.Select("inbox")
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc2 := startNoSwitchboard(t)
defer tc2.close()

View File

@ -14,7 +14,7 @@ func TestStore(t *testing.T) {
tc.client.Login("mjl@mox.example", password0)
tc.client.Enable("imap4rev2")
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.Select("inbox")
uid1 := imapclient.FetchUID(1)

View File

@ -18,7 +18,7 @@ func TestUnselect(t *testing.T) {
tc.transactf("no", "fetch 1 all") // Invalid when not selected.
tc.client.Select("inbox")
tc.client.Append("inbox", nil, nil, []byte(exampleMsg))
tc.client.Append("inbox", makeAppend(exampleMsg))
tc.client.StoreFlagsAdd("1", true, `\Deleted`)
tc.transactf("ok", "unselect")
tc.transactf("ok", "status inbox (messages)")