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