1package imapserver
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/mjl-/bstore"
9
10 "github.com/mjl-/mox/imapclient"
11 "github.com/mjl-/mox/store"
12)
13
14func TestFetch(t *testing.T) {
15 tc := start(t)
16 defer tc.close()
17
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")
24
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>",
36 }
37 noflags := imapclient.FetchFlags(nil)
38 bodyxstructure1 := imapclient.FetchBodystructure{
39 RespAttr: "BODY",
40 Body: imapclient.BodyTypeText{
41 MediaType: "TEXT",
42 MediaSubtype: "PLAIN",
43 BodyFields: imapclient.BodyFields{
44 Params: [][2]string{[...]string{"CHARSET", "US-ASCII"}},
45 Octets: 57,
46 },
47 Lines: 2,
48 },
49 }
50 bodystructure1 := bodyxstructure1
51 bodystructure1.RespAttr = "BODYSTRUCTURE"
52
53 split := strings.SplitN(exampleMsg, "\r\n\r\n", 2)
54 exampleMsgHeader := split[0] + "\r\n\r\n"
55 exampleMsgBody := split[1]
56
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"}
81
82 flagsSeen := imapclient.FetchFlags{`\Seen`}
83
84 tc.transactf("ok", "fetch 1 all")
85 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, noflags}})
86
87 tc.transactf("ok", "fetch 1 fast")
88 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, noflags}})
89
90 tc.transactf("ok", "fetch 1 full")
91 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, date1, rfcsize1, env1, bodyxstructure1, noflags}})
92
93 tc.transactf("ok", "fetch 1 flags")
94 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, noflags}})
95
96 tc.transactf("ok", "fetch 1 bodystructure")
97 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, bodystructure1}})
98
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}})
102
103 tc.transactf("ok", "fetch 1 binary[1]")
104 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarypart1}}) // Seen flag not changed.
105
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}})
109
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}})
113
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}})
117
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}})
121
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}})
125
126 tc.transactf("ok", "fetch 1 binary.size[1]")
127 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binarysizepart1}})
128
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.
134
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}})
138
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}})
142
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}})
146
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}})
150
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}})
154
155 // equivalent to body.peek[header], ../rfc/3501:3183
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}})
159
160 // equivalent to body[text], ../rfc/3501:3199
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}})
164
165 // equivalent to body[], ../rfc/3501:3179
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}})
169
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}})
174
175 tc.transactf("ok", "fetch 1 binary.peek[]")
176 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
177
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}})
183 // For non-multipart messages, 1 means the whole message. ../rfc/9051:4481
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}})
188
189 // MIME, part 1 for non-multipart messages is the message itself. ../rfc/9051:4481
190 tc.transactf("ok", "fetch 1 body.peek[1.mime]")
191 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, mime1}})
192
193 // Missing sequence number. ../rfc/9051:7018
194 tc.transactf("bad", "fetch 2 body[]")
195
196 tc.transactf("ok", "fetch 1:1 body[]")
197 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1, flagsSeen}})
198
199 // UID fetch
200 tc.transactf("ok", "uid fetch 1 body[]")
201 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
202
203 // UID fetch
204 tc.transactf("ok", "uid fetch 2 body[]")
205 tc.xuntagged()
206
207 // SAVEDATE
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")
214 if inbox == nil {
215 t.Fatalf("missing inbox")
216 }
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")
221 }
222 saveDate = m.SaveDate.Truncate(time.Second)
223 return nil
224 })
225 tc.check(err, "get savedate")
226 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, imapclient.FetchSaveDate{SaveDate: &saveDate}}})
227
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
250
251 tc.transactf("no", "fetch 1 body[2]") // No such part.
252
253 // Add more complex message.
254
255 uid2 := imapclient.FetchUID(2)
256 bodystructure2 := imapclient.FetchBodystructure{
257 RespAttr: "BODYSTRUCTURE",
258 Body: imapclient.BodyTypeMpart{
259 Bodies: []any{
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{
263 Bodies: []any{
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"}},
266 },
267 MediaSubtype: "PARALLEL",
268 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-2"}}},
269 },
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"}},
281 },
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},
284 Lines: 7,
285 },
286 },
287 MediaSubtype: "MIXED",
288 Ext: &imapclient.BodyExtensionMpart{Params: [][2]string{{"boundary", "unique-boundary-1"}}},
289 },
290 }
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}})
294
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}})
308
309 tc.transactf("ok", "fetch * bodystructure") // Highest msgseq.
310 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
311
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}})
314
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}})
317
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}})
320
321 tc.transactf("ok", "uid fetch 2:2 bodystructure")
322 tc.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, bodystructure2}})
323
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}}})
327
328 part1 := tocrlf(` ... Some text appears here ...
329
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
334 next part.]
335`)
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}}})
338
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.
341
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))}}})
346
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}}})
349
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}}})
352
353 part3 := tocrlf(`--unique-boundary-2
354Content-Type: audio/basic
355Content-Transfer-Encoding: base64
356
357aGVsbG8NCndvcmxkDQo=
358
359--unique-boundary-2
360Content-Type: image/jpeg
361Content-Transfer-Encoding: base64
362
363
364--unique-boundary-2--
365
366`)
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}}})
369
370 part2mime := tocrlf(`Content-type: text/plain; charset=US-ASCII
371
372`)
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}}})
375
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
381
382 ... Additional text in ISO-8859-1 goes here ...
383`)
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}}})
386
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
392
393`)
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}}})
396
397 part5mime := tocrlf(`Content-Type: Text/plain; charset=ISO-8859-1
398Content-Transfer-Encoding: Quoted-printable
399
400`)
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}}})
403
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}}})
407
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}}})
410
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`)
413 tc.client.Unselect()
414 tc.client.Examine("inbox")
415
416 tc.transactf("ok", "fetch 1 binary[]")
417 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, binary1}})
418
419 tc.transactf("ok", "fetch 1 body[]")
420 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, body1}})
421
422 tc.transactf("ok", "fetch 1 rfc822.text")
423 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfctext1}})
424
425 tc.transactf("ok", "fetch 1 rfc822")
426 tc.xuntagged(imapclient.UntaggedFetch{Seq: 1, Attrs: []imapclient.FetchAttr{uid1, rfc1}})
427
428 tc.client.Logout()
429}
430