blob: 109e734e8def09a356ddd0e37f24c6a8302613a3 [file] [log] [blame]
Dusan Kasanc2129de2017-04-13 10:42:21 +02001package parsemail
Dusan Kasan17e497e2017-04-10 22:44:22 +02002
3import (
Dusan Kasan17e497e2017-04-10 22:44:22 +02004 "encoding/base64"
Obi Symons333687f2020-04-05 14:31:35 +10005 "fmt"
Dusan Kasan17e497e2017-04-10 22:44:22 +02006 "io/ioutil"
Dusan Kasan4595dfe2017-04-13 00:38:24 +02007 "net/mail"
8 "strings"
9 "testing"
10 "time"
Dusan Kasan17e497e2017-04-10 22:44:22 +020011)
12
13func TestParseEmail(t *testing.T) {
Dusan Kasan4595dfe2017-04-13 00:38:24 +020014 var testData = map[int]struct {
Dusan Kasan17e497e2017-04-10 22:44:22 +020015 mailData string
16
Dusan Kasan428369f2020-02-24 00:47:31 +010017 contentType string
18 content string
Dusan Kasan4595dfe2017-04-13 00:38:24 +020019 subject string
20 date time.Time
21 from []mail.Address
22 sender mail.Address
23 to []mail.Address
24 replyTo []mail.Address
25 cc []mail.Address
26 bcc []mail.Address
27 messageID string
28 resentDate time.Time
29 resentFrom []mail.Address
30 resentSender mail.Address
31 resentTo []mail.Address
32 resentReplyTo []mail.Address
33 resentCc []mail.Address
34 resentBcc []mail.Address
Dusan Kasanb49ceb62017-04-13 00:00:36 +020035 resentMessageID string
Dusan Kasan4595dfe2017-04-13 00:38:24 +020036 inReplyTo []string
37 references []string
38 htmlBody string
39 textBody string
40 attachments []attachmentData
41 embeddedFiles []embeddedFileData
42 headerCheck func(mail.Header, *testing.T)
Dusan Kasan17e497e2017-04-10 22:44:22 +020043 }{
Dusan Kasanb49ceb62017-04-13 00:00:36 +020044 1: {
Dusan Kasan45ca2642017-04-18 10:39:35 +020045 mailData: rfc5322exampleA11,
Dusan Kasan4595dfe2017-04-13 00:38:24 +020046 subject: "Saying Hello",
Dusan Kasanb49ceb62017-04-13 00:00:36 +020047 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +020048 {
49 Name: "John Doe",
50 Address: "jdoe@machine.example",
51 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020052 },
53 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +020054 {
55 Name: "Mary Smith",
56 Address: "mary@example.net",
57 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020058 },
Dusan Kasan45ca2642017-04-18 10:39:35 +020059 sender: mail.Address{
60 Name: "Michael Jones",
61 Address: "mjones@machine.example",
62 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020063 messageID: "1234@local.machine.example",
Dusan Kasan4595dfe2017-04-13 00:38:24 +020064 date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
Dusan Kasanb49ceb62017-04-13 00:00:36 +020065 textBody: `This is a message just to say hello.
66So, "Hello".`,
67 },
68 2: {
Dusan Kasan45ca2642017-04-18 10:39:35 +020069 mailData: rfc5322exampleA12,
Dusan Kasanb49ceb62017-04-13 00:00:36 +020070 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +020071 {
72 Name: "Joe Q. Public",
73 Address: "john.q.public@example.com",
74 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020075 },
76 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +020077 {
78 Name: "Mary Smith",
79 Address: "mary@x.test",
80 },
81 {
82 Name: "",
83 Address: "jdoe@example.org",
84 },
85 {
86 Name: "Who?",
87 Address: "one@y.test",
88 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020089 },
90 cc: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +020091 {
92 Name: "",
93 Address: "boss@nil.test",
94 },
95 {
96 Name: "Giant; \"Big\" Box",
97 Address: "sysservices@example.net",
98 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +020099 },
100 messageID: "5678.21-Nov-1997@example.com",
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200101 date: parseDate("Tue, 01 Jul 2003 10:52:37 +0200"),
102 textBody: `Hi everyone.`,
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200103 },
104 3: {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200105 mailData: rfc5322exampleA2a,
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200106 subject: "Re: Saying Hello",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200107 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200108 {
109 Name: "Mary Smith",
110 Address: "mary@example.net",
111 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200112 },
113 replyTo: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200114 {
115 Name: "Mary Smith: Personal Account",
116 Address: "smith@home.example",
117 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200118 },
119 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200120 {
121 Name: "John Doe",
122 Address: "jdoe@machine.example",
123 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200124 },
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200125 messageID: "3456@example.net",
126 inReplyTo: []string{"1234@local.machine.example"},
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200127 references: []string{"1234@local.machine.example"},
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200128 date: parseDate("Fri, 21 Nov 1997 10:01:10 -0600"),
129 textBody: `This is a reply to your hello.`,
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200130 },
131 4: {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200132 mailData: rfc5322exampleA2b,
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200133 subject: "Re: Saying Hello",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200134 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200135 {
136 Name: "John Doe",
137 Address: "jdoe@machine.example",
138 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200139 },
140 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200141 {
142 Name: "Mary Smith: Personal Account",
143 Address: "smith@home.example",
144 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200145 },
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200146 messageID: "abcd.1234@local.machine.test",
147 inReplyTo: []string{"3456@example.net"},
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200148 references: []string{"1234@local.machine.example", "3456@example.net"},
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200149 date: parseDate("Fri, 21 Nov 1997 11:00:00 -0600"),
150 textBody: `This is a reply to your reply.`,
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200151 },
152 5: {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200153 mailData: rfc5322exampleA3,
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200154 subject: "Saying Hello",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200155 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200156 {
157 Name: "John Doe",
158 Address: "jdoe@machine.example",
159 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200160 },
161 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200162 {
163 Name: "Mary Smith",
164 Address: "mary@example.net",
165 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200166 },
167 messageID: "1234@local.machine.example",
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200168 date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200169 resentFrom: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200170 {
171 Name: "Mary Smith",
172 Address: "mary@example.net",
173 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200174 },
175 resentTo: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200176 {
177 Name: "Jane Brown",
178 Address: "j-brown@other.example",
179 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200180 },
181 resentMessageID: "78910@example.net",
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200182 resentDate: parseDate("Mon, 24 Nov 1997 14:22:01 -0800"),
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200183 textBody: `This is a message just to say hello.
184So, "Hello".`,
185 },
186 6: {
Dusan Kasan3325e732020-04-04 11:46:04 +0200187 mailData: data1,
Dusan Kasan428369f2020-02-24 00:47:31 +0100188 contentType: `multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf`,
Dusan Kasan3325e732020-04-04 11:46:04 +0200189 content: "",
190 subject: "Peter Paholík",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200191 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200192 {
193 Name: "Peter Paholík",
194 Address: "peter.paholik@gmail.com",
195 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200196 },
197 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200198 {
199 Name: "",
200 Address: "dusan@kasan.sk",
201 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200202 },
Dusan Kasan17e497e2017-04-10 22:44:22 +0200203 messageID: "CACtgX4kNXE7T5XKSKeH_zEcfUUmf2vXVASxYjaaK9cCn-3zb_g@mail.gmail.com",
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200204 date: parseDate("Fri, 07 Apr 2017 09:17:26 +0200"),
205 htmlBody: "<div dir=\"ltr\"><br></div>",
Dusan Kasan17e497e2017-04-10 22:44:22 +0200206 attachments: []attachmentData{
207 {
Dusan Kasan3325e732020-04-04 11:46:04 +0200208 filename: "Peter Paholík 1 4 2017 2017-04-07.json",
209 contentType: "application/json",
210 data: "[1, 2, 3]",
Dusan Kasan17e497e2017-04-10 22:44:22 +0200211 },
212 },
213 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200214 7: {
Dusan Kasan3325e732020-04-04 11:46:04 +0200215 mailData: data2,
Dusan Kasan428369f2020-02-24 00:47:31 +0100216 contentType: `multipart/alternative; boundary="------------C70C0458A558E585ACB75FB4"`,
Dusan Kasan3325e732020-04-04 11:46:04 +0200217 content: "",
218 subject: "Re: Test Subject 2",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200219 from: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200220 {
221 Name: "Sender Man",
222 Address: "sender@domain.com",
223 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200224 },
225 to: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200226 {
227 Name: "",
228 Address: "info@receiver.com",
229 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200230 },
231 cc: []mail.Address{
Dusan Kasan45ca2642017-04-18 10:39:35 +0200232 {
233 Name: "Cc Man",
234 Address: "ccman@gmail.com",
235 },
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200236 },
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200237 messageID: "0e9a21b4-01dc-e5c1-dcd6-58ce5aa61f4f@receiver.com",
238 inReplyTo: []string{"9ff38d03-c4ab-89b7-9328-e99d5e24e3ba@receiver.eu"},
Dusan Kasan17e497e2017-04-10 22:44:22 +0200239 references: []string{"2f6b7595-c01e-46e5-42bc-f263e1c4282d@receiver.com", "9ff38d03-c4ab-89b7-9328-e99d5e24e3ba@domain.com"},
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200240 date: parseDate("Fri, 07 Apr 2017 12:59:55 +0200"),
241 htmlBody: `<html>data<img src="part2.9599C449.04E5EC81@develhell.com"/></html>`,
Dusan Kasan17e497e2017-04-10 22:44:22 +0200242 textBody: `First level
243> Second level
244>> Third level
245>
246`,
247 embeddedFiles: []embeddedFileData{
248 {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200249 cid: "part2.9599C449.04E5EC81@develhell.com",
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200250 contentType: "image/png",
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200251 base64data: "iVBORw0KGgoAAAANSUhEUgAAAQEAAAAYCAIAAAB1IN9NAAAACXBIWXMAAAsTAAALEwEAmpwYYKUKF+Os3baUndC0pDnwNAmLy1SUr2Gw0luxQuV/AwC6cEhVV5VRrwAAAABJRU5ErkJggg==",
Dusan Kasan17e497e2017-04-10 22:44:22 +0200252 },
253 },
254 },
Dusan Kasan428369f2020-02-24 00:47:31 +0100255 8: {
256 mailData: imageContentExample,
257 subject: "Saying Hello",
258 from: []mail.Address{
259 {
260 Name: "John Doe",
261 Address: "jdoe@machine.example",
262 },
263 },
264 to: []mail.Address{
265 {
266 Name: "Mary Smith",
267 Address: "mary@example.net",
268 },
269 },
270 sender: mail.Address{
271 Name: "Michael Jones",
272 Address: "mjones@machine.example",
273 },
Dusan Kasan3325e732020-04-04 11:46:04 +0200274 messageID: "1234@local.machine.example",
275 date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
Dusan Kasan428369f2020-02-24 00:47:31 +0100276 contentType: `image/jpeg; x-unix-mode=0644; name="image.gif"`,
Dusan Kasan3325e732020-04-04 11:46:04 +0200277 content: `GIF89a;`,
Dusan Kasan428369f2020-02-24 00:47:31 +0100278 },
Dusan Kasane97c2f22020-04-04 10:50:00 +0200279 9: {
280 contentType: `multipart/mixed; boundary="0000000000007e2bb40587e36196"`,
Dusan Kasan3325e732020-04-04 11:46:04 +0200281 mailData: textPlainInMultipart,
282 subject: "Re: kern/54143 (virtualbox)",
Maya Rashishbae116e2019-06-10 12:15:25 +0300283 from: []mail.Address{
284 {
285 Name: "Rares",
286 Address: "rares@example.com",
287 },
288 },
289 to: []mail.Address{
290 {
291 Name: "",
292 Address: "bugs@example.com",
293 },
294 },
Dusan Kasan3325e732020-04-04 11:46:04 +0200295 date: parseDate("Fri, 02 May 2019 11:25:35 +0300"),
Maya Rashishbae116e2019-06-10 12:15:25 +0300296 textBody: `plain text part`,
297 },
Dusan Kasan88226cf2020-04-04 11:13:06 +0200298 10: {
k-yomo2e670d92020-04-30 22:42:48 +0900299 contentType: `multipart/mixed; boundary="0000000000007e2bb40587e36196"`,
300 mailData: textHTMLInMultipart,
301 subject: "Re: kern/54143 (virtualbox)",
302 from: []mail.Address{
303 {
304 Name: "Rares",
305 Address: "rares@example.com",
306 },
307 },
308 to: []mail.Address{
309 {
310 Name: "",
311 Address: "bugs@example.com",
312 },
313 },
314 date: parseDate("Fri, 02 May 2019 11:25:35 +0300"),
315 textBody: ``,
316 htmlBody: "<div dir=\"ltr\"><div>html text part</div><div><br></div><div><br><br></div></div>",
317 },
318 11: {
Dusan Kasan88226cf2020-04-04 11:13:06 +0200319 mailData: rfc5322exampleA12WithTimezone,
320 from: []mail.Address{
321 {
322 Name: "Joe Q. Public",
323 Address: "john.q.public@example.com",
324 },
325 },
326 to: []mail.Address{
327 {
328 Name: "Mary Smith",
329 Address: "mary@x.test",
330 },
331 {
332 Name: "",
333 Address: "jdoe@example.org",
334 },
335 {
336 Name: "Who?",
337 Address: "one@y.test",
338 },
339 },
340 cc: []mail.Address{
341 {
342 Name: "",
343 Address: "boss@nil.test",
344 },
345 {
346 Name: "Giant; \"Big\" Box",
347 Address: "sysservices@example.net",
348 },
349 },
350 messageID: "5678.21-Nov-1997@example.com",
351 date: parseDate("Tue, 01 Jul 2003 10:52:37 +0200"),
352 textBody: `Hi everyone.`,
353 },
k-yomo2e670d92020-04-30 22:42:48 +0900354 12: {
Dusan Kasan3325e732020-04-04 11:46:04 +0200355 contentType: "multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf",
356 mailData: attachment7bit,
357 subject: "Peter Foobar",
Edwin Lovef8d8da22019-04-02 14:07:32 +0100358 from: []mail.Address{
359 {
360 Name: "Peter Foobar",
361 Address: "peter.foobar@gmail.com",
362 },
363 },
364 to: []mail.Address{
365 {
366 Name: "",
367 Address: "dusan@kasan.sk",
368 },
369 },
370 messageID: "CACtgX4kNXE7T5XKSKeH_zEcfUUmf2vXVASxYjaaK9cCn-3zb_g@mail.gmail.com",
371 date: parseDate("Tue, 02 Apr 2019 11:12:26 +0000"),
372 htmlBody: "<div dir=\"ltr\"><br></div>",
373 attachments: []attachmentData{
374 {
Dusan Kasan3325e732020-04-04 11:46:04 +0200375 filename: "unencoded.csv",
376 contentType: "application/csv",
377 data: fmt.Sprintf("\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n", "Some", "Data", "In", "Csv", "Format", "Foo", "Bar", "Baz", "Bum", "Poo"),
Edwin Lovef8d8da22019-04-02 14:07:32 +0100378 },
379 },
Dusan Kasan17e497e2017-04-10 22:44:22 +0200380 },
k-yomo2e670d92020-04-30 22:42:48 +0900381 13: {
Dusan Kasan1ce83dc2020-04-07 00:53:14 +0200382 contentType: "multipart/related; boundary=\"000000000000ab2e2205a26de587\"",
383 mailData: multipartRelatedExample,
384 subject: "Saying Hello",
385 from: []mail.Address{
386 {
387 Name: "John Doe",
388 Address: "jdoe@machine.example",
389 },
390 },
391 sender: mail.Address{
392 Name: "Michael Jones",
393 Address: "mjones@machine.example",
394 },
395 to: []mail.Address{
396 {
397 Name: "Mary Smith",
398 Address: "mary@example.net",
399 },
400 },
401 messageID: "1234@local.machine.example",
402 date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
403 htmlBody: "<div dir=\"ltr\"><div>Time for the egg.</div><div><br></div><div><br><br></div></div>",
404 textBody: "Time for the egg.",
405 },
Dusan Kasan17e497e2017-04-10 22:44:22 +0200406 }
407
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200408 for index, td := range testData {
Dusan Kasan428369f2020-02-24 00:47:31 +0100409 e, err := Parse(strings.NewReader(td.mailData))
Antonio331b1182019-01-15 09:58:52 +0100410 if err != nil {
411 t.Error(err)
412 }
Dusan Kasan428369f2020-02-24 00:47:31 +0100413
414 if td.contentType != e.ContentType {
415 t.Errorf("[Test Case %v] Wrong content type. Expected: %s, Got: %s", index, td.contentType, e.ContentType)
416 }
417
418 if td.content != "" {
419 b, err := ioutil.ReadAll(e.Content)
420 if err != nil {
421 t.Error(err)
422 } else if td.content != string(b) {
423 t.Errorf("[Test Case %v] Wrong content. Expected: %s, Got: %s", index, td.content, string(b))
424 }
Dusan Kasan17e497e2017-04-10 22:44:22 +0200425 }
426
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200427 if td.subject != e.Subject {
428 t.Errorf("[Test Case %v] Wrong subject. Expected: %s, Got: %s", index, td.subject, e.Subject)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200429 }
430
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200431 if td.messageID != e.MessageID {
432 t.Errorf("[Test Case %v] Wrong messageID. Expected: '%s', Got: '%s'", index, td.messageID, e.MessageID)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200433 }
434
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200435 if !td.date.Equal(e.Date) {
436 t.Errorf("[Test Case %v] Wrong date. Expected: %v, Got: %v", index, td.date, e.Date)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200437 }
438
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200439 d := dereferenceAddressList(e.From)
440 if !assertAddressListEq(td.from, d) {
441 t.Errorf("[Test Case %v] Wrong from. Expected: %s, Got: %s", index, td.from, d)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200442 }
443
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200444 var sender mail.Address
445 if e.Sender != nil {
446 sender = *e.Sender
447 }
448 if td.sender != sender {
449 t.Errorf("[Test Case %v] Wrong sender. Expected: %s, Got: %s", index, td.sender, sender)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200450 }
451
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200452 d = dereferenceAddressList(e.To)
453 if !assertAddressListEq(td.to, d) {
454 t.Errorf("[Test Case %v] Wrong to. Expected: %s, Got: %s", index, td.to, d)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200455 }
456
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200457 d = dereferenceAddressList(e.Cc)
458 if !assertAddressListEq(td.cc, d) {
459 t.Errorf("[Test Case %v] Wrong cc. Expected: %s, Got: %s", index, td.cc, d)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200460 }
461
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200462 d = dereferenceAddressList(e.Bcc)
463 if !assertAddressListEq(td.bcc, d) {
464 t.Errorf("[Test Case %v] Wrong bcc. Expected: %s, Got: %s", index, td.bcc, d)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200465 }
466
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200467 if td.resentMessageID != e.ResentMessageID {
468 t.Errorf("[Test Case %v] Wrong resent messageID. Expected: '%s', Got: '%s'", index, td.resentMessageID, e.ResentMessageID)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200469 }
470
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200471 if !td.resentDate.Equal(e.ResentDate) && !td.resentDate.IsZero() && !e.ResentDate.IsZero() {
472 t.Errorf("[Test Case %v] Wrong resent date. Expected: %v, Got: %v", index, td.resentDate, e.ResentDate)
473 }
474
475 d = dereferenceAddressList(e.ResentFrom)
476 if !assertAddressListEq(td.resentFrom, d) {
477 t.Errorf("[Test Case %v] Wrong resent from. Expected: %s, Got: %s", index, td.resentFrom, d)
478 }
479
480 var resentSender mail.Address
481 if e.ResentSender != nil {
482 resentSender = *e.ResentSender
483 }
484 if td.resentSender != resentSender {
485 t.Errorf("[Test Case %v] Wrong resent sender. Expected: %s, Got: %s", index, td.resentSender, resentSender)
486 }
487
488 d = dereferenceAddressList(e.ResentTo)
489 if !assertAddressListEq(td.resentTo, d) {
490 t.Errorf("[Test Case %v] Wrong resent to. Expected: %s, Got: %s", index, td.resentTo, d)
491 }
492
493 d = dereferenceAddressList(e.ResentCc)
494 if !assertAddressListEq(td.resentCc, d) {
495 t.Errorf("[Test Case %v] Wrong resent cc. Expected: %s, Got: %s", index, td.resentCc, d)
496 }
497
498 d = dereferenceAddressList(e.ResentBcc)
499 if !assertAddressListEq(td.resentBcc, d) {
500 t.Errorf("[Test Case %v] Wrong resent bcc. Expected: %s, Got: %s", index, td.resentBcc, d)
501 }
502
503 if !assertSliceEq(td.inReplyTo, e.InReplyTo) {
504 t.Errorf("[Test Case %v] Wrong in reply to. Expected: %s, Got: %s", index, td.inReplyTo, e.InReplyTo)
505 }
506
507 if !assertSliceEq(td.references, e.References) {
508 t.Errorf("[Test Case %v] Wrong references. Expected: %s, Got: %s", index, td.references, e.References)
509 }
510
511 d = dereferenceAddressList(e.ReplyTo)
512 if !assertAddressListEq(td.replyTo, d) {
513 t.Errorf("[Test Case %v] Wrong reply to. Expected: %s, Got: %s", index, td.replyTo, d)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200514 }
515
516 if td.htmlBody != e.HTMLBody {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200517 t.Errorf("[Test Case %v] Wrong html body. Expected: '%s', Got: '%s'", index, td.htmlBody, e.HTMLBody)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200518 }
519
520 if td.textBody != e.TextBody {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200521 t.Errorf("[Test Case %v] Wrong text body. Expected: '%s', Got: '%s'", index, td.textBody, e.TextBody)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200522 }
523
Dusan Kasan17e497e2017-04-10 22:44:22 +0200524 if len(td.attachments) != len(e.Attachments) {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200525 t.Errorf("[Test Case %v] Incorrect number of attachments! Expected: %v, Got: %v.", index, len(td.attachments), len(e.Attachments))
Dusan Kasan17e497e2017-04-10 22:44:22 +0200526 } else {
527 attachs := e.Attachments[:]
528
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200529 for _, ad := range td.attachments {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200530 found := false
531
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200532 for i, ra := range attachs {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200533 b, err := ioutil.ReadAll(ra.Data)
534 if err != nil {
535 t.Error(err)
536 }
537
Dusan Kasan3325e732020-04-04 11:46:04 +0200538 if ra.Filename == ad.filename && string(b) == ad.data && ra.ContentType == ad.contentType {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200539 found = true
540 attachs = append(attachs[:i], attachs[i+1:]...)
541 }
542 }
543
544 if !found {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200545 t.Errorf("[Test Case %v] Attachment not found: %s", index, ad.filename)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200546 }
547 }
548
549 if len(attachs) != 0 {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200550 t.Errorf("[Test Case %v] Email contains %v unexpected attachments: %v", index, len(attachs), attachs)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200551 }
552 }
553
554 if len(td.embeddedFiles) != len(e.EmbeddedFiles) {
Dusan Kasan1a966482017-04-18 10:45:25 +0200555 t.Errorf("[Test Case %v] Incorrect number of embedded files! Expected: %v, Got: %v.", index, len(td.embeddedFiles), len(e.EmbeddedFiles))
Dusan Kasan17e497e2017-04-10 22:44:22 +0200556 } else {
557 embeds := e.EmbeddedFiles[:]
558
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200559 for _, ad := range td.embeddedFiles {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200560 found := false
561
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200562 for i, ra := range embeds {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200563 b, err := ioutil.ReadAll(ra.Data)
564 if err != nil {
565 t.Error(err)
566 }
567
568 encoded := base64.StdEncoding.EncodeToString(b)
569
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200570 if ra.CID == ad.cid && encoded == ad.base64data && ra.ContentType == ad.contentType {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200571 found = true
572 embeds = append(embeds[:i], embeds[i+1:]...)
573 }
574 }
575
576 if !found {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200577 t.Errorf("[Test Case %v] Embedded file not found: %s", index, ad.cid)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200578 }
579 }
580
581 if len(embeds) != 0 {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200582 t.Errorf("[Test Case %v] Email contains %v unexpected embedded files: %v", index, len(embeds), embeds)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200583 }
584 }
585 }
586}
587
588func parseDate(in string) time.Time {
589 out, err := time.Parse(time.RFC1123Z, in)
590 if err != nil {
591 panic(err)
592 }
593
594 return out
595}
596
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200597type attachmentData struct {
Edwin Lovef8d8da22019-04-02 14:07:32 +0100598 filename string
599 contentType string
Dusan Kasan3325e732020-04-04 11:46:04 +0200600 data string
Dusan Kasan17e497e2017-04-10 22:44:22 +0200601}
602
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200603type embeddedFileData struct {
604 cid string
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200605 contentType string
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200606 base64data string
Dusan Kasan17e497e2017-04-10 22:44:22 +0200607}
608
609func assertSliceEq(a, b []string) bool {
610 if len(a) == len(b) && len(a) == 0 {
611 return true
612 }
613
614 if a == nil && b == nil {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200615 return true
Dusan Kasan17e497e2017-04-10 22:44:22 +0200616 }
617
618 if a == nil || b == nil {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200619 return false
Dusan Kasan17e497e2017-04-10 22:44:22 +0200620 }
621
622 if len(a) != len(b) {
623 return false
624 }
625
626 for i := range a {
627 if a[i] != b[i] {
628 return false
629 }
630 }
631
632 return true
633}
634
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200635func assertAddressListEq(a, b []mail.Address) bool {
636 if len(a) == len(b) && len(a) == 0 {
637 return true
638 }
639
640 if a == nil && b == nil {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200641 return true
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200642 }
643
644 if a == nil || b == nil {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200645 return false
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200646 }
647
648 if len(a) != len(b) {
649 return false
650 }
651
652 for i := range a {
653 if a[i] != b[i] {
654 return false
655 }
656 }
657
658 return true
659}
660
661func dereferenceAddressList(al []*mail.Address) (result []mail.Address) {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200662 for _, a := range al {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200663 result = append(result, *a)
664 }
665
666 return
667}
668
Dusan Kasan45ca2642017-04-18 10:39:35 +0200669var data1 = `From: =?UTF-8?Q?Peter_Pahol=C3=ADk?= <peter.paholik@gmail.com>
Dusan Kasan17e497e2017-04-10 22:44:22 +0200670Date: Fri, 7 Apr 2017 09:17:26 +0200
671Message-ID: <CACtgX4kNXE7T5XKSKeH_zEcfUUmf2vXVASxYjaaK9cCn-3zb_g@mail.gmail.com>
Dusan Kasanf4376a62017-05-23 21:03:55 +0200672Subject: =?UTF-8?Q?Peter_Pahol=C3=ADk?=
Dusan Kasan17e497e2017-04-10 22:44:22 +0200673To: dusan@kasan.sk
674Content-Type: multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf
675
676--f403045f1dcc043a44054c8e6bbf
677Content-Type: multipart/alternative; boundary=f403045f1dcc043a3f054c8e6bbd
678
679--f403045f1dcc043a3f054c8e6bbd
680Content-Type: text/plain; charset=UTF-8
681
682
683
684--f403045f1dcc043a3f054c8e6bbd
685Content-Type: text/html; charset=UTF-8
686
687<div dir="ltr"><br></div>
688
689--f403045f1dcc043a3f054c8e6bbd--
690--f403045f1dcc043a44054c8e6bbf
Dusan Kasan3325e732020-04-04 11:46:04 +0200691Content-Type: application/json;
Dusan Kasan17e497e2017-04-10 22:44:22 +0200692 name="=?UTF-8?Q?Peter_Paholi=CC=81k_1?=
Dusan Kasan3325e732020-04-04 11:46:04 +0200693 =?UTF-8?Q?_4_2017_2017=2D04=2D07=2Ejson?="
Dusan Kasan17e497e2017-04-10 22:44:22 +0200694Content-Disposition: attachment;
695 filename="=?UTF-8?Q?Peter_Paholi=CC=81k_1?=
Dusan Kasan3325e732020-04-04 11:46:04 +0200696 =?UTF-8?Q?_4_2017_2017=2D04=2D07=2Ejson?="
Dusan Kasan17e497e2017-04-10 22:44:22 +0200697Content-Transfer-Encoding: base64
698X-Attachment-Id: f_j17i0f0d0
699
Dusan Kasan3325e732020-04-04 11:46:04 +0200700WzEsIDIsIDNd
Dusan Kasan17e497e2017-04-10 22:44:22 +0200701--f403045f1dcc043a44054c8e6bbf--
702`
703
Dusan Kasan45ca2642017-04-18 10:39:35 +0200704var data2 = `Subject: Re: Test Subject 2
Dusan Kasan17e497e2017-04-10 22:44:22 +0200705To: info@receiver.com
706References: <2f6b7595-c01e-46e5-42bc-f263e1c4282d@receiver.com>
707 <9ff38d03-c4ab-89b7-9328-e99d5e24e3ba@domain.com>
708Cc: Cc Man <ccman@gmail.com>
709From: Sender Man <sender@domain.com>
710Message-ID: <0e9a21b4-01dc-e5c1-dcd6-58ce5aa61f4f@receiver.com>
711Date: Fri, 7 Apr 2017 12:59:55 +0200
712User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:45.0)
713 Gecko/20100101 Thunderbird/45.8.0
714MIME-Version: 1.0
715In-Reply-To: <9ff38d03-c4ab-89b7-9328-e99d5e24e3ba@receiver.eu>
716Content-Type: multipart/alternative;
717 boundary="------------C70C0458A558E585ACB75FB4"
718
719This is a multi-part message in MIME format.
720--------------C70C0458A558E585ACB75FB4
721Content-Type: text/plain; charset=utf-8; format=flowed
722Content-Transfer-Encoding: 8bit
723
724First level
725> Second level
726>> Third level
727>
728
729
730--------------C70C0458A558E585ACB75FB4
731Content-Type: multipart/related;
732 boundary="------------5DB4A1356834BB602A5F88B2"
733
734
735--------------5DB4A1356834BB602A5F88B2
736Content-Type: text/html; charset=utf-8
737Content-Transfer-Encoding: 8bit
738
739<html>data<img src="part2.9599C449.04E5EC81@develhell.com"/></html>
740
741--------------5DB4A1356834BB602A5F88B2
742Content-Type: image/png
743Content-Transfer-Encoding: base64
744Content-ID: <part2.9599C449.04E5EC81@develhell.com>
745
746iVBORw0KGgoAAAANSUhEUgAAAQEAAAAYCAIAAAB1IN9NAAAACXBIWXMAAAsTAAALEwEAmpwY
747YKUKF+Os3baUndC0pDnwNAmLy1SUr2Gw0luxQuV/AwC6cEhVV5VRrwAAAABJRU5ErkJggg==
748--------------5DB4A1356834BB602A5F88B2
749
750--------------C70C0458A558E585ACB75FB4--
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200751`
752
Dusan Kasane97c2f22020-04-04 10:50:00 +0200753var textPlainInMultipart = `From: Rares <rares@example.com>
Maya Rashishbae116e2019-06-10 12:15:25 +0300754Date: Thu, 2 May 2019 11:25:35 +0300
755Subject: Re: kern/54143 (virtualbox)
756To: bugs@example.com
757Content-Type: multipart/mixed; boundary="0000000000007e2bb40587e36196"
758
759--0000000000007e2bb40587e36196
760Content-Type: text/plain; charset="UTF-8"
761
762plain text part
763--0000000000007e2bb40587e36196--
764`
765
k-yomo2e670d92020-04-30 22:42:48 +0900766var textHTMLInMultipart = `From: Rares <rares@example.com>
767Date: Thu, 2 May 2019 11:25:35 +0300
768Subject: Re: kern/54143 (virtualbox)
769To: bugs@example.com
770Content-Type: multipart/mixed; boundary="0000000000007e2bb40587e36196"
771
772--0000000000007e2bb40587e36196
773Content-Type: text/html; charset="UTF-8"
774
775<div dir="ltr"><div>html text part</div><div><br></div><div><br><br></div></div>
776
777--0000000000007e2bb40587e36196--
778`
779
Dusan Kasan45ca2642017-04-18 10:39:35 +0200780var rfc5322exampleA11 = `From: John Doe <jdoe@machine.example>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200781Sender: Michael Jones <mjones@machine.example>
782To: Mary Smith <mary@example.net>
783Subject: Saying Hello
784Date: Fri, 21 Nov 1997 09:55:06 -0600
785Message-ID: <1234@local.machine.example>
786
787This is a message just to say hello.
788So, "Hello".
789`
790
Dusan Kasan45ca2642017-04-18 10:39:35 +0200791var rfc5322exampleA12 = `From: "Joe Q. Public" <john.q.public@example.com>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200792To: Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>
793Cc: <boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>
794Date: Tue, 1 Jul 2003 10:52:37 +0200
795Message-ID: <5678.21-Nov-1997@example.com>
796
797Hi everyone.
798`
799
Dusan Kasan88226cf2020-04-04 11:13:06 +0200800var rfc5322exampleA12WithTimezone = `From: "Joe Q. Public" <john.q.public@example.com>
801To: Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>
802Cc: <boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>
Edwin Love33953e52019-04-02 15:29:22 +0100803Date: Tue, 1 Jul 2003 10:52:37 +0200 (GMT)
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200804Message-ID: <5678.21-Nov-1997@example.com>
805
806Hi everyone.
807`
808
809//todo: not yet implemented in net/mail
810//once there is support for this, add it
Dusan Kasan45ca2642017-04-18 10:39:35 +0200811var rfc5322exampleA13 = `From: Pete <pete@silly.example>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200812To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;
813Cc: Undisclosed recipients:;
814Date: Thu, 13 Feb 1969 23:32:54 -0330
815Message-ID: <testabcd.1234@silly.example>
816
817Testing.
818`
819
820//we skipped the first message bcause it's the same as A 1.1
Dusan Kasan45ca2642017-04-18 10:39:35 +0200821var rfc5322exampleA2a = `From: Mary Smith <mary@example.net>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200822To: John Doe <jdoe@machine.example>
823Reply-To: "Mary Smith: Personal Account" <smith@home.example>
824Subject: Re: Saying Hello
825Date: Fri, 21 Nov 1997 10:01:10 -0600
826Message-ID: <3456@example.net>
827In-Reply-To: <1234@local.machine.example>
828References: <1234@local.machine.example>
829
830This is a reply to your hello.
831`
832
Dusan Kasan45ca2642017-04-18 10:39:35 +0200833var rfc5322exampleA2b = `To: "Mary Smith: Personal Account" <smith@home.example>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200834From: John Doe <jdoe@machine.example>
835Subject: Re: Saying Hello
836Date: Fri, 21 Nov 1997 11:00:00 -0600
837Message-ID: <abcd.1234@local.machine.test>
838In-Reply-To: <3456@example.net>
839References: <1234@local.machine.example> <3456@example.net>
840
841This is a reply to your reply.
842`
843
Dusan Kasan45ca2642017-04-18 10:39:35 +0200844var rfc5322exampleA3 = `Resent-From: Mary Smith <mary@example.net>
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200845Resent-To: Jane Brown <j-brown@other.example>
846Resent-Date: Mon, 24 Nov 1997 14:22:01 -0800
847Resent-Message-ID: <78910@example.net>
848From: John Doe <jdoe@machine.example>
849To: Mary Smith <mary@example.net>
850Subject: Saying Hello
851Date: Fri, 21 Nov 1997 09:55:06 -0600
852Message-ID: <1234@local.machine.example>
853
854This is a message just to say hello.
855So, "Hello".`
856
Dusan Kasan45ca2642017-04-18 10:39:35 +0200857var rfc5322exampleA4 = `Received: from x.y.test
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200858 by example.net
859 via TCP
860 with ESMTP
861 id ABC12345
862 for <mary@example.net>; 21 Nov 1997 10:05:43 -0600
863Received: from node.example by x.y.test; 21 Nov 1997 10:01:22 -0600
864From: John Doe <jdoe@node.example>
865To: Mary Smith <mary@example.net>
866Subject: Saying Hello
867Date: Fri, 21 Nov 1997 09:55:06 -0600
868Message-ID: <1234@local.node.example>
869
870This is a message just to say hello.
871So, "Hello".`
Dusan Kasan428369f2020-02-24 00:47:31 +0100872
873var imageContentExample = `From: John Doe <jdoe@machine.example>
874Sender: Michael Jones <mjones@machine.example>
875To: Mary Smith <mary@example.net>
876Subject: Saying Hello
877Date: Fri, 21 Nov 1997 09:55:06 -0600
878Message-ID: <1234@local.machine.example>
879Content-Type: image/jpeg;
880 x-unix-mode=0644;
881 name="image.gif"
882Content-Transfer-Encoding: base64
883
884R0lGODlhAQE7`
Obi Symons333687f2020-04-05 14:31:35 +1000885
886var multipartRelatedExample = `MIME-Version: 1.0
887From: John Doe <jdoe@machine.example>
888Sender: Michael Jones <mjones@machine.example>
889To: Mary Smith <mary@example.net>
890Subject: Saying Hello
891Date: Fri, 21 Nov 1997 09:55:06 -0600
892Message-ID: <1234@local.machine.example>
893Subject: ooops
894To: test@example.rocks
895Content-Type: multipart/related; boundary="000000000000ab2e2205a26de587"
896
897--000000000000ab2e2205a26de587
898Content-Type: multipart/alternative; boundary="000000000000ab2e1f05a26de586"
899
900--000000000000ab2e1f05a26de586
901Content-Type: text/plain; charset="UTF-8"
902
903Time for the egg.
904
905--000000000000ab2e1f05a26de586
906Content-Type: text/html; charset="UTF-8"
907
908<div dir="ltr"><div>Time for the egg.</div><div><br></div><div><br><br></div></div>
909
910--000000000000ab2e1f05a26de586--
911
912
913--000000000000ab2e2205a26de587--
Obi Symons5bf5e012020-04-05 14:36:29 +1000914`
Dusan Kasan3325e732020-04-04 11:46:04 +0200915var attachment7bit = `From: =?UTF-8?Q?Peter_Foobar?= <peter.foobar@gmail.com>
916Date: Tue, 2 Apr 2019 11:12:26 +0000
917Message-ID: <CACtgX4kNXE7T5XKSKeH_zEcfUUmf2vXVASxYjaaK9cCn-3zb_g@mail.gmail.com>
918Subject: =?UTF-8?Q?Peter_Foobar?=
919To: dusan@kasan.sk
920Content-Type: multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf
921
922--f403045f1dcc043a44054c8e6bbf
923Content-Type: multipart/alternative; boundary=f403045f1dcc043a3f054c8e6bbd
924
925--f403045f1dcc043a3f054c8e6bbd
926Content-Type: text/plain; charset=UTF-8
927
928
929
930--f403045f1dcc043a3f054c8e6bbd
931Content-Type: text/html; charset=UTF-8
932
933<div dir="ltr"><br></div>
934
935--f403045f1dcc043a3f054c8e6bbd--
936--f403045f1dcc043a44054c8e6bbf
937Content-Type: application/csv;
938 name="unencoded.csv"
939Content-Transfer-Encoding: 7bit
940Content-Disposition: attachment;
941 filename="unencoded.csv"
942
943
944"Some", "Data", "In", "Csv", "Format"
945"Foo", "Bar", "Baz", "Bum", "Poo"
946
947--f403045f1dcc043a44054c8e6bbf--
948`