Source file
src/net/mail/message_test.go
1
2
3
4
5 package mail
6
7 import (
8 "bytes"
9 "io"
10 "mime"
11 "reflect"
12 "strings"
13 "testing"
14 "time"
15 )
16
17 var parseTests = []struct {
18 in string
19 header Header
20 body string
21 }{
22 {
23
24 in: `From: John Doe <jdoe@machine.example>
25 To: Mary Smith <mary@example.net>
26 Subject: Saying Hello
27 Date: Fri, 21 Nov 1997 09:55:06 -0600
28 Message-ID: <1234@local.machine.example>
29
30 This is a message just to say hello.
31 So, "Hello".
32 `,
33 header: Header{
34 "From": []string{"John Doe <jdoe@machine.example>"},
35 "To": []string{"Mary Smith <mary@example.net>"},
36 "Subject": []string{"Saying Hello"},
37 "Date": []string{"Fri, 21 Nov 1997 09:55:06 -0600"},
38 "Message-Id": []string{"<1234@local.machine.example>"},
39 },
40 body: "This is a message just to say hello.\nSo, \"Hello\".\n",
41 },
42 }
43
44 func TestParsing(t *testing.T) {
45 for i, test := range parseTests {
46 msg, err := ReadMessage(bytes.NewBuffer([]byte(test.in)))
47 if err != nil {
48 t.Errorf("test #%d: Failed parsing message: %v", i, err)
49 continue
50 }
51 if !headerEq(msg.Header, test.header) {
52 t.Errorf("test #%d: Incorrectly parsed message header.\nGot:\n%+v\nWant:\n%+v",
53 i, msg.Header, test.header)
54 }
55 body, err := io.ReadAll(msg.Body)
56 if err != nil {
57 t.Errorf("test #%d: Failed reading body: %v", i, err)
58 continue
59 }
60 bodyStr := string(body)
61 if bodyStr != test.body {
62 t.Errorf("test #%d: Incorrectly parsed message body.\nGot:\n%+v\nWant:\n%+v",
63 i, bodyStr, test.body)
64 }
65 }
66 }
67
68 func headerEq(a, b Header) bool {
69 if len(a) != len(b) {
70 return false
71 }
72 for k, as := range a {
73 bs, ok := b[k]
74 if !ok {
75 return false
76 }
77 if !reflect.DeepEqual(as, bs) {
78 return false
79 }
80 }
81 return true
82 }
83
84 func TestDateParsing(t *testing.T) {
85 tests := []struct {
86 dateStr string
87 exp time.Time
88 }{
89
90 {
91 "Fri, 21 Nov 1997 09:55:06 -0600",
92 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
93 },
94
95
96 {
97 "21 Nov 97 09:55:06 GMT",
98 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)),
99 },
100
101 {
102 "Fri, 21 Nov 1997 09:55:06 -0600 (MDT)",
103 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
104 },
105 {
106 "Thu, 20 Nov 1997 09:55:06 -0600 (MDT)",
107 time.Date(1997, 11, 20, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
108 },
109 {
110 "Thu, 20 Nov 1997 09:55:06 GMT (GMT)",
111 time.Date(1997, 11, 20, 9, 55, 6, 0, time.UTC),
112 },
113 {
114 "Fri, 21 Nov 1997 09:55:06 +1300 (TOT)",
115 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", +13*60*60)),
116 },
117 }
118 for _, test := range tests {
119 hdr := Header{
120 "Date": []string{test.dateStr},
121 }
122 date, err := hdr.Date()
123 if err != nil {
124 t.Errorf("Header(Date: %s).Date(): %v", test.dateStr, err)
125 } else if !date.Equal(test.exp) {
126 t.Errorf("Header(Date: %s).Date() = %+v, want %+v", test.dateStr, date, test.exp)
127 }
128
129 date, err = ParseDate(test.dateStr)
130 if err != nil {
131 t.Errorf("ParseDate(%s): %v", test.dateStr, err)
132 } else if !date.Equal(test.exp) {
133 t.Errorf("ParseDate(%s) = %+v, want %+v", test.dateStr, date, test.exp)
134 }
135 }
136 }
137
138 func TestDateParsingCFWS(t *testing.T) {
139 tests := []struct {
140 dateStr string
141 exp time.Time
142 valid bool
143 }{
144
145 {
146 " ",
147
148 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
149 false,
150 },
151
152 {
153 " Fri, 21 Nov 1997 09:55:06 -0600",
154 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
155 true,
156 },
157 {
158 "21 Nov 1997 09:55:06 -0600",
159 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
160 true,
161 },
162 {
163 "Fri 21 Nov 1997 09:55:06 -0600",
164 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
165 false,
166 },
167
168 {
169 "Fri, 21 Nov 1997 09:55:06 -0600",
170 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
171 true,
172 },
173
174 {
175 "Fri, 21 Nov 1997 09:55:06 -0600",
176 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
177 true,
178 },
179
180 {
181 "Fri, 21 Nov 1997 09:55:06 CST",
182 time.Time{},
183 true,
184 },
185
186 {
187 "Fri, 21 Nov 1997 09:55:06 CST (no leading FWS and a trailing CRLF) \r\n",
188 time.Time{},
189 true,
190 },
191
192 {
193 "Fri, 21 Nov 1997 09:55:06 -0600 (MDT and non-US-ASCII signs éèç )",
194 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
195 true,
196 },
197
198
199 {
200 "Fri, 21 Nov 1997 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
201 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
202 true,
203 },
204
205 {
206 "Fri, 21 Nov 1997 \r 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
207 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
208 false,
209 },
210
211 {
212 "Fri, 21 Nov 199\r\n7 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
213 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
214 true,
215 },
216
217 {
218 "Fri, 21 Nov 1997 ù 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
219 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
220 false,
221 },
222
223 {
224 "Fri, 21 Nov () 1997 09:55:06 -0600 \r\n (thisisa(valid)cfws) \t ",
225 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
226 false,
227 },
228
229 {
230 "Fri, 21 Nov 1997 09:55:06 -060 \r\n (Thisisa(valid)cfws) \t ",
231 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
232 false,
233 },
234
235 {
236 "Fri, 21 1997 09:55:06 -0600",
237 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
238 false,
239 },
240
241 {
242 "Fri, 21 OCT 1997 09:55:06 CST",
243 time.Time{},
244 false,
245 },
246
247 {
248 "Fri, 21 Nov 1997 09:55:06 -060",
249 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
250 false,
251 },
252
253 {
254 "Fri, 21 1997 09:55:06 GT",
255 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
256 false,
257 },
258
259
260 {
261 "Tue, 26 May 2020 14:04:40 GMT",
262 time.Date(2020, 05, 26, 14, 04, 40, 0, time.UTC),
263 true,
264 },
265 {
266 "Tue, 26 May 2020 14:04:40 UT",
267 time.Date(2020, 05, 26, 14, 04, 40, 0, time.UTC),
268 true,
269 },
270 {
271 "Thu, 21 May 2020 14:04:40 UT",
272 time.Date(2020, 05, 21, 14, 04, 40, 0, time.UTC),
273 true,
274 },
275 {
276 "Tue, 26 May 2020 14:04:40 XT",
277 time.Date(2020, 05, 26, 14, 04, 40, 0, time.UTC),
278 false,
279 },
280 {
281 "Thu, 21 May 2020 14:04:40 XT",
282 time.Date(2020, 05, 21, 14, 04, 40, 0, time.UTC),
283 false,
284 },
285 {
286 "Thu, 21 May 2020 14:04:40 UTC",
287 time.Date(2020, 05, 21, 14, 04, 40, 0, time.UTC),
288 true,
289 },
290 {
291 "Fri, 21 Nov 1997 09:55:06 GMT (GMT)",
292 time.Date(1997, 11, 21, 9, 55, 6, 0, time.UTC),
293 true,
294 },
295 }
296 for _, test := range tests {
297 hdr := Header{
298 "Date": []string{test.dateStr},
299 }
300 date, err := hdr.Date()
301 if err != nil && test.valid {
302 t.Errorf("Header(Date: %s).Date(): %v", test.dateStr, err)
303 } else if err == nil && test.exp.IsZero() {
304
305
306 } else if err == nil && !date.Equal(test.exp) && test.valid {
307 t.Errorf("Header(Date: %s).Date() = %+v, want %+v", test.dateStr, date, test.exp)
308 } else if err == nil && !test.valid {
309 t.Errorf("Header(Date: %s).Date() did not return an error but %v", test.dateStr, date)
310 }
311
312 date, err = ParseDate(test.dateStr)
313 if err != nil && test.valid {
314 t.Errorf("ParseDate(%s): %v", test.dateStr, err)
315 } else if err == nil && test.exp.IsZero() {
316
317
318 } else if err == nil && !test.valid {
319 t.Errorf("ParseDate(%s) did not return an error but %v", test.dateStr, date)
320 } else if err == nil && test.valid && !date.Equal(test.exp) {
321 t.Errorf("ParseDate(%s) = %+v, want %+v", test.dateStr, date, test.exp)
322 }
323 }
324 }
325
326 func TestAddressParsingError(t *testing.T) {
327 mustErrTestCases := [...]struct {
328 text string
329 wantErrText string
330 }{
331 0: {"=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown@gmail.com>", "charset not supported"},
332 1: {"a@gmail.com b@gmail.com", "expected single address"},
333 2: {string([]byte{0xed, 0xa0, 0x80}) + " <micro@example.net>", "invalid utf-8 in address"},
334 3: {"\"" + string([]byte{0xed, 0xa0, 0x80}) + "\" <half-surrogate@example.com>", "invalid utf-8 in quoted-string"},
335 4: {"\"\\" + string([]byte{0x80}) + "\" <escaped-invalid-unicode@example.net>", "invalid utf-8 in quoted-string"},
336 5: {"\"\x00\" <null@example.net>", "bad character in quoted-string"},
337 6: {"\"\\\x00\" <escaped-null@example.net>", "bad character in quoted-string"},
338 7: {"John Doe", "no angle-addr"},
339 8: {`<jdoe#machine.example>`, "missing @ in addr-spec"},
340 9: {`John <middle> Doe <jdoe@machine.example>`, "missing @ in addr-spec"},
341 10: {"cfws@example.com (", "misformatted parenthetical comment"},
342 11: {"empty group: ;", "empty group"},
343 12: {"root group: embed group: null@example.com;", "no angle-addr"},
344 13: {"group not closed: null@example.com", "expected comma"},
345 14: {"group: first@example.com, second@example.com;", "group with multiple addresses"},
346 15: {"john.doe", "missing '@' or angle-addr"},
347 16: {"john.doe@", "no angle-addr"},
348 17: {"John Doe@foo.bar", "no angle-addr"},
349 }
350
351 for i, tc := range mustErrTestCases {
352 _, err := ParseAddress(tc.text)
353 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
354 t.Errorf(`mail.ParseAddress(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
355 }
356 }
357
358 t.Run("CustomWordDecoder", func(t *testing.T) {
359 p := &AddressParser{WordDecoder: &mime.WordDecoder{}}
360 for i, tc := range mustErrTestCases {
361 _, err := p.Parse(tc.text)
362 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
363 t.Errorf(`p.Parse(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
364 }
365 }
366 })
367
368 }
369
370 func TestAddressParsing(t *testing.T) {
371 tests := []struct {
372 addrsStr string
373 exp []*Address
374 }{
375
376 {
377 `jdoe@machine.example`,
378 []*Address{{
379 Address: "jdoe@machine.example",
380 }},
381 },
382
383 {
384 `John Doe <jdoe@machine.example>`,
385 []*Address{{
386 Name: "John Doe",
387 Address: "jdoe@machine.example",
388 }},
389 },
390
391 {
392 `"Joe Q. Public" <john.q.public@example.com>`,
393 []*Address{{
394 Name: "Joe Q. Public",
395 Address: "john.q.public@example.com",
396 }},
397 },
398 {
399 `"John (middle) Doe" <jdoe@machine.example>`,
400 []*Address{{
401 Name: "John (middle) Doe",
402 Address: "jdoe@machine.example",
403 }},
404 },
405 {
406 `John (middle) Doe <jdoe@machine.example>`,
407 []*Address{{
408 Name: "John (middle) Doe",
409 Address: "jdoe@machine.example",
410 }},
411 },
412 {
413 `John !@M@! Doe <jdoe@machine.example>`,
414 []*Address{{
415 Name: "John !@M@! Doe",
416 Address: "jdoe@machine.example",
417 }},
418 },
419 {
420 `"John <middle> Doe" <jdoe@machine.example>`,
421 []*Address{{
422 Name: "John <middle> Doe",
423 Address: "jdoe@machine.example",
424 }},
425 },
426 {
427 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
428 []*Address{
429 {
430 Name: "Mary Smith",
431 Address: "mary@x.test",
432 },
433 {
434 Address: "jdoe@example.org",
435 },
436 {
437 Name: "Who?",
438 Address: "one@y.test",
439 },
440 },
441 },
442 {
443 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
444 []*Address{
445 {
446 Address: "boss@nil.test",
447 },
448 {
449 Name: `Giant; "Big" Box`,
450 Address: "sysservices@example.net",
451 },
452 },
453 },
454
455 {
456 `Joe Q. Public <john.q.public@example.com>`,
457 []*Address{{
458 Name: "Joe Q. Public",
459 Address: "john.q.public@example.com",
460 }},
461 },
462
463 {
464 `group1: groupaddr1@example.com;`,
465 []*Address{
466 {
467 Name: "",
468 Address: "groupaddr1@example.com",
469 },
470 },
471 },
472 {
473 `empty group: ;`,
474 []*Address(nil),
475 },
476 {
477 `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
478 []*Address{
479 {
480 Name: "Ed Jones",
481 Address: "c@a.test",
482 },
483 {
484 Name: "",
485 Address: "joe@where.test",
486 },
487 {
488 Name: "John",
489 Address: "jdoe@one.test",
490 },
491 },
492 },
493
494 {
495 ` , joe@where.test,,John <jdoe@one.test>,`,
496 []*Address{
497 {
498 Name: "",
499 Address: "joe@where.test",
500 },
501 {
502 Name: "John",
503 Address: "jdoe@one.test",
504 },
505 },
506 },
507 {
508 ` , joe@where.test,,John <jdoe@one.test>,,`,
509 []*Address{
510 {
511 Name: "",
512 Address: "joe@where.test",
513 },
514 {
515 Name: "John",
516 Address: "jdoe@one.test",
517 },
518 },
519 },
520 {
521 `Group1: <addr1@example.com>;, Group 2: addr2@example.com;, John <addr3@example.com>`,
522 []*Address{
523 {
524 Name: "",
525 Address: "addr1@example.com",
526 },
527 {
528 Name: "",
529 Address: "addr2@example.com",
530 },
531 {
532 Name: "John",
533 Address: "addr3@example.com",
534 },
535 },
536 },
537
538 {
539 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
540 []*Address{
541 {
542 Name: `Jörg Doe`,
543 Address: "joerg@example.com",
544 },
545 },
546 },
547
548 {
549 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
550 []*Address{
551 {
552 Name: `Jorg Doe`,
553 Address: "joerg@example.com",
554 },
555 },
556 },
557
558 {
559 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
560 []*Address{
561 {
562 Name: `Jörg Doe`,
563 Address: "joerg@example.com",
564 },
565 },
566 },
567
568 {
569 `=?utf-8?q?J=C3=B6rg?= =?utf-8?q?Doe?= <joerg@example.com>`,
570 []*Address{
571 {
572 Name: `JörgDoe`,
573 Address: "joerg@example.com",
574 },
575 },
576 },
577
578 {
579 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
580 []*Address{
581 {
582 Name: `André Pirard`,
583 Address: "PIRARD@vm1.ulg.ac.be",
584 },
585 },
586 },
587
588 {
589 `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
590 []*Address{
591 {
592 Name: `Jörg`,
593 Address: "joerg@example.com",
594 },
595 },
596 },
597
598 {
599 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
600 []*Address{
601 {
602 Name: `Jörg`,
603 Address: "joerg@example.com",
604 },
605 },
606 },
607
608 {
609 `Asem H. <noreply@example.com>`,
610 []*Address{
611 {
612 Name: `Asem H.`,
613 Address: "noreply@example.com",
614 },
615 },
616 },
617
618 {
619 `"Gø Pher" <gopher@example.com>`,
620 []*Address{
621 {
622 Name: `Gø Pher`,
623 Address: "gopher@example.com",
624 },
625 },
626 },
627
628 {
629 `µ <micro@example.com>`,
630 []*Address{
631 {
632 Name: `µ`,
633 Address: "micro@example.com",
634 },
635 },
636 },
637
638 {
639 `Micro <µ@example.com>`,
640 []*Address{
641 {
642 Name: `Micro`,
643 Address: "µ@example.com",
644 },
645 },
646 },
647
648 {
649 `Micro <micro@µ.example.com>`,
650 []*Address{
651 {
652 Name: `Micro`,
653 Address: "micro@µ.example.com",
654 },
655 },
656 },
657
658 {
659 `"" <emptystring@example.com>`,
660 []*Address{
661 {
662 Name: "",
663 Address: "emptystring@example.com",
664 },
665 },
666 },
667
668 {
669 `<cfws@example.com> (CFWS (cfws)) (another comment)`,
670 []*Address{
671 {
672 Name: "",
673 Address: "cfws@example.com",
674 },
675 },
676 },
677 {
678 `<cfws@example.com> () (another comment), <cfws2@example.com> (another)`,
679 []*Address{
680 {
681 Name: "",
682 Address: "cfws@example.com",
683 },
684 {
685 Name: "",
686 Address: "cfws2@example.com",
687 },
688 },
689 },
690
691 {
692 `john@example.com (John Doe)`,
693 []*Address{
694 {
695 Name: "John Doe",
696 Address: "john@example.com",
697 },
698 },
699 },
700
701 {
702 `John Doe <john@example.com> (Joey)`,
703 []*Address{
704 {
705 Name: "John Doe",
706 Address: "john@example.com",
707 },
708 },
709 },
710
711 {
712 `john@example.com(John Doe)`,
713 []*Address{
714 {
715 Name: "John Doe",
716 Address: "john@example.com",
717 },
718 },
719 },
720
721 {
722 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
723 []*Address{
724 {
725 Name: "Adam Sjøgren",
726 Address: "asjo@example.com",
727 },
728 },
729 },
730
731 {
732 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
733 []*Address{
734 {
735 Name: "Adam Sjøgren",
736 Address: "asjo@example.com",
737 },
738 },
739 },
740
741 {
742 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?= (Debian))`,
743 []*Address{
744 {
745 Name: "Adam Sjøgren (Debian)",
746 Address: "asjo@example.com",
747 },
748 },
749 },
750 }
751 for _, test := range tests {
752 if len(test.exp) == 1 {
753 addr, err := ParseAddress(test.addrsStr)
754 if err != nil {
755 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
756 continue
757 }
758 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
759 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
760 }
761 }
762
763 addrs, err := ParseAddressList(test.addrsStr)
764 if err != nil {
765 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
766 continue
767 }
768 if !reflect.DeepEqual(addrs, test.exp) {
769 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
770 }
771 }
772 }
773
774 func TestAddressParser(t *testing.T) {
775 tests := []struct {
776 addrsStr string
777 exp []*Address
778 }{
779
780 {
781 `jdoe@machine.example`,
782 []*Address{{
783 Address: "jdoe@machine.example",
784 }},
785 },
786
787 {
788 `John Doe <jdoe@machine.example>`,
789 []*Address{{
790 Name: "John Doe",
791 Address: "jdoe@machine.example",
792 }},
793 },
794
795 {
796 `"Joe Q. Public" <john.q.public@example.com>`,
797 []*Address{{
798 Name: "Joe Q. Public",
799 Address: "john.q.public@example.com",
800 }},
801 },
802 {
803 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
804 []*Address{
805 {
806 Name: "Mary Smith",
807 Address: "mary@x.test",
808 },
809 {
810 Address: "jdoe@example.org",
811 },
812 {
813 Name: "Who?",
814 Address: "one@y.test",
815 },
816 },
817 },
818 {
819 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
820 []*Address{
821 {
822 Address: "boss@nil.test",
823 },
824 {
825 Name: `Giant; "Big" Box`,
826 Address: "sysservices@example.net",
827 },
828 },
829 },
830
831 {
832 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
833 []*Address{
834 {
835 Name: `Jörg Doe`,
836 Address: "joerg@example.com",
837 },
838 },
839 },
840
841 {
842 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
843 []*Address{
844 {
845 Name: `Jorg Doe`,
846 Address: "joerg@example.com",
847 },
848 },
849 },
850
851 {
852 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`,
853 []*Address{
854 {
855 Name: `Jörg Doe`,
856 Address: "joerg@example.com",
857 },
858 },
859 },
860
861 {
862 `=?windows-1252?q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
863 []*Address{
864 {
865 Name: `André Pirard`,
866 Address: "PIRARD@vm1.ulg.ac.be",
867 },
868 },
869 },
870
871 {
872 `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`,
873 []*Address{
874 {
875 Name: `Jörg`,
876 Address: "joerg@example.com",
877 },
878 },
879 },
880
881 {
882 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
883 []*Address{
884 {
885 Name: `Jörg`,
886 Address: "joerg@example.com",
887 },
888 },
889 },
890
891 {
892 `Asem H. <noreply@example.com>`,
893 []*Address{
894 {
895 Name: `Asem H.`,
896 Address: "noreply@example.com",
897 },
898 },
899 },
900 }
901
902 ap := AddressParser{WordDecoder: &mime.WordDecoder{
903 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
904 in, err := io.ReadAll(input)
905 if err != nil {
906 return nil, err
907 }
908
909 switch charset {
910 case "iso-8859-15":
911 in = bytes.ReplaceAll(in, []byte("\xf6"), []byte("ö"))
912 case "windows-1252":
913 in = bytes.ReplaceAll(in, []byte("\xe9"), []byte("é"))
914 }
915
916 return bytes.NewReader(in), nil
917 },
918 }}
919
920 for _, test := range tests {
921 if len(test.exp) == 1 {
922 addr, err := ap.Parse(test.addrsStr)
923 if err != nil {
924 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
925 continue
926 }
927 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
928 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
929 }
930 }
931
932 addrs, err := ap.ParseList(test.addrsStr)
933 if err != nil {
934 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
935 continue
936 }
937 if !reflect.DeepEqual(addrs, test.exp) {
938 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
939 }
940 }
941 }
942
943 func TestAddressString(t *testing.T) {
944 tests := []struct {
945 addr *Address
946 exp string
947 }{
948 {
949 &Address{Address: "bob@example.com"},
950 "<bob@example.com>",
951 },
952 {
953 &Address{Address: `my@idiot@address@example.com`},
954 `<"my@idiot@address"@example.com>`,
955 },
956 {
957 &Address{Address: ` @example.com`},
958 `<" "@example.com>`,
959 },
960 {
961 &Address{Name: "Bob", Address: "bob@example.com"},
962 `"Bob" <bob@example.com>`,
963 },
964 {
965
966 &Address{Name: "Böb", Address: "bob@example.com"},
967 `=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
968 },
969 {
970 &Address{Name: "Bob Jane", Address: "bob@example.com"},
971 `"Bob Jane" <bob@example.com>`,
972 },
973 {
974 &Address{Name: "Böb Jacöb", Address: "bob@example.com"},
975 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`,
976 },
977 {
978 &Address{Name: "Rob", Address: ""},
979 `"Rob" <@>`,
980 },
981 {
982 &Address{Name: "Rob", Address: "@"},
983 `"Rob" <@>`,
984 },
985 {
986 &Address{Name: "Böb, Jacöb", Address: "bob@example.com"},
987 `=?utf-8?b?QsO2YiwgSmFjw7Zi?= <bob@example.com>`,
988 },
989 {
990 &Address{Name: "=??Q?x?=", Address: "hello@world.com"},
991 `"=??Q?x?=" <hello@world.com>`,
992 },
993 {
994 &Address{Name: "=?hello", Address: "hello@world.com"},
995 `"=?hello" <hello@world.com>`,
996 },
997 {
998 &Address{Name: "world?=", Address: "hello@world.com"},
999 `"world?=" <hello@world.com>`,
1000 },
1001 {
1002
1003 &Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"},
1004 "=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>",
1005 },
1006 }
1007 for _, test := range tests {
1008 s := test.addr.String()
1009 if s != test.exp {
1010 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
1011 continue
1012 }
1013
1014
1015 if test.addr.Address != "" && test.addr.Address != "@" {
1016 a, err := ParseAddress(test.exp)
1017 if err != nil {
1018 t.Errorf("ParseAddress(%#q): %v", test.exp, err)
1019 continue
1020 }
1021 if a.Name != test.addr.Name || a.Address != test.addr.Address {
1022 t.Errorf("ParseAddress(%#q) = %#v, want %#v", test.exp, a, test.addr)
1023 }
1024 }
1025 }
1026 }
1027
1028
1029 func TestAddressParsingAndFormatting(t *testing.T) {
1030
1031
1032 tests := []string{
1033 `<Bob@example.com>`,
1034 `<bob.bob@example.com>`,
1035 `<".bob"@example.com>`,
1036 `<" "@example.com>`,
1037 `<some.mail-with-dash@example.com>`,
1038 `<"dot.and space"@example.com>`,
1039 `<"very.unusual.@.unusual.com"@example.com>`,
1040 `<admin@mailserver1>`,
1041 `<postmaster@localhost>`,
1042 "<#!$%&'*+-/=?^_`{}|~@example.org>",
1043 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`,
1044 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`,
1045 `<"Abc\\@def"@example.com>`,
1046 `<"Joe\\Blow"@example.com>`,
1047 `<test1/test2=test3@example.com>`,
1048 `<def!xyz%abc@example.com>`,
1049 `<_somename@example.com>`,
1050 `<joe@uk>`,
1051 `<~@example.com>`,
1052 `<"..."@test.com>`,
1053 `<"john..doe"@example.com>`,
1054 `<"john.doe."@example.com>`,
1055 `<".john.doe"@example.com>`,
1056 `<"."@example.com>`,
1057 `<".."@example.com>`,
1058 `<"0:"@0>`,
1059 }
1060
1061 for _, test := range tests {
1062 addr, err := ParseAddress(test)
1063 if err != nil {
1064 t.Errorf("Couldn't parse address %s: %s", test, err.Error())
1065 continue
1066 }
1067 str := addr.String()
1068 addr, err = ParseAddress(str)
1069 if err != nil {
1070 t.Errorf("ParseAddr(%q) error: %v", test, err)
1071 continue
1072 }
1073
1074 if addr.String() != test {
1075 t.Errorf("String() round-trip = %q; want %q", addr, test)
1076 continue
1077 }
1078
1079 }
1080
1081
1082 badTests := []string{
1083 `<Abc.example.com>`,
1084 `<A@b@c@example.com>`,
1085 `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`,
1086 `<just"not"right@example.com>`,
1087 `<this is"not\allowed@example.com>`,
1088 `<this\ still\"not\\allowed@example.com>`,
1089 `<john..doe@example.com>`,
1090 `<john.doe@example..com>`,
1091 `<john.doe@example..com>`,
1092 `<john.doe.@example.com>`,
1093 `<john.doe.@.example.com>`,
1094 `<.john.doe@example.com>`,
1095 `<@example.com>`,
1096 `<.@example.com>`,
1097 `<test@.>`,
1098 `< @example.com>`,
1099 `<""test""blah""@example.com>`,
1100 `<""@0>`,
1101 }
1102
1103 for _, test := range badTests {
1104 _, err := ParseAddress(test)
1105 if err == nil {
1106 t.Errorf("Should have failed to parse address: %s", test)
1107 continue
1108 }
1109
1110 }
1111
1112 }
1113
1114 func TestAddressFormattingAndParsing(t *testing.T) {
1115 tests := []*Address{
1116 {Name: "@lïce", Address: "alice@example.com"},
1117 {Name: "Böb O'Connor", Address: "bob@example.com"},
1118 {Name: "???", Address: "bob@example.com"},
1119 {Name: "Böb ???", Address: "bob@example.com"},
1120 {Name: "Böb (Jacöb)", Address: "bob@example.com"},
1121 {Name: "à#$%&'(),.:;<>@[]^`{|}~'", Address: "bob@example.com"},
1122
1123 {Name: "\"\\\x1f,\"", Address: "0@0"},
1124
1125 {Name: "naé, mée", Address: "test.mail@gmail.com"},
1126 }
1127
1128 for i, test := range tests {
1129 parsed, err := ParseAddress(test.String())
1130 if err != nil {
1131 t.Errorf("test #%d: ParseAddr(%q) error: %v", i, test.String(), err)
1132 continue
1133 }
1134 if parsed.Name != test.Name {
1135 t.Errorf("test #%d: Parsed name = %q; want %q", i, parsed.Name, test.Name)
1136 }
1137 if parsed.Address != test.Address {
1138 t.Errorf("test #%d: Parsed address = %q; want %q", i, parsed.Address, test.Address)
1139 }
1140 }
1141 }
1142
1143 func TestEmptyAddress(t *testing.T) {
1144 parsed, err := ParseAddress("")
1145 if parsed != nil || err == nil {
1146 t.Errorf(`ParseAddress("") = %v, %v, want nil, error`, parsed, err)
1147 }
1148 list, err := ParseAddressList("")
1149 if len(list) > 0 || err == nil {
1150 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1151 }
1152 list, err = ParseAddressList(",")
1153 if len(list) > 0 || err == nil {
1154 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1155 }
1156 list, err = ParseAddressList("a@b c@d")
1157 if len(list) > 0 || err == nil {
1158 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1159 }
1160 }
1161
View as plain text