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 false,
269 },
270 {
271 "Thu, 21 May 2020 14:04:40 UT",
272 time.Date(2020, 05, 21, 14, 04, 40, 0, time.UTC),
273 false,
274 },
275 {
276 "Thu, 21 May 2020 14:04:40 UTC",
277 time.Date(2020, 05, 21, 14, 04, 40, 0, time.UTC),
278 true,
279 },
280 {
281 "Fri, 21 Nov 1997 09:55:06 GMT (GMT)",
282 time.Date(1997, 11, 21, 9, 55, 6, 0, time.UTC),
283 true,
284 },
285 }
286 for _, test := range tests {
287 hdr := Header{
288 "Date": []string{test.dateStr},
289 }
290 date, err := hdr.Date()
291 if err != nil && test.valid {
292 t.Errorf("Header(Date: %s).Date(): %v", test.dateStr, err)
293 } else if err == nil && test.exp.IsZero() {
294
295
296 } else if err == nil && !date.Equal(test.exp) && test.valid {
297 t.Errorf("Header(Date: %s).Date() = %+v, want %+v", test.dateStr, date, test.exp)
298 } else if err == nil && !test.valid {
299 t.Errorf("Header(Date: %s).Date() did not return an error but %v", test.dateStr, date)
300 }
301
302 date, err = ParseDate(test.dateStr)
303 if err != nil && test.valid {
304 t.Errorf("ParseDate(%s): %v", test.dateStr, err)
305 } else if err == nil && test.exp.IsZero() {
306
307
308 } else if err == nil && !test.valid {
309 t.Errorf("ParseDate(%s) did not return an error but %v", test.dateStr, date)
310 } else if err == nil && test.valid && !date.Equal(test.exp) {
311 t.Errorf("ParseDate(%s) = %+v, want %+v", test.dateStr, date, test.exp)
312 }
313 }
314 }
315
316 func TestAddressParsingError(t *testing.T) {
317 mustErrTestCases := [...]struct {
318 text string
319 wantErrText string
320 }{
321 0: {"=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown@gmail.com>", "charset not supported"},
322 1: {"a@gmail.com b@gmail.com", "expected single address"},
323 2: {string([]byte{0xed, 0xa0, 0x80}) + " <micro@example.net>", "invalid utf-8 in address"},
324 3: {"\"" + string([]byte{0xed, 0xa0, 0x80}) + "\" <half-surrogate@example.com>", "invalid utf-8 in quoted-string"},
325 4: {"\"\\" + string([]byte{0x80}) + "\" <escaped-invalid-unicode@example.net>", "invalid utf-8 in quoted-string"},
326 5: {"\"\x00\" <null@example.net>", "bad character in quoted-string"},
327 6: {"\"\\\x00\" <escaped-null@example.net>", "bad character in quoted-string"},
328 7: {"John Doe", "no angle-addr"},
329 8: {`<jdoe#machine.example>`, "missing @ in addr-spec"},
330 9: {`John <middle> Doe <jdoe@machine.example>`, "missing @ in addr-spec"},
331 10: {"cfws@example.com (", "misformatted parenthetical comment"},
332 11: {"empty group: ;", "empty group"},
333 12: {"root group: embed group: null@example.com;", "no angle-addr"},
334 13: {"group not closed: null@example.com", "expected comma"},
335 14: {"group: first@example.com, second@example.com;", "group with multiple addresses"},
336 15: {"john.doe", "missing '@' or angle-addr"},
337 16: {"john.doe@", "no angle-addr"},
338 17: {"John Doe@foo.bar", "no angle-addr"},
339 }
340
341 for i, tc := range mustErrTestCases {
342 _, err := ParseAddress(tc.text)
343 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
344 t.Errorf(`mail.ParseAddress(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
345 }
346 }
347 }
348
349 func TestAddressParsing(t *testing.T) {
350 tests := []struct {
351 addrsStr string
352 exp []*Address
353 }{
354
355 {
356 `jdoe@machine.example`,
357 []*Address{{
358 Address: "jdoe@machine.example",
359 }},
360 },
361
362 {
363 `John Doe <jdoe@machine.example>`,
364 []*Address{{
365 Name: "John Doe",
366 Address: "jdoe@machine.example",
367 }},
368 },
369
370 {
371 `"Joe Q. Public" <john.q.public@example.com>`,
372 []*Address{{
373 Name: "Joe Q. Public",
374 Address: "john.q.public@example.com",
375 }},
376 },
377 {
378 `"John (middle) Doe" <jdoe@machine.example>`,
379 []*Address{{
380 Name: "John (middle) Doe",
381 Address: "jdoe@machine.example",
382 }},
383 },
384 {
385 `John (middle) Doe <jdoe@machine.example>`,
386 []*Address{{
387 Name: "John (middle) Doe",
388 Address: "jdoe@machine.example",
389 }},
390 },
391 {
392 `John !@M@! Doe <jdoe@machine.example>`,
393 []*Address{{
394 Name: "John !@M@! Doe",
395 Address: "jdoe@machine.example",
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 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
407 []*Address{
408 {
409 Name: "Mary Smith",
410 Address: "mary@x.test",
411 },
412 {
413 Address: "jdoe@example.org",
414 },
415 {
416 Name: "Who?",
417 Address: "one@y.test",
418 },
419 },
420 },
421 {
422 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
423 []*Address{
424 {
425 Address: "boss@nil.test",
426 },
427 {
428 Name: `Giant; "Big" Box`,
429 Address: "sysservices@example.net",
430 },
431 },
432 },
433
434 {
435 `Joe Q. Public <john.q.public@example.com>`,
436 []*Address{{
437 Name: "Joe Q. Public",
438 Address: "john.q.public@example.com",
439 }},
440 },
441
442 {
443 `group1: groupaddr1@example.com;`,
444 []*Address{
445 {
446 Name: "",
447 Address: "groupaddr1@example.com",
448 },
449 },
450 },
451 {
452 `empty group: ;`,
453 []*Address(nil),
454 },
455 {
456 `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
457 []*Address{
458 {
459 Name: "Ed Jones",
460 Address: "c@a.test",
461 },
462 {
463 Name: "",
464 Address: "joe@where.test",
465 },
466 {
467 Name: "John",
468 Address: "jdoe@one.test",
469 },
470 },
471 },
472
473 {
474 ` , joe@where.test,,John <jdoe@one.test>,`,
475 []*Address{
476 {
477 Name: "",
478 Address: "joe@where.test",
479 },
480 {
481 Name: "John",
482 Address: "jdoe@one.test",
483 },
484 },
485 },
486 {
487 ` , joe@where.test,,John <jdoe@one.test>,,`,
488 []*Address{
489 {
490 Name: "",
491 Address: "joe@where.test",
492 },
493 {
494 Name: "John",
495 Address: "jdoe@one.test",
496 },
497 },
498 },
499 {
500 `Group1: <addr1@example.com>;, Group 2: addr2@example.com;, John <addr3@example.com>`,
501 []*Address{
502 {
503 Name: "",
504 Address: "addr1@example.com",
505 },
506 {
507 Name: "",
508 Address: "addr2@example.com",
509 },
510 {
511 Name: "John",
512 Address: "addr3@example.com",
513 },
514 },
515 },
516
517 {
518 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
519 []*Address{
520 {
521 Name: `Jörg Doe`,
522 Address: "joerg@example.com",
523 },
524 },
525 },
526
527 {
528 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
529 []*Address{
530 {
531 Name: `Jorg Doe`,
532 Address: "joerg@example.com",
533 },
534 },
535 },
536
537 {
538 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
539 []*Address{
540 {
541 Name: `Jörg Doe`,
542 Address: "joerg@example.com",
543 },
544 },
545 },
546
547 {
548 `=?utf-8?q?J=C3=B6rg?= =?utf-8?q?Doe?= <joerg@example.com>`,
549 []*Address{
550 {
551 Name: `JörgDoe`,
552 Address: "joerg@example.com",
553 },
554 },
555 },
556
557 {
558 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
559 []*Address{
560 {
561 Name: `André Pirard`,
562 Address: "PIRARD@vm1.ulg.ac.be",
563 },
564 },
565 },
566
567 {
568 `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
569 []*Address{
570 {
571 Name: `Jörg`,
572 Address: "joerg@example.com",
573 },
574 },
575 },
576
577 {
578 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
579 []*Address{
580 {
581 Name: `Jörg`,
582 Address: "joerg@example.com",
583 },
584 },
585 },
586
587 {
588 `Asem H. <noreply@example.com>`,
589 []*Address{
590 {
591 Name: `Asem H.`,
592 Address: "noreply@example.com",
593 },
594 },
595 },
596
597 {
598 `"Gø Pher" <gopher@example.com>`,
599 []*Address{
600 {
601 Name: `Gø Pher`,
602 Address: "gopher@example.com",
603 },
604 },
605 },
606
607 {
608 `µ <micro@example.com>`,
609 []*Address{
610 {
611 Name: `µ`,
612 Address: "micro@example.com",
613 },
614 },
615 },
616
617 {
618 `Micro <µ@example.com>`,
619 []*Address{
620 {
621 Name: `Micro`,
622 Address: "µ@example.com",
623 },
624 },
625 },
626
627 {
628 `Micro <micro@µ.example.com>`,
629 []*Address{
630 {
631 Name: `Micro`,
632 Address: "micro@µ.example.com",
633 },
634 },
635 },
636
637 {
638 `"" <emptystring@example.com>`,
639 []*Address{
640 {
641 Name: "",
642 Address: "emptystring@example.com",
643 },
644 },
645 },
646
647 {
648 `<cfws@example.com> (CFWS (cfws)) (another comment)`,
649 []*Address{
650 {
651 Name: "",
652 Address: "cfws@example.com",
653 },
654 },
655 },
656 {
657 `<cfws@example.com> () (another comment), <cfws2@example.com> (another)`,
658 []*Address{
659 {
660 Name: "",
661 Address: "cfws@example.com",
662 },
663 {
664 Name: "",
665 Address: "cfws2@example.com",
666 },
667 },
668 },
669
670 {
671 `john@example.com (John Doe)`,
672 []*Address{
673 {
674 Name: "John Doe",
675 Address: "john@example.com",
676 },
677 },
678 },
679
680 {
681 `John Doe <john@example.com> (Joey)`,
682 []*Address{
683 {
684 Name: "John Doe",
685 Address: "john@example.com",
686 },
687 },
688 },
689
690 {
691 `john@example.com(John Doe)`,
692 []*Address{
693 {
694 Name: "John Doe",
695 Address: "john@example.com",
696 },
697 },
698 },
699
700 {
701 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
702 []*Address{
703 {
704 Name: "Adam Sjøgren",
705 Address: "asjo@example.com",
706 },
707 },
708 },
709
710 {
711 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
712 []*Address{
713 {
714 Name: "Adam Sjøgren",
715 Address: "asjo@example.com",
716 },
717 },
718 },
719
720 {
721 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?= (Debian))`,
722 []*Address{
723 {
724 Name: "Adam Sjøgren (Debian)",
725 Address: "asjo@example.com",
726 },
727 },
728 },
729 }
730 for _, test := range tests {
731 if len(test.exp) == 1 {
732 addr, err := ParseAddress(test.addrsStr)
733 if err != nil {
734 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
735 continue
736 }
737 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
738 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
739 }
740 }
741
742 addrs, err := ParseAddressList(test.addrsStr)
743 if err != nil {
744 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
745 continue
746 }
747 if !reflect.DeepEqual(addrs, test.exp) {
748 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
749 }
750 }
751 }
752
753 func TestAddressParser(t *testing.T) {
754 tests := []struct {
755 addrsStr string
756 exp []*Address
757 }{
758
759 {
760 `jdoe@machine.example`,
761 []*Address{{
762 Address: "jdoe@machine.example",
763 }},
764 },
765
766 {
767 `John Doe <jdoe@machine.example>`,
768 []*Address{{
769 Name: "John Doe",
770 Address: "jdoe@machine.example",
771 }},
772 },
773
774 {
775 `"Joe Q. Public" <john.q.public@example.com>`,
776 []*Address{{
777 Name: "Joe Q. Public",
778 Address: "john.q.public@example.com",
779 }},
780 },
781 {
782 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
783 []*Address{
784 {
785 Name: "Mary Smith",
786 Address: "mary@x.test",
787 },
788 {
789 Address: "jdoe@example.org",
790 },
791 {
792 Name: "Who?",
793 Address: "one@y.test",
794 },
795 },
796 },
797 {
798 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
799 []*Address{
800 {
801 Address: "boss@nil.test",
802 },
803 {
804 Name: `Giant; "Big" Box`,
805 Address: "sysservices@example.net",
806 },
807 },
808 },
809
810 {
811 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
812 []*Address{
813 {
814 Name: `Jörg Doe`,
815 Address: "joerg@example.com",
816 },
817 },
818 },
819
820 {
821 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
822 []*Address{
823 {
824 Name: `Jorg Doe`,
825 Address: "joerg@example.com",
826 },
827 },
828 },
829
830 {
831 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`,
832 []*Address{
833 {
834 Name: `Jörg Doe`,
835 Address: "joerg@example.com",
836 },
837 },
838 },
839
840 {
841 `=?windows-1252?q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
842 []*Address{
843 {
844 Name: `André Pirard`,
845 Address: "PIRARD@vm1.ulg.ac.be",
846 },
847 },
848 },
849
850 {
851 `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`,
852 []*Address{
853 {
854 Name: `Jörg`,
855 Address: "joerg@example.com",
856 },
857 },
858 },
859
860 {
861 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
862 []*Address{
863 {
864 Name: `Jörg`,
865 Address: "joerg@example.com",
866 },
867 },
868 },
869
870 {
871 `Asem H. <noreply@example.com>`,
872 []*Address{
873 {
874 Name: `Asem H.`,
875 Address: "noreply@example.com",
876 },
877 },
878 },
879 }
880
881 ap := AddressParser{WordDecoder: &mime.WordDecoder{
882 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
883 in, err := io.ReadAll(input)
884 if err != nil {
885 return nil, err
886 }
887
888 switch charset {
889 case "iso-8859-15":
890 in = bytes.ReplaceAll(in, []byte("\xf6"), []byte("ö"))
891 case "windows-1252":
892 in = bytes.ReplaceAll(in, []byte("\xe9"), []byte("é"))
893 }
894
895 return bytes.NewReader(in), nil
896 },
897 }}
898
899 for _, test := range tests {
900 if len(test.exp) == 1 {
901 addr, err := ap.Parse(test.addrsStr)
902 if err != nil {
903 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
904 continue
905 }
906 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
907 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
908 }
909 }
910
911 addrs, err := ap.ParseList(test.addrsStr)
912 if err != nil {
913 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
914 continue
915 }
916 if !reflect.DeepEqual(addrs, test.exp) {
917 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
918 }
919 }
920 }
921
922 func TestAddressString(t *testing.T) {
923 tests := []struct {
924 addr *Address
925 exp string
926 }{
927 {
928 &Address{Address: "bob@example.com"},
929 "<bob@example.com>",
930 },
931 {
932 &Address{Address: `my@idiot@address@example.com`},
933 `<"my@idiot@address"@example.com>`,
934 },
935 {
936 &Address{Address: ` @example.com`},
937 `<" "@example.com>`,
938 },
939 {
940 &Address{Name: "Bob", Address: "bob@example.com"},
941 `"Bob" <bob@example.com>`,
942 },
943 {
944
945 &Address{Name: "Böb", Address: "bob@example.com"},
946 `=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
947 },
948 {
949 &Address{Name: "Bob Jane", Address: "bob@example.com"},
950 `"Bob Jane" <bob@example.com>`,
951 },
952 {
953 &Address{Name: "Böb Jacöb", Address: "bob@example.com"},
954 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`,
955 },
956 {
957 &Address{Name: "Rob", Address: ""},
958 `"Rob" <@>`,
959 },
960 {
961 &Address{Name: "Rob", Address: "@"},
962 `"Rob" <@>`,
963 },
964 {
965 &Address{Name: "Böb, Jacöb", Address: "bob@example.com"},
966 `=?utf-8?b?QsO2YiwgSmFjw7Zi?= <bob@example.com>`,
967 },
968 {
969 &Address{Name: "=??Q?x?=", Address: "hello@world.com"},
970 `"=??Q?x?=" <hello@world.com>`,
971 },
972 {
973 &Address{Name: "=?hello", Address: "hello@world.com"},
974 `"=?hello" <hello@world.com>`,
975 },
976 {
977 &Address{Name: "world?=", Address: "hello@world.com"},
978 `"world?=" <hello@world.com>`,
979 },
980 {
981
982 &Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"},
983 "=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>",
984 },
985 }
986 for _, test := range tests {
987 s := test.addr.String()
988 if s != test.exp {
989 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
990 continue
991 }
992
993
994 if test.addr.Address != "" && test.addr.Address != "@" {
995 a, err := ParseAddress(test.exp)
996 if err != nil {
997 t.Errorf("ParseAddress(%#q): %v", test.exp, err)
998 continue
999 }
1000 if a.Name != test.addr.Name || a.Address != test.addr.Address {
1001 t.Errorf("ParseAddress(%#q) = %#v, want %#v", test.exp, a, test.addr)
1002 }
1003 }
1004 }
1005 }
1006
1007
1008 func TestAddressParsingAndFormatting(t *testing.T) {
1009
1010
1011 tests := []string{
1012 `<Bob@example.com>`,
1013 `<bob.bob@example.com>`,
1014 `<".bob"@example.com>`,
1015 `<" "@example.com>`,
1016 `<some.mail-with-dash@example.com>`,
1017 `<"dot.and space"@example.com>`,
1018 `<"very.unusual.@.unusual.com"@example.com>`,
1019 `<admin@mailserver1>`,
1020 `<postmaster@localhost>`,
1021 "<#!$%&'*+-/=?^_`{}|~@example.org>",
1022 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`,
1023 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`,
1024 `<"Abc\\@def"@example.com>`,
1025 `<"Joe\\Blow"@example.com>`,
1026 `<test1/test2=test3@example.com>`,
1027 `<def!xyz%abc@example.com>`,
1028 `<_somename@example.com>`,
1029 `<joe@uk>`,
1030 `<~@example.com>`,
1031 `<"..."@test.com>`,
1032 `<"john..doe"@example.com>`,
1033 `<"john.doe."@example.com>`,
1034 `<".john.doe"@example.com>`,
1035 `<"."@example.com>`,
1036 `<".."@example.com>`,
1037 `<"0:"@0>`,
1038 }
1039
1040 for _, test := range tests {
1041 addr, err := ParseAddress(test)
1042 if err != nil {
1043 t.Errorf("Couldn't parse address %s: %s", test, err.Error())
1044 continue
1045 }
1046 str := addr.String()
1047 addr, err = ParseAddress(str)
1048 if err != nil {
1049 t.Errorf("ParseAddr(%q) error: %v", test, err)
1050 continue
1051 }
1052
1053 if addr.String() != test {
1054 t.Errorf("String() round-trip = %q; want %q", addr, test)
1055 continue
1056 }
1057
1058 }
1059
1060
1061 badTests := []string{
1062 `<Abc.example.com>`,
1063 `<A@b@c@example.com>`,
1064 `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`,
1065 `<just"not"right@example.com>`,
1066 `<this is"not\allowed@example.com>`,
1067 `<this\ still\"not\\allowed@example.com>`,
1068 `<john..doe@example.com>`,
1069 `<john.doe@example..com>`,
1070 `<john.doe@example..com>`,
1071 `<john.doe.@example.com>`,
1072 `<john.doe.@.example.com>`,
1073 `<.john.doe@example.com>`,
1074 `<@example.com>`,
1075 `<.@example.com>`,
1076 `<test@.>`,
1077 `< @example.com>`,
1078 `<""test""blah""@example.com>`,
1079 `<""@0>`,
1080 }
1081
1082 for _, test := range badTests {
1083 _, err := ParseAddress(test)
1084 if err == nil {
1085 t.Errorf("Should have failed to parse address: %s", test)
1086 continue
1087 }
1088
1089 }
1090
1091 }
1092
1093 func TestAddressFormattingAndParsing(t *testing.T) {
1094 tests := []*Address{
1095 {Name: "@lïce", Address: "alice@example.com"},
1096 {Name: "Böb O'Connor", Address: "bob@example.com"},
1097 {Name: "???", Address: "bob@example.com"},
1098 {Name: "Böb ???", Address: "bob@example.com"},
1099 {Name: "Böb (Jacöb)", Address: "bob@example.com"},
1100 {Name: "à#$%&'(),.:;<>@[]^`{|}~'", Address: "bob@example.com"},
1101
1102 {Name: "\"\\\x1f,\"", Address: "0@0"},
1103
1104 {Name: "naé, mée", Address: "test.mail@gmail.com"},
1105 }
1106
1107 for i, test := range tests {
1108 parsed, err := ParseAddress(test.String())
1109 if err != nil {
1110 t.Errorf("test #%d: ParseAddr(%q) error: %v", i, test.String(), err)
1111 continue
1112 }
1113 if parsed.Name != test.Name {
1114 t.Errorf("test #%d: Parsed name = %q; want %q", i, parsed.Name, test.Name)
1115 }
1116 if parsed.Address != test.Address {
1117 t.Errorf("test #%d: Parsed address = %q; want %q", i, parsed.Address, test.Address)
1118 }
1119 }
1120 }
1121
1122 func TestEmptyAddress(t *testing.T) {
1123 parsed, err := ParseAddress("")
1124 if parsed != nil || err == nil {
1125 t.Errorf(`ParseAddress("") = %v, %v, want nil, error`, parsed, err)
1126 }
1127 list, err := ParseAddressList("")
1128 if len(list) > 0 || err == nil {
1129 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1130 }
1131 list, err = ParseAddressList(",")
1132 if len(list) > 0 || err == nil {
1133 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1134 }
1135 list, err = ParseAddressList("a@b c@d")
1136 if len(list) > 0 || err == nil {
1137 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1138 }
1139 }
1140
View as plain text