mirror of
https://github.com/mjl-/mox.git
synced 2025-07-19 03:26:37 +03:00
imapserver: implement UIDONLY extension, RFC 9586
Once clients enable this extension, commands can no longer refer to "message sequence numbers" (MSNs), but can only refer to messages with UIDs. This means both sides no longer have to carefully keep their sequence numbers in sync (error-prone), and don't have to keep track of a mapping of sequence numbers to UIDs (saves resources). With UIDONLY enabled, all FETCH responses are replaced with UIDFETCH response.
This commit is contained in:
@ -12,10 +12,18 @@ import (
|
||||
)
|
||||
|
||||
func TestFetch(t *testing.T) {
|
||||
tc := start(t)
|
||||
testFetch(t, false)
|
||||
}
|
||||
|
||||
func TestFetchUIDOnly(t *testing.T) {
|
||||
testFetch(t, true)
|
||||
}
|
||||
|
||||
func testFetch(t *testing.T, uidonly bool) {
|
||||
tc := start(t, uidonly)
|
||||
defer tc.close()
|
||||
|
||||
tc.client.Login("mjl@mox.example", password0)
|
||||
tc.login("mjl@mox.example", password0)
|
||||
tc.client.Enable("imap4rev2")
|
||||
received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
|
||||
tc.check(err, "parse time")
|
||||
@ -82,156 +90,159 @@ func TestFetch(t *testing.T) {
|
||||
|
||||
flagsSeen := imapclient.FetchFlags{`\Seen`}
|
||||
|
||||
tc.transactf("ok", "fetch 1 all")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, noflags}})
|
||||
if !uidonly {
|
||||
tc.transactf("ok", "fetch 1 all")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, date1, rfcsize1, env1, noflags))
|
||||
|
||||
tc.transactf("ok", "fetch 1 fast")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, noflags}})
|
||||
tc.transactf("ok", "fetch 1 fast")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, date1, rfcsize1, noflags))
|
||||
|
||||
tc.transactf("ok", "fetch 1 full")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, bodyxstructure1, noflags}})
|
||||
tc.transactf("ok", "fetch 1 full")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, date1, rfcsize1, env1, bodyxstructure1, noflags))
|
||||
|
||||
tc.transactf("ok", "fetch 1 flags")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, noflags}})
|
||||
tc.transactf("ok", "fetch 1 flags")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, noflags))
|
||||
|
||||
tc.transactf("ok", "fetch 1 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}})
|
||||
tc.transactf("ok", "fetch 1 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1))
|
||||
|
||||
// Should be returned unmodified, because there is no content-transfer-encoding.
|
||||
tc.transactf("ok", "fetch 1 binary[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
// Should be returned unmodified, because there is no content-transfer-encoding.
|
||||
tc.transactf("ok", "fetch 1 binary[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binary1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.transactf("ok", "fetch 1 binary[1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypart1}}) // Seen flag not changed.
|
||||
tc.transactf("ok", "fetch 1 binary[1]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binarypart1)) // Seen flag not changed.
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "uid fetch 1 binary[]<1.1>")
|
||||
tc.xuntagged(
|
||||
imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartial1, noflags}},
|
||||
imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}}, // For UID FETCH, we get the flags during the command.
|
||||
)
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "uid fetch 1 binary[]<1.1>")
|
||||
tc.xuntagged(
|
||||
tc.untaggedFetch(1, 1, binarypartial1, noflags),
|
||||
tc.untaggedFetch(1, 1, flagsSeen), // For UID FETCH, we get the flags during the command.
|
||||
)
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[1]<1.1>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartpartial1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[1]<1.1>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binarypartpartial1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[]<10000.10001>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binaryend1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[]<10000.10001>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binaryend1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[1]<10000.10001>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartend1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary[1]<10000.10001>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binarypartend1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 binary.size[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysize1}})
|
||||
tc.transactf("ok", "fetch 1 binary.size[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binarysize1))
|
||||
|
||||
tc.transactf("ok", "fetch 1 binary.size[1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysizepart1}})
|
||||
tc.transactf("ok", "fetch 1 binary.size[1]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binarysizepart1))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.transactf("ok", "fetch 1 body[]<1.2>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyoff1}}) // Already seen.
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged() // Already seen.
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
tc.transactf("ok", "fetch 1 body[]<1.2>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodyoff1)) // Already seen.
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged() // Already seen.
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodypart1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodypart1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]<1.2>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1off1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]<1.2>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1off1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]<100000.100000>")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyend1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[1]<100000.100000>")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodyend1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[header]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyheader1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[header]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodyheader1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[text]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodytext1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body[text]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodytext1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
// equivalent to body.peek[header], ../rfc/3501:3183
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 rfc822.header")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfcheader1}})
|
||||
// equivalent to body.peek[header], ../rfc/3501:3183
|
||||
tc.transactf("ok", "fetch 1 rfc822.header")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, rfcheader1))
|
||||
|
||||
// equivalent to body[text], ../rfc/3501:3199
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 rfc822.text")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
// equivalent to body[text], ../rfc/3501:3199
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 rfc822.text")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, rfctext1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
// equivalent to body[], ../rfc/3501:3179
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 rfc822")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
// equivalent to body[], ../rfc/3501:3179
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 rfc822")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, rfc1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
|
||||
// With PEEK, we should not get the \Seen flag.
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body.peek[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
|
||||
// With PEEK, we should not get the \Seen flag.
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1 body.peek[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1))
|
||||
|
||||
tc.transactf("ok", "fetch 1 binary.peek[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
|
||||
tc.transactf("ok", "fetch 1 binary.peek[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binary1))
|
||||
|
||||
// HEADER.FIELDS and .NOT
|
||||
tc.transactf("ok", "fetch 1 body.peek[header.fields (date)]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, dateheader1}})
|
||||
tc.transactf("ok", "fetch 1 body.peek[header.fields.not (date)]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodateheader1}})
|
||||
// For non-multipart messages, 1 means the whole message, but since it's not of
|
||||
// type message/{rfc822,global} (a message), you can't get the message headers.
|
||||
// ../rfc/9051:4481
|
||||
tc.transactf("no", "fetch 1 body.peek[1.header]")
|
||||
// HEADER.FIELDS and .NOT
|
||||
tc.transactf("ok", "fetch 1 body.peek[header.fields (date)]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, dateheader1))
|
||||
tc.transactf("ok", "fetch 1 body.peek[header.fields.not (date)]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, nodateheader1))
|
||||
// For non-multipart messages, 1 means the whole message, but since it's not of
|
||||
// type message/{rfc822,global} (a message), you can't get the message headers.
|
||||
// ../rfc/9051:4481
|
||||
tc.transactf("no", "fetch 1 body.peek[1.header]")
|
||||
|
||||
// MIME, part 1 for non-multipart messages is the message itself. ../rfc/9051:4481
|
||||
tc.transactf("ok", "fetch 1 body.peek[1.mime]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, mime1}})
|
||||
// MIME, part 1 for non-multipart messages is the message itself. ../rfc/9051:4481
|
||||
tc.transactf("ok", "fetch 1 body.peek[1.mime]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, mime1))
|
||||
|
||||
// Missing sequence number. ../rfc/9051:7018
|
||||
tc.transactf("bad", "fetch 2 body[]")
|
||||
// Missing sequence number. ../rfc/9051:7018
|
||||
tc.transactf("bad", "fetch 2 body[]")
|
||||
|
||||
tc.transactf("ok", "fetch 1:1 body[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, noflags}})
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, flagsSeen}})
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.transactf("ok", "fetch 1:1 body[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1, noflags))
|
||||
tc.transactf("ok", "noop")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, flagsSeen))
|
||||
} else {
|
||||
tc.client.UIDStoreFlagsAdd("1", true, `\Seen`)
|
||||
tc.transactf("ok", "noop")
|
||||
}
|
||||
|
||||
// UID fetch
|
||||
tc.transactf("ok", "uid fetch 1 body[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1))
|
||||
|
||||
// UID fetch
|
||||
tc.transactf("ok", "uid fetch 2 body[]")
|
||||
tc.xuntagged()
|
||||
|
||||
@ -254,9 +265,9 @@ func TestFetch(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
tc.check(err, "get savedate")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchSaveDate{SaveDate: &saveDate}}})
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchSaveDate{SaveDate: &saveDate}))
|
||||
|
||||
// Test some invalid syntax.
|
||||
// Test some invalid syntax. Also invalid for uidonly.
|
||||
tc.transactf("bad", "fetch")
|
||||
tc.transactf("bad", "fetch ")
|
||||
tc.transactf("bad", "fetch ")
|
||||
@ -279,11 +290,12 @@ func TestFetch(t *testing.T) {
|
||||
tc.transactf("bad", "fetch 1 body[header.fields.not ()]") // List must be non-empty.
|
||||
tc.transactf("bad", "fetch 1 body[mime]") // MIME must be prefixed with a number. ../rfc/9051:4497
|
||||
|
||||
tc.transactf("no", "fetch 1 body[2]") // No such part.
|
||||
if !uidonly {
|
||||
tc.transactf("no", "fetch 1 body[2]") // No such part.
|
||||
}
|
||||
|
||||
// Add more complex message.
|
||||
|
||||
uid2 := imapclient.FetchUID(2)
|
||||
bodystructure2 := imapclient.FetchBodystructure{
|
||||
RespAttr: "BODYSTRUCTURE",
|
||||
Body: imapclient.BodyTypeMpart{
|
||||
@ -325,41 +337,42 @@ func TestFetch(t *testing.T) {
|
||||
},
|
||||
}
|
||||
tc.client.Append("inbox", makeAppendTime(nestedMessage, received))
|
||||
tc.transactf("ok", "fetch 2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "uid fetch 2 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, bodystructure2))
|
||||
|
||||
// Multiple responses.
|
||||
tc.transactf("ok", "fetch 1:2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "fetch 1,2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "fetch 2:1 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "fetch 1:* bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "fetch *:1 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.transactf("ok", "fetch *:2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
|
||||
tc.transactf("ok", "fetch * bodystructure") // Highest msgseq.
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
if !uidonly {
|
||||
tc.transactf("ok", "fetch 1:2 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch 1,2 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch 2:1 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch 1:* bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch *:1 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch *:2 bodystructure")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, bodystructure2))
|
||||
tc.transactf("ok", "fetch * bodystructure") // Highest msgseq.
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, bodystructure2))
|
||||
}
|
||||
|
||||
tc.transactf("ok", "uid fetch 1:* bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
|
||||
tc.transactf("ok", "uid fetch 1:2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
|
||||
tc.transactf("ok", "uid fetch 1,2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, bodystructure1), tc.untaggedFetch(2, 2, bodystructure2))
|
||||
|
||||
tc.transactf("ok", "uid fetch 2:2 bodystructure")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, bodystructure2))
|
||||
|
||||
// todo: read the bodies/headers of the parts, and of the nested message.
|
||||
tc.transactf("ok", "fetch 2 body.peek[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[]", Body: nestedMessage}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[]", Body: nestedMessage}))
|
||||
|
||||
part1 := tocrlf(` ... Some text appears here ...
|
||||
|
||||
@ -369,22 +382,22 @@ func TestFetch(t *testing.T) {
|
||||
It could have been done with explicit typing as in the
|
||||
next part.]
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: part1}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[1]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: part1}))
|
||||
|
||||
tc.transactf("no", "fetch 2 binary.peek[3]") // Only allowed on leaf parts, not multiparts.
|
||||
tc.transactf("no", "fetch 2 binary.peek[5]") // Only allowed on leaf parts, not messages.
|
||||
tc.transactf("no", "uid fetch 2 binary.peek[3]") // Only allowed on leaf parts, not multiparts.
|
||||
tc.transactf("no", "uid fetch 2 binary.peek[5]") // Only allowed on leaf parts, not messages.
|
||||
|
||||
part31 := "aGVsbG8NCndvcmxkDQo=\r\n"
|
||||
part31dec := "hello\r\nworld\r\n"
|
||||
tc.transactf("ok", "fetch 2 binary.size[3.1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[3.1]", Parts: []uint32{3, 1}, Size: int64(len(part31dec))}}})
|
||||
tc.transactf("ok", "uid fetch 2 binary.size[3.1]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[3.1]", Parts: []uint32{3, 1}, Size: int64(len(part31dec))}))
|
||||
|
||||
tc.transactf("ok", "fetch 2 body.peek[3.1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3.1]", Section: "3.1", Body: part31}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[3.1]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[3.1]", Section: "3.1", Body: part31}))
|
||||
|
||||
tc.transactf("ok", "fetch 2 binary.peek[3.1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBinary{RespAttr: "BINARY[3.1]", Parts: []uint32{3, 1}, Data: part31dec}}})
|
||||
tc.transactf("ok", "uid fetch 2 binary.peek[3.1]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBinary{RespAttr: "BINARY[3.1]", Parts: []uint32{3, 1}, Data: part31dec}))
|
||||
|
||||
part3 := tocrlf(`--unique-boundary-2
|
||||
Content-Type: audio/basic
|
||||
@ -401,12 +414,12 @@ Content-Disposition: inline; filename=image.jpg
|
||||
--unique-boundary-2--
|
||||
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[3]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3]", Section: "3", Body: part3}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[3]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[3]", Section: "3", Body: part3}))
|
||||
|
||||
part2mime := "Content-type: text/plain; charset=US-ASCII\r\n"
|
||||
tc.transactf("ok", "fetch 2 body.peek[2.mime]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[2.MIME]", Section: "2.MIME", Body: part2mime}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[2.mime]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[2.MIME]", Section: "2.MIME", Body: part2mime}))
|
||||
|
||||
part5 := tocrlf(`From: info@mox.example
|
||||
To: mox <info@mox.example>
|
||||
@ -416,8 +429,8 @@ Content-Transfer-Encoding: Quoted-printable
|
||||
|
||||
... Additional text in ISO-8859-1 goes here ...
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[5]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5]", Section: "5", Body: part5}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[5]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[5]", Section: "5", Body: part5}))
|
||||
|
||||
part5header := tocrlf(`From: info@mox.example
|
||||
To: mox <info@mox.example>
|
||||
@ -426,42 +439,42 @@ Content-Type: Text/plain; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: Quoted-printable
|
||||
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.header]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.HEADER]", Section: "5.HEADER", Body: part5header}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[5.header]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[5.HEADER]", Section: "5.HEADER", Body: part5header}))
|
||||
|
||||
part5mime := tocrlf(`Content-Type: message/rfc822
|
||||
Content-MD5: MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
|
||||
Content-Language: en,de
|
||||
Content-Location: http://localhost
|
||||
`)
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.mime]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.MIME]", Section: "5.MIME", Body: part5mime}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[5.mime]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[5.MIME]", Section: "5.MIME", Body: part5mime}))
|
||||
|
||||
part5text := " ... Additional text in ISO-8859-1 goes here ...\r\n"
|
||||
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.text]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.TEXT]", Section: "5.TEXT", Body: part5text}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[5.text]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[5.TEXT]", Section: "5.TEXT", Body: part5text}))
|
||||
|
||||
part5body := " ... Additional text in ISO-8859-1 goes here ...\r\n"
|
||||
tc.transactf("ok", "fetch 2 body.peek[5.1]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5body}}})
|
||||
tc.transactf("ok", "uid fetch 2 body.peek[5.1]")
|
||||
tc.xuntagged(tc.untaggedFetch(2, 2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5body}))
|
||||
|
||||
// 5.1 is the part that is the sub message, but not as message/rfc822, but as part,
|
||||
// so we cannot request a header.
|
||||
tc.transactf("no", "fetch 2 body.peek[5.1.header]")
|
||||
tc.transactf("no", "uid fetch 2 body.peek[5.1.header]")
|
||||
|
||||
// In case of EXAMINE instead of SELECT, we should not be seeing any changed \Seen flags for non-peek commands.
|
||||
tc.client.StoreFlagsClear("1", true, `\Seen`)
|
||||
tc.client.UIDStoreFlagsClear("1", true, `\Seen`)
|
||||
tc.client.Unselect()
|
||||
tc.client.Examine("inbox")
|
||||
|
||||
// Preview
|
||||
preview := "Hello Joe, do you think we can meet at 3:30 tomorrow?"
|
||||
tc.transactf("ok", "fetch 1 preview")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
|
||||
tc.transactf("ok", "uid fetch 1 preview")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchPreview{Preview: &preview}))
|
||||
|
||||
tc.transactf("ok", "fetch 1 preview (lazy)")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
|
||||
tc.transactf("ok", "uid fetch 1 preview (lazy)")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchPreview{Preview: &preview}))
|
||||
|
||||
// On-demand preview and saving on first request.
|
||||
err = tc.account.DB.Write(ctxbg, func(tx *bstore.Tx) error {
|
||||
@ -478,8 +491,8 @@ Content-Location: http://localhost
|
||||
})
|
||||
tcheck(t, err, "remove preview from database")
|
||||
|
||||
tc.transactf("ok", "fetch 1 preview")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchPreview{Preview: &preview}}})
|
||||
tc.transactf("ok", "uid fetch 1 preview")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, imapclient.FetchPreview{Preview: &preview}))
|
||||
m := store.Message{ID: 1}
|
||||
err = tc.account.DB.Get(ctxbg, &m)
|
||||
tcheck(t, err, "get message")
|
||||
@ -489,29 +502,38 @@ Content-Location: http://localhost
|
||||
t.Fatalf("got preview %q, expected %q", *m.Preview, preview+"\n")
|
||||
}
|
||||
|
||||
tc.transactf("bad", "fetch 1 preview (bogus)")
|
||||
tc.transactf("bad", "uid fetch 1 preview (bogus)")
|
||||
|
||||
// Start a second session. Use it to remove the message. First session should still
|
||||
// be able to access the messages.
|
||||
tc2 := startNoSwitchboard(t)
|
||||
tc2 := startNoSwitchboard(t, uidonly)
|
||||
defer tc2.closeNoWait()
|
||||
tc2.client.Login("mjl@mox.example", password0)
|
||||
tc2.login("mjl@mox.example", password0)
|
||||
tc2.client.Select("inbox")
|
||||
tc2.client.StoreFlagsSet("1", true, `\Deleted`)
|
||||
tc2.client.UIDStoreFlagsSet("1", true, `\Deleted`)
|
||||
tc2.client.Expunge()
|
||||
tc2.client.Logout()
|
||||
|
||||
tc.transactf("ok", "fetch 1 binary[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
|
||||
if uidonly {
|
||||
tc.transactf("ok", "uid fetch 1 binary[]")
|
||||
tc.xuntagged(
|
||||
tc.untaggedFetch(1, 1, imapclient.FetchFlags{`\Deleted`}),
|
||||
imapclient.UntaggedVanished{UIDs: xparseNumSet("1")},
|
||||
)
|
||||
// Message no longer available in session.
|
||||
} else {
|
||||
tc.transactf("ok", "fetch 1 binary[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, binary1))
|
||||
|
||||
tc.transactf("ok", "fetch 1 body[]")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
|
||||
tc.transactf("ok", "fetch 1 body[]")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, body1))
|
||||
|
||||
tc.transactf("ok", "fetch 1 rfc822.text")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1}})
|
||||
tc.transactf("ok", "fetch 1 rfc822.text")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, rfctext1))
|
||||
|
||||
tc.transactf("ok", "fetch 1 rfc822")
|
||||
tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}})
|
||||
tc.transactf("ok", "fetch 1 rfc822")
|
||||
tc.xuntagged(tc.untaggedFetch(1, 1, rfc1))
|
||||
}
|
||||
|
||||
tc.client.Logout()
|
||||
}
|
||||
|
Reference in New Issue
Block a user