8 "github.com/mjl-/bstore"
10 "github.com/mjl-/mox/imapclient"
11 "github.com/mjl-/mox/store"
14func TestFetch(t *testing.T) {
18 tc.client.Login("mjl@mox.example", password0)
19 tc.client.Enable("imap4rev2")
20 received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
21 tc.check(err, "parse time")
22 tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
23 tc.client.Select("inbox")
25 uid1 := imapclient.FetchUID(1)
26 date1 := imapclient.FetchInternalDate{Date: received}
27 rfcsize1 := imapclient.FetchRFC822Size(len(exampleMsg))
28 env1 := imapclient.FetchEnvelope{
29 Date: "Mon, 7 Feb 1994 21:52:25 -0800",
30 Subject: "afternoon meeting",
31 From: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
32 Sender: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
33 ReplyTo: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
34 To: []imapclient.Address{{Mailbox: "mooch", Host: "owatagu.siam.edu.example"}},
35 MessageID: "<B27397-0100000@Blurdybloop.example>",
37 noflags := imapclient.FetchFlags(nil)
38 bodyxstructure1 := imapclient.FetchBodystructure{
40 Body: imapclient.BodyTypeText{
42 MediaSubtype: "PLAIN",
43 BodyFields: imapclient.BodyFields{
44 Params: [][2]string{[...]string{"CHARSET", "US-ASCII"}},
50 bodystructure1 := bodyxstructure1
51 bodystructure1.RespAttr = "BODYSTRUCTURE"
53 split := strings.SplitN(exampleMsg, "\r\n\r\n", 2)
54 exampleMsgHeader := split[0] + "\r\n\r\n"
55 exampleMsgBody := split[1]
57 binary1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: exampleMsg}
58 binarypart1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: exampleMsgBody}
59 binarypartial1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: exampleMsg[1:2]}
60 binarypartpartial1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: exampleMsgBody[1:2]}
61 binaryend1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: ""}
62 binarypartend1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: ""}
63 binarysize1 := imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[]", Size: int64(len(exampleMsg))}
64 binarysizepart1 := imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[1]", Parts: []uint32{1}, Size: int64(len(exampleMsgBody))}
65 bodyheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER]", Section: "HEADER", Body: exampleMsgHeader}
66 bodytext1 := imapclient.FetchBody{RespAttr: "BODY[TEXT]", Section: "TEXT", Body: exampleMsgBody}
67 body1 := imapclient.FetchBody{RespAttr: "BODY[]", Body: exampleMsg}
68 bodypart1 := imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: exampleMsgBody}
69 bodyoff1 := imapclient.FetchBody{RespAttr: "BODY[]<1>", Section: "", Offset: 1, Body: exampleMsg[1:3]}
70 body1off1 := imapclient.FetchBody{RespAttr: "BODY[1]<1>", Section: "1", Offset: 1, Body: exampleMsgBody[1:3]}
71 bodyend1 := imapclient.FetchBody{RespAttr: "BODY[1]<100000>", Section: "1", Offset: 100000, Body: ""} // todo: should offset be what was requested, or the size of the message?
72 rfcheader1 := imapclient.FetchRFC822Header(exampleMsgHeader)
73 rfctext1 := imapclient.FetchRFC822Text(exampleMsgBody)
74 rfc1 := imapclient.FetchRFC822(exampleMsg)
75 headerSplit := strings.SplitN(exampleMsgHeader, "\r\n", 2)
76 dateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS (Date)]", Section: "HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
77 nodateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS.NOT (Date)]", Section: "HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
78 date1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS (Date)]", Section: "1.HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
79 nodate1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS.NOT (Date)]", Section: "1.HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
80 mime1 := imapclient.FetchBody{RespAttr: "BODY[1.MIME]", Section: "1.MIME", Body: "MIME-Version: 1.0\r\nContent-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n\r\n"}
82 flagsSeen := imapclient.FetchFlags{`\Seen`}
84 tc.transactf("ok", "fetch 1 all")
85 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, noflags}})
87 tc.transactf("ok", "fetch 1 fast")
88 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, noflags}})
90 tc.transactf("ok", "fetch 1 full")
91 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, bodyxstructure1, noflags}})
93 tc.transactf("ok", "fetch 1 flags")
94 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, noflags}})
96 tc.transactf("ok", "fetch 1 bodystructure")
97 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}})
99 // Should be returned unmodified, because there is no content-transfer-encoding.
100 tc.transactf("ok", "fetch 1 binary[]")
101 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1, flagsSeen}})
103 tc.transactf("ok", "fetch 1 binary[1]")
104 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypart1}}) // Seen flag not changed.
106 tc.client.StoreFlagsClear("1", true, `\Seen`)
107 tc.transactf("ok", "fetch 1 binary[]<1.1>")
108 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartial1, flagsSeen}})
110 tc.client.StoreFlagsClear("1", true, `\Seen`)
111 tc.transactf("ok", "fetch 1 binary[1]<1.1>")
112 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartpartial1, flagsSeen}})
114 tc.client.StoreFlagsClear("1", true, `\Seen`)
115 tc.transactf("ok", "fetch 1 binary[]<10000.10001>")
116 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binaryend1, flagsSeen}})
118 tc.client.StoreFlagsClear("1", true, `\Seen`)
119 tc.transactf("ok", "fetch 1 binary[1]<10000.10001>")
120 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartend1, flagsSeen}})
122 tc.client.StoreFlagsClear("1", true, `\Seen`)
123 tc.transactf("ok", "fetch 1 binary.size[]")
124 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysize1}})
126 tc.transactf("ok", "fetch 1 binary.size[1]")
127 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysizepart1}})
129 tc.client.StoreFlagsClear("1", true, `\Seen`)
130 tc.transactf("ok", "fetch 1 body[]")
131 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, flagsSeen}})
132 tc.transactf("ok", "fetch 1 body[]<1.2>")
133 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyoff1}}) // Already seen.
135 tc.client.StoreFlagsClear("1", true, `\Seen`)
136 tc.transactf("ok", "fetch 1 body[1]")
137 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodypart1, flagsSeen}})
139 tc.client.StoreFlagsClear("1", true, `\Seen`)
140 tc.transactf("ok", "fetch 1 body[1]<1.2>")
141 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1off1, flagsSeen}})
143 tc.client.StoreFlagsClear("1", true, `\Seen`)
144 tc.transactf("ok", "fetch 1 body[1]<100000.100000>")
145 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyend1, flagsSeen}})
147 tc.client.StoreFlagsClear("1", true, `\Seen`)
148 tc.transactf("ok", "fetch 1 body[header]")
149 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyheader1, flagsSeen}})
151 tc.client.StoreFlagsClear("1", true, `\Seen`)
152 tc.transactf("ok", "fetch 1 body[text]")
153 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodytext1, flagsSeen}})
156 tc.client.StoreFlagsClear("1", true, `\Seen`)
157 tc.transactf("ok", "fetch 1 rfc822.header")
158 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfcheader1}})
161 tc.client.StoreFlagsClear("1", true, `\Seen`)
162 tc.transactf("ok", "fetch 1 rfc822.text")
163 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1, flagsSeen}})
166 tc.client.StoreFlagsClear("1", true, `\Seen`)
167 tc.transactf("ok", "fetch 1 rfc822")
168 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1, flagsSeen}})
170 // With PEEK, we should not get the \Seen flag.
171 tc.client.StoreFlagsClear("1", true, `\Seen`)
172 tc.transactf("ok", "fetch 1 body.peek[]")
173 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
175 tc.transactf("ok", "fetch 1 binary.peek[]")
176 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
178 // HEADER.FIELDS and .NOT
179 tc.transactf("ok", "fetch 1 body.peek[header.fields (date)]")
180 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, dateheader1}})
181 tc.transactf("ok", "fetch 1 body.peek[header.fields.not (date)]")
182 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodateheader1}})
184 tc.transactf("ok", "fetch 1 body.peek[1.header.fields (date)]")
185 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1header1}})
186 tc.transactf("ok", "fetch 1 body.peek[1.header.fields.not (date)]")
187 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodate1header1}})
190 tc.transactf("ok", "fetch 1 body.peek[1.mime]")
191 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, mime1}})
194 tc.transactf("bad", "fetch 2 body[]")
196 tc.transactf("ok", "fetch 1:1 body[]")
197 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, flagsSeen}})
200 tc.transactf("ok", "uid fetch 1 body[]")
201 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
204 tc.transactf("ok", "uid fetch 2 body[]")
208 tc.transactf("ok", "uid fetch 1 savedate")
209 // Fetch exact SaveDate we'll be expecting from server.
210 var saveDate time.Time
211 err = tc.account.DB.Read(ctxbg, func(tx *bstore.Tx) error {
212 inbox, err := tc.account.MailboxFind(tx, "Inbox")
213 tc.check(err, "get inbox")
215 t.Fatalf("missing inbox")
217 m, err := bstore.QueryTx[store.Message](tx).FilterNonzero(store.Message{MailboxID: inbox.ID, UID: store.UID(uid1)}).Get()
218 tc.check(err, "get message")
219 if m.SaveDate == nil {
220 t.Fatalf("zero savedate for message")
222 saveDate = m.SaveDate.Truncate(time.Second)
225 tc.check(err, "get savedate")
226 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchSaveDate{SaveDate: &saveDate}}})
228 // Test some invalid syntax.
229 tc.transactf("bad", "fetch")
230 tc.transactf("bad", "fetch ")
231 tc.transactf("bad", "fetch ")
232 tc.transactf("bad", "fetch 1") // At least one requested item required.
233 tc.transactf("bad", "fetch 1 ()") // Empty list not allowed
234 tc.transactf("bad", "fetch 1 unknown")
235 tc.transactf("bad", "fetch 1 (unknown)")
236 tc.transactf("bad", "fetch 1 (all)") // Macro's not allowed in list.
237 tc.transactf("bad", "fetch 1 binary") // [] required
238 tc.transactf("bad", "fetch 1 binary[text]") // Text/header etc only allowed for body[].
239 tc.transactf("bad", "fetch 1 binary[]<1>") // Count required.
240 tc.transactf("bad", "fetch 1 binary[]<1.0>") // Count must be > 0.
241 tc.transactf("bad", "fetch 1 binary[]<1..1>") // Single dot.
242 tc.transactf("bad", "fetch 1 body[]<1>") // Count required.
243 tc.transactf("bad", "fetch 1 body[]<1.0>") // Count must be > 0.
244 tc.transactf("bad", "fetch 1 body[]<1..1>") // Single dot.
245 tc.transactf("bad", "fetch 1 body[header.fields]") // List of headers required.
246 tc.transactf("bad", "fetch 1 body[header.fields ()]") // List must be non-empty.
247 tc.transactf("bad", "fetch 1 body[header.fields.not]") // List of headers required.
248 tc.transactf("bad", "fetch 1 body[header.fields.not ()]") // List must be non-empty.
249 tc.transactf("bad", "fetch 1 body[mime]") // MIME must be prefixed with a number.
../rfc/9051:4497
251 tc.transactf("no", "fetch 1 body[2]") // No such part.
253 // Add more complex message.
255 uid2 := imapclient.FetchUID(2)
256 bodystructure2 := imapclient.FetchBodystructure{
257 RespAttr: "BODYSTRUCTURE",
258 Body: imapclient.BodyTypeMpart{
260 imapclient.BodyTypeBasic{BodyFields: imapclient.BodyFields{Octets: 275}},
261 imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "PLAIN", BodyFields: imapclient.BodyFields{Params: [][2]string{{"CHARSET", "US-ASCII"}}, Octets: 114}, Lines: 3},
262 imapclient.BodyTypeMpart{
264 imapclient.BodyTypeBasic{MediaType: "AUDIO", MediaSubtype: "BASIC", BodyFields: imapclient.BodyFields{CTE: "BASE64", Octets: 22}},
265 imapclient.BodyTypeBasic{MediaType: "IMAGE", MediaSubtype: "JPEG", BodyFields: imapclient.BodyFields{CTE: "BASE64"}},
267 MediaSubtype: "PARALLEL",
268 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-2"}}},
270 imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "ENRICHED", BodyFields: imapclient.BodyFields{Octets: 145}, Lines: 5},
271 imapclient.BodyTypeMsg{
272 MediaType: "MESSAGE",
273 MediaSubtype: "RFC822",
274 BodyFields: imapclient.BodyFields{Octets: 228},
275 Envelope: imapclient.Envelope{
276 Subject: "(subject in US-ASCII)",
277 From: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
278 Sender: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
279 ReplyTo: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
280 To: []imapclient.Address{{Name: "mox", Adl: "", Mailbox: "info", Host: "mox.example"}},
282 Bodystructure: imapclient.BodyTypeText{
283 MediaType: "TEXT", MediaSubtype: "PLAIN", BodyFields: imapclient.BodyFields{Params: [][2]string{{"CHARSET", "ISO-8859-1"}}, CTE: "QUOTED-PRINTABLE", Octets: 51}, Lines: 1},
287 MediaSubtype: "MIXED",
288 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-1"}}},
291 tc.client.Append("inbox", nil, &received, []byte(nestedMessage))
292 tc.transactf("ok", "fetch 2 bodystructure")
293 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
295 // Multiple responses.
296 tc.transactf("ok", "fetch 1:2 bodystructure")
297 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
298 tc.transactf("ok", "fetch 1,2 bodystructure")
299 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
300 tc.transactf("ok", "fetch 2:1 bodystructure")
301 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
302 tc.transactf("ok", "fetch 1:* bodystructure")
303 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
304 tc.transactf("ok", "fetch *:1 bodystructure")
305 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
306 tc.transactf("ok", "fetch *:2 bodystructure")
307 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
309 tc.transactf("ok", "fetch * bodystructure") // Highest msgseq.
310 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
312 tc.transactf("ok", "uid fetch 1:* bodystructure")
313 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
315 tc.transactf("ok", "uid fetch 1:2 bodystructure")
316 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
318 tc.transactf("ok", "uid fetch 1,2 bodystructure")
319 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
321 tc.transactf("ok", "uid fetch 2:2 bodystructure")
322 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
324 // todo: read the bodies/headers of the parts, and of the nested message.
325 tc.transactf("ok", "fetch 2 body.peek[]")
326 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[]", Body: nestedMessage}}})
328 part1 := tocrlf(` ... Some text appears here ...
330[Note that the blank between the boundary and the start
331 of the text in this part means no header fields were
332 given and this is text in the US-ASCII character set.
333 It could have been done with explicit typing as in the
336 tc.transactf("ok", "fetch 2 body.peek[1]")
337 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: part1}}})
339 tc.transactf("no", "fetch 2 binary.peek[3]") // Only allowed on leaf parts, not multiparts.
340 tc.transactf("no", "fetch 2 binary.peek[5]") // Only allowed on leaf parts, not messages.
342 part31 := "aGVsbG8NCndvcmxkDQo=\r\n"
343 part31dec := "hello\r\nworld\r\n"
344 tc.transactf("ok", "fetch 2 binary.size[3.1]")
345 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))}}})
347 tc.transactf("ok", "fetch 2 body.peek[3.1]")
348 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3.1]", Section: "3.1", Body: part31}}})
350 tc.transactf("ok", "fetch 2 binary.peek[3.1]")
351 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBinary{RespAttr: "BINARY[3.1]", Parts: []uint32{3, 1}, Data: part31dec}}})
353 part3 := tocrlf(`--unique-boundary-2
354Content-Type: audio/basic
355Content-Transfer-Encoding: base64
360Content-Type: image/jpeg
361Content-Transfer-Encoding: base64
367 tc.transactf("ok", "fetch 2 body.peek[3]")
368 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3]", Section: "3", Body: part3}}})
370 part2mime := tocrlf(`Content-type: text/plain; charset=US-ASCII
373 tc.transactf("ok", "fetch 2 body.peek[2.mime]")
374 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[2.MIME]", Section: "2.MIME", Body: part2mime}}})
376 part5 := tocrlf(`From: info@mox.example
377To: mox <info@mox.example>
378Subject: (subject in US-ASCII)
379Content-Type: Text/plain; charset=ISO-8859-1
380Content-Transfer-Encoding: Quoted-printable
382 ... Additional text in ISO-8859-1 goes here ...
384 tc.transactf("ok", "fetch 2 body.peek[5]")
385 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5]", Section: "5", Body: part5}}})
387 part5header := tocrlf(`From: info@mox.example
388To: mox <info@mox.example>
389Subject: (subject in US-ASCII)
390Content-Type: Text/plain; charset=ISO-8859-1
391Content-Transfer-Encoding: Quoted-printable
394 tc.transactf("ok", "fetch 2 body.peek[5.header]")
395 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.HEADER]", Section: "5.HEADER", Body: part5header}}})
397 part5mime := tocrlf(`Content-Type: Text/plain; charset=ISO-8859-1
398Content-Transfer-Encoding: Quoted-printable
401 tc.transactf("ok", "fetch 2 body.peek[5.mime]")
402 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.MIME]", Section: "5.MIME", Body: part5mime}}})
404 part5text := " ... Additional text in ISO-8859-1 goes here ...\r\n"
405 tc.transactf("ok", "fetch 2 body.peek[5.text]")
406 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.TEXT]", Section: "5.TEXT", Body: part5text}}})
408 tc.transactf("ok", "fetch 2 body.peek[5.1]")
409 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5text}}})
411 // In case of EXAMINE instead of SELECT, we should not be seeing any changed \Seen flags for non-peek commands.
412 tc.client.StoreFlagsClear("1", true, `\Seen`)
414 tc.client.Examine("inbox")
416 tc.transactf("ok", "fetch 1 binary[]")
417 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
419 tc.transactf("ok", "fetch 1 body[]")
420 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
422 tc.transactf("ok", "fetch 1 rfc822.text")
423 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1}})
425 tc.transactf("ok", "fetch 1 rfc822")
426 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}})