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