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 21: {"<jdoe@[" + string([]byte{0xed, 0xa0, 0x80}) + "192.168.0.1]>", "invalid utf-8 in domain-literal"},
394 22: {"<jdoe@[[192.168.0.1]>", "bad character in domain-literal"},
395 23: {"<jdoe@[192.168.0.1>", "unclosed domain-literal"},
396 24: {"<jdoe@[256.0.0.1]>", "invalid IP address in domain-literal"},
397 }
398
399 for i, tc := range mustErrTestCases {
400 _, err := ParseAddress(tc.text)
401 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
402 t.Errorf(`mail.ParseAddress(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
403 }
404 }
405
406 t.Run("CustomWordDecoder", func(t *testing.T) {
407 p := &AddressParser{WordDecoder: &mime.WordDecoder{}}
408 for i, tc := range mustErrTestCases {
409 _, err := p.Parse(tc.text)
410 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
411 t.Errorf(`p.Parse(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
412 }
413 }
414 })
415
416 }
417
418 func TestAddressParsing(t *testing.T) {
419 tests := []struct {
420 addrsStr string
421 exp []*Address
422 }{
423
424 {
425 `jdoe@machine.example`,
426 []*Address{{
427 Address: "jdoe@machine.example",
428 }},
429 },
430
431 {
432 `John Doe <jdoe@machine.example>`,
433 []*Address{{
434 Name: "John Doe",
435 Address: "jdoe@machine.example",
436 }},
437 },
438
439 {
440 `"Joe Q. Public" <john.q.public@example.com>`,
441 []*Address{{
442 Name: "Joe Q. Public",
443 Address: "john.q.public@example.com",
444 }},
445 },
446
447 {
448 `John (middle) Doe <jdoe@machine.example>`,
449 []*Address{{
450 Name: "John Doe",
451 Address: "jdoe@machine.example",
452 }},
453 },
454
455 {
456 `"John (middle) Doe" <jdoe@machine.example>`,
457 []*Address{{
458 Name: "John (middle) Doe",
459 Address: "jdoe@machine.example",
460 }},
461 },
462 {
463 `"John <middle> Doe" <jdoe@machine.example>`,
464 []*Address{{
465 Name: "John <middle> Doe",
466 Address: "jdoe@machine.example",
467 }},
468 },
469 {
470 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
471 []*Address{
472 {
473 Name: "Mary Smith",
474 Address: "mary@x.test",
475 },
476 {
477 Address: "jdoe@example.org",
478 },
479 {
480 Name: "Who?",
481 Address: "one@y.test",
482 },
483 },
484 },
485 {
486 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
487 []*Address{
488 {
489 Address: "boss@nil.test",
490 },
491 {
492 Name: `Giant; "Big" Box`,
493 Address: "sysservices@example.net",
494 },
495 },
496 },
497
498 {
499 `Joe Q. Public <john.q.public@example.com>`,
500 []*Address{{
501 Name: "Joe Q. Public",
502 Address: "john.q.public@example.com",
503 }},
504 },
505
506 {
507 `group1: groupaddr1@example.com;`,
508 []*Address{
509 {
510 Name: "",
511 Address: "groupaddr1@example.com",
512 },
513 },
514 },
515 {
516 `empty group: ;`,
517 []*Address(nil),
518 },
519 {
520 `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
521 []*Address{
522 {
523 Name: "Ed Jones",
524 Address: "c@a.test",
525 },
526 {
527 Name: "",
528 Address: "joe@where.test",
529 },
530 {
531 Name: "John",
532 Address: "jdoe@one.test",
533 },
534 },
535 },
536
537 {
538 ` , joe@where.test,,John <jdoe@one.test>,`,
539 []*Address{
540 {
541 Name: "",
542 Address: "joe@where.test",
543 },
544 {
545 Name: "John",
546 Address: "jdoe@one.test",
547 },
548 },
549 },
550 {
551 ` , joe@where.test,,John <jdoe@one.test>,,`,
552 []*Address{
553 {
554 Name: "",
555 Address: "joe@where.test",
556 },
557 {
558 Name: "John",
559 Address: "jdoe@one.test",
560 },
561 },
562 },
563 {
564 `Group1: <addr1@example.com>;, Group 2: addr2@example.com;, John <addr3@example.com>`,
565 []*Address{
566 {
567 Name: "",
568 Address: "addr1@example.com",
569 },
570 {
571 Name: "",
572 Address: "addr2@example.com",
573 },
574 {
575 Name: "John",
576 Address: "addr3@example.com",
577 },
578 },
579 },
580
581 {
582 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
583 []*Address{
584 {
585 Name: `Jörg Doe`,
586 Address: "joerg@example.com",
587 },
588 },
589 },
590
591 {
592 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
593 []*Address{
594 {
595 Name: `Jorg Doe`,
596 Address: "joerg@example.com",
597 },
598 },
599 },
600
601 {
602 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
603 []*Address{
604 {
605 Name: `Jörg Doe`,
606 Address: "joerg@example.com",
607 },
608 },
609 },
610
611 {
612 `=?utf-8?q?J=C3=B6rg?= =?utf-8?q?Doe?= <joerg@example.com>`,
613 []*Address{
614 {
615 Name: `JörgDoe`,
616 Address: "joerg@example.com",
617 },
618 },
619 },
620
621 {
622 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
623 []*Address{
624 {
625 Name: `André Pirard`,
626 Address: "PIRARD@vm1.ulg.ac.be",
627 },
628 },
629 },
630
631 {
632 `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
633 []*Address{
634 {
635 Name: `Jörg`,
636 Address: "joerg@example.com",
637 },
638 },
639 },
640
641 {
642 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
643 []*Address{
644 {
645 Name: `Jörg`,
646 Address: "joerg@example.com",
647 },
648 },
649 },
650
651 {
652 `Asem H. <noreply@example.com>`,
653 []*Address{
654 {
655 Name: `Asem H.`,
656 Address: "noreply@example.com",
657 },
658 },
659 },
660
661 {
662 `"Gø Pher" <gopher@example.com>`,
663 []*Address{
664 {
665 Name: `Gø Pher`,
666 Address: "gopher@example.com",
667 },
668 },
669 },
670
671 {
672 `µ <micro@example.com>`,
673 []*Address{
674 {
675 Name: `µ`,
676 Address: "micro@example.com",
677 },
678 },
679 },
680
681 {
682 `Micro <µ@example.com>`,
683 []*Address{
684 {
685 Name: `Micro`,
686 Address: "µ@example.com",
687 },
688 },
689 },
690
691 {
692 `Micro <micro@µ.example.com>`,
693 []*Address{
694 {
695 Name: `Micro`,
696 Address: "micro@µ.example.com",
697 },
698 },
699 },
700
701 {
702 `"" <emptystring@example.com>`,
703 []*Address{
704 {
705 Name: "",
706 Address: "emptystring@example.com",
707 },
708 },
709 },
710
711 {
712 `<cfws@example.com> (CFWS (cfws)) (another comment)`,
713 []*Address{
714 {
715 Name: "",
716 Address: "cfws@example.com",
717 },
718 },
719 },
720 {
721 `<cfws@example.com> () (another comment), <cfws2@example.com> (another)`,
722 []*Address{
723 {
724 Name: "",
725 Address: "cfws@example.com",
726 },
727 {
728 Name: "",
729 Address: "cfws2@example.com",
730 },
731 },
732 },
733
734 {
735 `john@example.com (John Doe)`,
736 []*Address{
737 {
738 Name: "John Doe",
739 Address: "john@example.com",
740 },
741 },
742 },
743
744 {
745 `John Doe <john@example.com> (Joey)`,
746 []*Address{
747 {
748 Name: "John Doe",
749 Address: "john@example.com",
750 },
751 },
752 },
753
754 {
755 `john@example.com(John Doe)`,
756 []*Address{
757 {
758 Name: "John Doe",
759 Address: "john@example.com",
760 },
761 },
762 },
763
764 {
765 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
766 []*Address{
767 {
768 Name: "Adam Sjøgren",
769 Address: "asjo@example.com",
770 },
771 },
772 },
773
774 {
775 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
776 []*Address{
777 {
778 Name: "Adam Sjøgren",
779 Address: "asjo@example.com",
780 },
781 },
782 },
783
784 {
785 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?= (Debian))`,
786 []*Address{
787 {
788 Name: "Adam Sjøgren (Debian)",
789 Address: "asjo@example.com",
790 },
791 },
792 },
793
794 {
795 `group (comment:): a@example.com, b@example.com;`,
796 []*Address{
797 {
798 Address: "a@example.com",
799 },
800 {
801 Address: "b@example.com",
802 },
803 },
804 },
805 {
806 `x(:"):"@a.example;("@b.example;`,
807 []*Address{
808 {
809 Address: `@a.example;(@b.example`,
810 },
811 },
812 },
813
814 {
815 `jdoe@[192.168.0.1]`,
816 []*Address{{
817 Address: "jdoe@[192.168.0.1]",
818 }},
819 },
820 {
821 `John Doe <jdoe@[192.168.0.1]>`,
822 []*Address{{
823 Name: "John Doe",
824 Address: "jdoe@[192.168.0.1]",
825 }},
826 },
827 }
828 for _, test := range tests {
829 if len(test.exp) == 1 {
830 addr, err := ParseAddress(test.addrsStr)
831 if err != nil {
832 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
833 continue
834 }
835 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
836 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
837 }
838 }
839
840 addrs, err := ParseAddressList(test.addrsStr)
841 if err != nil {
842 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
843 continue
844 }
845 if !reflect.DeepEqual(addrs, test.exp) {
846 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
847 }
848 }
849 }
850
851 func TestAddressParser(t *testing.T) {
852 tests := []struct {
853 addrsStr string
854 exp []*Address
855 }{
856
857 {
858 `jdoe@machine.example`,
859 []*Address{{
860 Address: "jdoe@machine.example",
861 }},
862 },
863
864 {
865 `John Doe <jdoe@machine.example>`,
866 []*Address{{
867 Name: "John Doe",
868 Address: "jdoe@machine.example",
869 }},
870 },
871
872 {
873 `"Joe Q. Public" <john.q.public@example.com>`,
874 []*Address{{
875 Name: "Joe Q. Public",
876 Address: "john.q.public@example.com",
877 }},
878 },
879 {
880 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
881 []*Address{
882 {
883 Name: "Mary Smith",
884 Address: "mary@x.test",
885 },
886 {
887 Address: "jdoe@example.org",
888 },
889 {
890 Name: "Who?",
891 Address: "one@y.test",
892 },
893 },
894 },
895 {
896 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
897 []*Address{
898 {
899 Address: "boss@nil.test",
900 },
901 {
902 Name: `Giant; "Big" Box`,
903 Address: "sysservices@example.net",
904 },
905 },
906 },
907
908 {
909 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
910 []*Address{
911 {
912 Name: `Jörg Doe`,
913 Address: "joerg@example.com",
914 },
915 },
916 },
917
918 {
919 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
920 []*Address{
921 {
922 Name: `Jorg Doe`,
923 Address: "joerg@example.com",
924 },
925 },
926 },
927
928 {
929 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`,
930 []*Address{
931 {
932 Name: `Jörg Doe`,
933 Address: "joerg@example.com",
934 },
935 },
936 },
937
938 {
939 `=?windows-1252?q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
940 []*Address{
941 {
942 Name: `André Pirard`,
943 Address: "PIRARD@vm1.ulg.ac.be",
944 },
945 },
946 },
947
948 {
949 `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`,
950 []*Address{
951 {
952 Name: `Jörg`,
953 Address: "joerg@example.com",
954 },
955 },
956 },
957
958 {
959 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
960 []*Address{
961 {
962 Name: `Jörg`,
963 Address: "joerg@example.com",
964 },
965 },
966 },
967
968 {
969 `Asem H. <noreply@example.com>`,
970 []*Address{
971 {
972 Name: `Asem H.`,
973 Address: "noreply@example.com",
974 },
975 },
976 },
977
978 {
979 `jdoe@[192.168.0.1]`,
980 []*Address{{
981 Address: "jdoe@[192.168.0.1]",
982 }},
983 },
984 {
985 `John Doe <jdoe@[192.168.0.1]>`,
986 []*Address{{
987 Name: "John Doe",
988 Address: "jdoe@[192.168.0.1]",
989 }},
990 },
991 }
992
993 ap := AddressParser{WordDecoder: &mime.WordDecoder{
994 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
995 in, err := io.ReadAll(input)
996 if err != nil {
997 return nil, err
998 }
999
1000 switch charset {
1001 case "iso-8859-15":
1002 in = bytes.ReplaceAll(in, []byte("\xf6"), []byte("ö"))
1003 case "windows-1252":
1004 in = bytes.ReplaceAll(in, []byte("\xe9"), []byte("é"))
1005 }
1006
1007 return bytes.NewReader(in), nil
1008 },
1009 }}
1010
1011 for _, test := range tests {
1012 if len(test.exp) == 1 {
1013 addr, err := ap.Parse(test.addrsStr)
1014 if err != nil {
1015 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
1016 continue
1017 }
1018 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
1019 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
1020 }
1021 }
1022
1023 addrs, err := ap.ParseList(test.addrsStr)
1024 if err != nil {
1025 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
1026 continue
1027 }
1028 if !reflect.DeepEqual(addrs, test.exp) {
1029 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
1030 }
1031 }
1032 }
1033
1034 func TestAddressString(t *testing.T) {
1035 tests := []struct {
1036 addr *Address
1037 exp string
1038 }{
1039 {
1040 &Address{Address: "bob@example.com"},
1041 "<bob@example.com>",
1042 },
1043 {
1044 &Address{Address: `my@idiot@address@example.com`},
1045 `<"my@idiot@address"@example.com>`,
1046 },
1047 {
1048 &Address{Address: ` @example.com`},
1049 `<" "@example.com>`,
1050 },
1051 {
1052 &Address{Name: "Bob", Address: "bob@example.com"},
1053 `"Bob" <bob@example.com>`,
1054 },
1055 {
1056
1057 &Address{Name: "Böb", Address: "bob@example.com"},
1058 `=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
1059 },
1060 {
1061 &Address{Name: "Bob Jane", Address: "bob@example.com"},
1062 `"Bob Jane" <bob@example.com>`,
1063 },
1064 {
1065 &Address{Name: "Böb Jacöb", Address: "bob@example.com"},
1066 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`,
1067 },
1068 {
1069 &Address{Name: "Rob", Address: ""},
1070 `"Rob" <@>`,
1071 },
1072 {
1073 &Address{Name: "Rob", Address: "@"},
1074 `"Rob" <@>`,
1075 },
1076 {
1077 &Address{Name: "Böb, Jacöb", Address: "bob@example.com"},
1078 `=?utf-8?b?QsO2YiwgSmFjw7Zi?= <bob@example.com>`,
1079 },
1080 {
1081 &Address{Name: "=??Q?x?=", Address: "hello@world.com"},
1082 `"=??Q?x?=" <hello@world.com>`,
1083 },
1084 {
1085 &Address{Name: "=?hello", Address: "hello@world.com"},
1086 `"=?hello" <hello@world.com>`,
1087 },
1088 {
1089 &Address{Name: "world?=", Address: "hello@world.com"},
1090 `"world?=" <hello@world.com>`,
1091 },
1092 {
1093
1094 &Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"},
1095 "=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>",
1096 },
1097
1098 {
1099 &Address{Address: "bob@[192.168.0.1]"},
1100 "<bob@[192.168.0.1]>",
1101 },
1102 {
1103 &Address{Name: "Bob", Address: "bob@[192.168.0.1]"},
1104 `"Bob" <bob@[192.168.0.1]>`,
1105 },
1106 }
1107 for _, test := range tests {
1108 s := test.addr.String()
1109 if s != test.exp {
1110 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
1111 continue
1112 }
1113
1114
1115 if test.addr.Address != "" && test.addr.Address != "@" {
1116 a, err := ParseAddress(test.exp)
1117 if err != nil {
1118 t.Errorf("ParseAddress(%#q): %v", test.exp, err)
1119 continue
1120 }
1121 if a.Name != test.addr.Name || a.Address != test.addr.Address {
1122 t.Errorf("ParseAddress(%#q) = %#v, want %#v", test.exp, a, test.addr)
1123 }
1124 }
1125 }
1126 }
1127
1128
1129 func TestAddressParsingAndFormatting(t *testing.T) {
1130
1131
1132 tests := []string{
1133 `<Bob@example.com>`,
1134 `<bob.bob@example.com>`,
1135 `<".bob"@example.com>`,
1136 `<" "@example.com>`,
1137 `<some.mail-with-dash@example.com>`,
1138 `<"dot.and space"@example.com>`,
1139 `<"very.unusual.@.unusual.com"@example.com>`,
1140 `<admin@mailserver1>`,
1141 `<postmaster@localhost>`,
1142 "<#!$%&'*+-/=?^_`{}|~@example.org>",
1143 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`,
1144 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`,
1145 `<"Abc\\@def"@example.com>`,
1146 `<"Joe\\Blow"@example.com>`,
1147 `<test1/test2=test3@example.com>`,
1148 `<def!xyz%abc@example.com>`,
1149 `<_somename@example.com>`,
1150 `<joe@uk>`,
1151 `<~@example.com>`,
1152 `<"..."@test.com>`,
1153 `<"john..doe"@example.com>`,
1154 `<"john.doe."@example.com>`,
1155 `<".john.doe"@example.com>`,
1156 `<"."@example.com>`,
1157 `<".."@example.com>`,
1158 `<"0:"@0>`,
1159 `<Bob@[192.168.0.1]>`,
1160 }
1161
1162 for _, test := range tests {
1163 addr, err := ParseAddress(test)
1164 if err != nil {
1165 t.Errorf("Couldn't parse address %s: %s", test, err.Error())
1166 continue
1167 }
1168 str := addr.String()
1169 addr, err = ParseAddress(str)
1170 if err != nil {
1171 t.Errorf("ParseAddr(%q) error: %v", test, err)
1172 continue
1173 }
1174
1175 if addr.String() != test {
1176 t.Errorf("String() round-trip = %q; want %q", addr, test)
1177 continue
1178 }
1179
1180 }
1181
1182
1183 badTests := []string{
1184 `<Abc.example.com>`,
1185 `<A@b@c@example.com>`,
1186 `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`,
1187 `<just"not"right@example.com>`,
1188 `<this is"not\allowed@example.com>`,
1189 `<this\ still\"not\\allowed@example.com>`,
1190 `<john..doe@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 `<@example.com>`,
1197 `<.@example.com>`,
1198 `<test@.>`,
1199 `< @example.com>`,
1200 `<""test""blah""@example.com>`,
1201 `<""@0>`,
1202 }
1203
1204 for _, test := range badTests {
1205 _, err := ParseAddress(test)
1206 if err == nil {
1207 t.Errorf("Should have failed to parse address: %s", test)
1208 continue
1209 }
1210
1211 }
1212
1213 }
1214
1215 func TestAddressFormattingAndParsing(t *testing.T) {
1216 tests := []*Address{
1217 {Name: "@lïce", Address: "alice@example.com"},
1218 {Name: "Böb O'Connor", Address: "bob@example.com"},
1219 {Name: "???", Address: "bob@example.com"},
1220 {Name: "Böb ???", Address: "bob@example.com"},
1221 {Name: "Böb (Jacöb)", Address: "bob@example.com"},
1222 {Name: "à#$%&'(),.:;<>@[]^`{|}~'", Address: "bob@example.com"},
1223
1224 {Name: "\"\\\x1f,\"", Address: "0@0"},
1225
1226 {Name: "naé, mée", Address: "test.mail@gmail.com"},
1227 }
1228
1229 for i, test := range tests {
1230 parsed, err := ParseAddress(test.String())
1231 if err != nil {
1232 t.Errorf("test #%d: ParseAddr(%q) error: %v", i, test.String(), err)
1233 continue
1234 }
1235 if parsed.Name != test.Name {
1236 t.Errorf("test #%d: Parsed name = %q; want %q", i, parsed.Name, test.Name)
1237 }
1238 if parsed.Address != test.Address {
1239 t.Errorf("test #%d: Parsed address = %q; want %q", i, parsed.Address, test.Address)
1240 }
1241 }
1242 }
1243
1244 func TestEmptyAddress(t *testing.T) {
1245 parsed, err := ParseAddress("")
1246 if parsed != nil || err == nil {
1247 t.Errorf(`ParseAddress("") = %v, %v, want nil, error`, parsed, err)
1248 }
1249 list, err := ParseAddressList("")
1250 if len(list) > 0 || err == nil {
1251 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1252 }
1253 list, err = ParseAddressList(",")
1254 if len(list) > 0 || err == nil {
1255 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1256 }
1257 list, err = ParseAddressList("a@b c@d")
1258 if len(list) > 0 || err == nil {
1259 t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err)
1260 }
1261 }
1262
View as plain text