1package imapserver
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/mjl-/mox/imapclient"
9)
10
11func TestFetch(t *testing.T) {
12 tc := start(t)
13 defer tc.close()
14
15 tc.client.Login("mjl@mox.example", password0)
16 tc.client.Enable("imap4rev2")
17 received, err := time.Parse(time.RFC3339, "2022-11-16T10:01:00+01:00")
18 tc.check(err, "parse time")
19 tc.client.Append("inbox", nil, &received, []byte(exampleMsg))
20 tc.client.Select("inbox")
21
22 uid1 := imapclient.FetchUID(1)
23 date1 := imapclient.FetchInternalDate("16-Nov-2022 10:01:00 +0100")
24 rfcsize1 := imapclient.FetchRFC822Size(len(exampleMsg))
25 env1 := imapclient.FetchEnvelope{
26 Date: "Mon, 7 Feb 1994 21:52:25 -0800",
27 Subject: "afternoon meeting",
28 From: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
29 Sender: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
30 ReplyTo: []imapclient.Address{{Name: "Fred Foobar", Mailbox: "foobar", Host: "blurdybloop.example"}},
31 To: []imapclient.Address{{Mailbox: "mooch", Host: "owatagu.siam.edu.example"}},
32 MessageID: "<B27397-0100000@Blurdybloop.example>",
33 }
34 noflags := imapclient.FetchFlags(nil)
35 bodyxstructure1 := imapclient.FetchBodystructure{
36 RespAttr: "BODY",
37 Body: imapclient.BodyTypeText{
38 MediaType: "TEXT",
39 MediaSubtype: "PLAIN",
40 BodyFields: imapclient.BodyFields{
41 Params: [][2]string{[...]string{"CHARSET", "US-ASCII"}},
42 Octets: 57,
43 },
44 Lines: 2,
45 },
46 }
47 bodystructure1 := bodyxstructure1
48 bodystructure1.RespAttr = "BODYSTRUCTURE"
49
50 split := strings.SplitN(exampleMsg, "\r\n\r\n", 2)
51 exampleMsgHeader := split[0] + "\r\n\r\n"
52 exampleMsgBody := split[1]
53
54 binary1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: exampleMsg}
55 binarypart1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: exampleMsgBody}
56 binarypartial1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: exampleMsg[1:2]}
57 binarypartpartial1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: exampleMsgBody[1:2]}
58 binaryend1 := imapclient.FetchBinary{RespAttr: "BINARY[]", Data: ""}
59 binarypartend1 := imapclient.FetchBinary{RespAttr: "BINARY[1]", Parts: []uint32{1}, Data: ""}
60 binarysize1 := imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[]", Size: int64(len(exampleMsg))}
61 binarysizepart1 := imapclient.FetchBinarySize{RespAttr: "BINARY.SIZE[1]", Parts: []uint32{1}, Size: int64(len(exampleMsgBody))}
62 bodyheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER]", Section: "HEADER", Body: exampleMsgHeader}
63 bodytext1 := imapclient.FetchBody{RespAttr: "BODY[TEXT]", Section: "TEXT", Body: exampleMsgBody}
64 body1 := imapclient.FetchBody{RespAttr: "BODY[]", Body: exampleMsg}
65 bodypart1 := imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: exampleMsgBody}
66 bodyoff1 := imapclient.FetchBody{RespAttr: "BODY[]<1>", Section: "", Offset: 1, Body: exampleMsg[1:3]}
67 body1off1 := imapclient.FetchBody{RespAttr: "BODY[1]<1>", Section: "1", Offset: 1, Body: exampleMsgBody[1:3]}
68 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?
69 rfcheader1 := imapclient.FetchRFC822Header(exampleMsgHeader)
70 rfctext1 := imapclient.FetchRFC822Text(exampleMsgBody)
71 rfc1 := imapclient.FetchRFC822(exampleMsg)
72 headerSplit := strings.SplitN(exampleMsgHeader, "\r\n", 2)
73 dateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS (Date)]", Section: "HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
74 nodateheader1 := imapclient.FetchBody{RespAttr: "BODY[HEADER.FIELDS.NOT (Date)]", Section: "HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
75 date1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS (Date)]", Section: "1.HEADER.FIELDS (Date)", Body: headerSplit[0] + "\r\n\r\n"}
76 nodate1header1 := imapclient.FetchBody{RespAttr: "BODY[1.HEADER.FIELDS.NOT (Date)]", Section: "1.HEADER.FIELDS.NOT (Date)", Body: headerSplit[1]}
77 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"}
78
79 flagsSeen := imapclient.FetchFlags{`\Seen`}
80
81 tc.transactf("ok", "fetch 1 all")
82 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, noflags}})
83
84 tc.transactf("ok", "fetch 1 fast")
85 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, noflags}})
86
87 tc.transactf("ok", "fetch 1 full")
88 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, bodyxstructure1, noflags}})
89
90 tc.transactf("ok", "fetch 1 flags")
91 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, noflags}})
92
93 tc.transactf("ok", "fetch 1 bodystructure")
94 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}})
95
96 // Should be returned unmodified, because there is no content-transfer-encoding.
97 tc.transactf("ok", "fetch 1 binary[]")
98 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1, flagsSeen}})
99
100 tc.transactf("ok", "fetch 1 binary[1]")
101 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypart1}}) // Seen flag not changed.
102
103 tc.client.StoreFlagsClear("1", true, `\Seen`)
104 tc.transactf("ok", "fetch 1 binary[]<1.1>")
105 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartial1, flagsSeen}})
106
107 tc.client.StoreFlagsClear("1", true, `\Seen`)
108 tc.transactf("ok", "fetch 1 binary[1]<1.1>")
109 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartpartial1, flagsSeen}})
110
111 tc.client.StoreFlagsClear("1", true, `\Seen`)
112 tc.transactf("ok", "fetch 1 binary[]<10000.10001>")
113 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binaryend1, flagsSeen}})
114
115 tc.client.StoreFlagsClear("1", true, `\Seen`)
116 tc.transactf("ok", "fetch 1 binary[1]<10000.10001>")
117 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypartend1, flagsSeen}})
118
119 tc.client.StoreFlagsClear("1", true, `\Seen`)
120 tc.transactf("ok", "fetch 1 binary.size[]")
121 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysize1}})
122
123 tc.transactf("ok", "fetch 1 binary.size[1]")
124 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysizepart1}})
125
126 tc.client.StoreFlagsClear("1", true, `\Seen`)
127 tc.transactf("ok", "fetch 1 body[]")
128 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, flagsSeen}})
129 tc.transactf("ok", "fetch 1 body[]<1.2>")
130 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyoff1}}) // Already seen.
131
132 tc.client.StoreFlagsClear("1", true, `\Seen`)
133 tc.transactf("ok", "fetch 1 body[1]")
134 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodypart1, flagsSeen}})
135
136 tc.client.StoreFlagsClear("1", true, `\Seen`)
137 tc.transactf("ok", "fetch 1 body[1]<1.2>")
138 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1off1, flagsSeen}})
139
140 tc.client.StoreFlagsClear("1", true, `\Seen`)
141 tc.transactf("ok", "fetch 1 body[1]<100000.100000>")
142 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyend1, flagsSeen}})
143
144 tc.client.StoreFlagsClear("1", true, `\Seen`)
145 tc.transactf("ok", "fetch 1 body[header]")
146 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodyheader1, flagsSeen}})
147
148 tc.client.StoreFlagsClear("1", true, `\Seen`)
149 tc.transactf("ok", "fetch 1 body[text]")
150 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodytext1, flagsSeen}})
151
152 // equivalent to body.peek[header], ../rfc/3501:3183
153 tc.client.StoreFlagsClear("1", true, `\Seen`)
154 tc.transactf("ok", "fetch 1 rfc822.header")
155 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfcheader1}})
156
157 // equivalent to body[text], ../rfc/3501:3199
158 tc.client.StoreFlagsClear("1", true, `\Seen`)
159 tc.transactf("ok", "fetch 1 rfc822.text")
160 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1, flagsSeen}})
161
162 // equivalent to body[], ../rfc/3501:3179
163 tc.client.StoreFlagsClear("1", true, `\Seen`)
164 tc.transactf("ok", "fetch 1 rfc822")
165 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1, flagsSeen}})
166
167 // With PEEK, we should not get the \Seen flag.
168 tc.client.StoreFlagsClear("1", true, `\Seen`)
169 tc.transactf("ok", "fetch 1 body.peek[]")
170 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
171
172 tc.transactf("ok", "fetch 1 binary.peek[]")
173 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
174
175 // HEADER.FIELDS and .NOT
176 tc.transactf("ok", "fetch 1 body.peek[header.fields (date)]")
177 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, dateheader1}})
178 tc.transactf("ok", "fetch 1 body.peek[header.fields.not (date)]")
179 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodateheader1}})
180 // For non-multipart messages, 1 means the whole message. ../rfc/9051:4481
181 tc.transactf("ok", "fetch 1 body.peek[1.header.fields (date)]")
182 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1header1}})
183 tc.transactf("ok", "fetch 1 body.peek[1.header.fields.not (date)]")
184 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, nodate1header1}})
185
186 // MIME, part 1 for non-multipart messages is the message itself. ../rfc/9051:4481
187 tc.transactf("ok", "fetch 1 body.peek[1.mime]")
188 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, mime1}})
189
190 // Missing sequence number. ../rfc/9051:7018
191 tc.transactf("bad", "fetch 2 body[]")
192
193 tc.transactf("ok", "fetch 1:1 body[]")
194 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, flagsSeen}})
195
196 // UID fetch
197 tc.transactf("ok", "uid fetch 1 body[]")
198 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
199
200 // UID fetch
201 tc.transactf("ok", "uid fetch 2 body[]")
202 tc.xuntagged()
203
204 // Test some invalid syntax.
205 tc.transactf("bad", "fetch")
206 tc.transactf("bad", "fetch ")
207 tc.transactf("bad", "fetch ")
208 tc.transactf("bad", "fetch 1") // At least one requested item required.
209 tc.transactf("bad", "fetch 1 ()") // Empty list not allowed
210 tc.transactf("bad", "fetch 1 unknown")
211 tc.transactf("bad", "fetch 1 (unknown)")
212 tc.transactf("bad", "fetch 1 (all)") // Macro's not allowed in list.
213 tc.transactf("bad", "fetch 1 binary") // [] required
214 tc.transactf("bad", "fetch 1 binary[text]") // Text/header etc only allowed for body[].
215 tc.transactf("bad", "fetch 1 binary[]<1>") // Count required.
216 tc.transactf("bad", "fetch 1 binary[]<1.0>") // Count must be > 0.
217 tc.transactf("bad", "fetch 1 binary[]<1..1>") // Single dot.
218 tc.transactf("bad", "fetch 1 body[]<1>") // Count required.
219 tc.transactf("bad", "fetch 1 body[]<1.0>") // Count must be > 0.
220 tc.transactf("bad", "fetch 1 body[]<1..1>") // Single dot.
221 tc.transactf("bad", "fetch 1 body[header.fields]") // List of headers required.
222 tc.transactf("bad", "fetch 1 body[header.fields ()]") // List must be non-empty.
223 tc.transactf("bad", "fetch 1 body[header.fields.not]") // List of headers required.
224 tc.transactf("bad", "fetch 1 body[header.fields.not ()]") // List must be non-empty.
225 tc.transactf("bad", "fetch 1 body[mime]") // MIME must be prefixed with a number. ../rfc/9051:4497
226
227 tc.transactf("no", "fetch 1 body[2]") // No such part.
228
229 // Add more complex message.
230
231 uid2 := imapclient.FetchUID(2)
232 bodystructure2 := imapclient.FetchBodystructure{
233 RespAttr: "BODYSTRUCTURE",
234 Body: imapclient.BodyTypeMpart{
235 Bodies: []any{
236 imapclient.BodyTypeBasic{BodyFields: imapclient.BodyFields{Octets: 275}},
237 imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "PLAIN", BodyFields: imapclient.BodyFields{Params: [][2]string{{"CHARSET", "US-ASCII"}}, Octets: 114}, Lines: 3},
238 imapclient.BodyTypeMpart{
239 Bodies: []any{
240 imapclient.BodyTypeBasic{MediaType: "AUDIO", MediaSubtype: "BASIC", BodyFields: imapclient.BodyFields{CTE: "BASE64", Octets: 22}},
241 imapclient.BodyTypeBasic{MediaType: "IMAGE", MediaSubtype: "JPEG", BodyFields: imapclient.BodyFields{CTE: "BASE64"}},
242 },
243 MediaSubtype: "PARALLEL",
244 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-2"}}},
245 },
246 imapclient.BodyTypeText{MediaType: "TEXT", MediaSubtype: "ENRICHED", BodyFields: imapclient.BodyFields{Octets: 145}, Lines: 5},
247 imapclient.BodyTypeMsg{
248 MediaType: "MESSAGE",
249 MediaSubtype: "RFC822",
250 BodyFields: imapclient.BodyFields{Octets: 228},
251 Envelope: imapclient.Envelope{
252 Subject: "(subject in US-ASCII)",
253 From: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
254 Sender: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
255 ReplyTo: []imapclient.Address{{Name: "", Adl: "", Mailbox: "info", Host: "mox.example"}},
256 To: []imapclient.Address{{Name: "mox", Adl: "", Mailbox: "info", Host: "mox.example"}},
257 },
258 Bodystructure: imapclient.BodyTypeText{
259 MediaType: "TEXT", MediaSubtype: "PLAIN", BodyFields: imapclient.BodyFields{Params: [][2]string{{"CHARSET", "ISO-8859-1"}}, CTE: "QUOTED-PRINTABLE", Octets: 51}, Lines: 1},
260 Lines: 7,
261 },
262 },
263 MediaSubtype: "MIXED",
264 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-1"}}},
265 },
266 }
267 tc.client.Append("inbox", nil, &received, []byte(nestedMessage))
268 tc.transactf("ok", "fetch 2 bodystructure")
269 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
270
271 // Multiple responses.
272 tc.transactf("ok", "fetch 1:2 bodystructure")
273 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
274 tc.transactf("ok", "fetch 1,2 bodystructure")
275 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
276 tc.transactf("ok", "fetch 2:1 bodystructure")
277 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
278 tc.transactf("ok", "fetch 1:* bodystructure")
279 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
280 tc.transactf("ok", "fetch *:1 bodystructure")
281 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
282 tc.transactf("ok", "fetch *:2 bodystructure")
283 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
284
285 tc.transactf("ok", "fetch * bodystructure") // Highest msgseq.
286 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
287
288 tc.transactf("ok", "uid fetch 1:* bodystructure")
289 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
290
291 tc.transactf("ok", "uid fetch 1:2 bodystructure")
292 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
293
294 tc.transactf("ok", "uid fetch 1,2 bodystructure")
295 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}}, imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
296
297 tc.transactf("ok", "uid fetch 2:2 bodystructure")
298 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
299
300 // todo: read the bodies/headers of the parts, and of the nested message.
301 tc.transactf("ok", "fetch 2 body.peek[]")
302 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[]", Body: nestedMessage}}})
303
304 part1 := tocrlf(` ... Some text appears here ...
305
306[Note that the blank between the boundary and the start
307 of the text in this part means no header fields were
308 given and this is text in the US-ASCII character set.
309 It could have been done with explicit typing as in the
310 next part.]
311`)
312 tc.transactf("ok", "fetch 2 body.peek[1]")
313 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[1]", Section: "1", Body: part1}}})
314
315 tc.transactf("no", "fetch 2 binary.peek[3]") // Only allowed on leaf parts, not multiparts.
316 tc.transactf("no", "fetch 2 binary.peek[5]") // Only allowed on leaf parts, not messages.
317
318 part31 := "aGVsbG8NCndvcmxkDQo=\r\n"
319 part31dec := "hello\r\nworld\r\n"
320 tc.transactf("ok", "fetch 2 binary.size[3.1]")
321 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))}}})
322
323 tc.transactf("ok", "fetch 2 body.peek[3.1]")
324 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3.1]", Section: "3.1", Body: part31}}})
325
326 tc.transactf("ok", "fetch 2 binary.peek[3.1]")
327 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBinary{RespAttr: "BINARY[3.1]", Parts: []uint32{3, 1}, Data: part31dec}}})
328
329 part3 := tocrlf(`--unique-boundary-2
330Content-Type: audio/basic
331Content-Transfer-Encoding: base64
332
333aGVsbG8NCndvcmxkDQo=
334
335--unique-boundary-2
336Content-Type: image/jpeg
337Content-Transfer-Encoding: base64
338
339
340--unique-boundary-2--
341
342`)
343 tc.transactf("ok", "fetch 2 body.peek[3]")
344 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[3]", Section: "3", Body: part3}}})
345
346 part2mime := tocrlf(`Content-type: text/plain; charset=US-ASCII
347
348`)
349 tc.transactf("ok", "fetch 2 body.peek[2.mime]")
350 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[2.MIME]", Section: "2.MIME", Body: part2mime}}})
351
352 part5 := tocrlf(`From: info@mox.example
353To: mox <info@mox.example>
354Subject: (subject in US-ASCII)
355Content-Type: Text/plain; charset=ISO-8859-1
356Content-Transfer-Encoding: Quoted-printable
357
358 ... Additional text in ISO-8859-1 goes here ...
359`)
360 tc.transactf("ok", "fetch 2 body.peek[5]")
361 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5]", Section: "5", Body: part5}}})
362
363 part5header := tocrlf(`From: info@mox.example
364To: mox <info@mox.example>
365Subject: (subject in US-ASCII)
366Content-Type: Text/plain; charset=ISO-8859-1
367Content-Transfer-Encoding: Quoted-printable
368
369`)
370 tc.transactf("ok", "fetch 2 body.peek[5.header]")
371 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.HEADER]", Section: "5.HEADER", Body: part5header}}})
372
373 part5mime := tocrlf(`Content-Type: Text/plain; charset=ISO-8859-1
374Content-Transfer-Encoding: Quoted-printable
375
376`)
377 tc.transactf("ok", "fetch 2 body.peek[5.mime]")
378 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.MIME]", Section: "5.MIME", Body: part5mime}}})
379
380 part5text := " ... Additional text in ISO-8859-1 goes here ...\r\n"
381 tc.transactf("ok", "fetch 2 body.peek[5.text]")
382 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.TEXT]", Section: "5.TEXT", Body: part5text}}})
383
384 tc.transactf("ok", "fetch 2 body.peek[5.1]")
385 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, imapclient.FetchBody{RespAttr: "BODY[5.1]", Section: "5.1", Body: part5text}}})
386
387 // In case of EXAMINE instead of SELECT, we should not be seeing any changed \Seen flags for non-peek commands.
388 tc.client.StoreFlagsClear("1", true, `\Seen`)
389 tc.client.Unselect()
390 tc.client.Examine("inbox")
391
392 tc.transactf("ok", "fetch 1 binary[]")
393 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
394
395 tc.transactf("ok", "fetch 1 body[]")
396 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
397
398 tc.transactf("ok", "fetch 1 rfc822.text")
399 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1}})
400
401 tc.transactf("ok", "fetch 1 rfc822")
402 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}})
403
404 tc.client.Logout()
405}
406