blob: b19da52c423a7ffd08d8d3a84d89be7a5d4d8e87 [file] [log] [blame]
David Symonds32e36442011-06-01 14:10:21 +10001// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package mail
6
7import (
8 "bytes"
Alexandre Cesaro1defd222015-05-25 18:58:19 +02009 "io"
David Symonds32e36442011-06-01 14:10:21 +100010 "io/ioutil"
Alexandre Cesaro1defd222015-05-25 18:58:19 +020011 "mime"
David Symonds32e36442011-06-01 14:10:21 +100012 "reflect"
David Crawshaw8bc1bfb2014-05-07 05:58:36 -040013 "strings"
David Symonds32e36442011-06-01 14:10:21 +100014 "testing"
15 "time"
16)
17
18var parseTests = []struct {
19 in string
20 header Header
21 body string
22}{
23 {
24 // RFC 5322, Appendix A.1.1
25 in: `From: John Doe <jdoe@machine.example>
26To: Mary Smith <mary@example.net>
27Subject: Saying Hello
28Date: Fri, 21 Nov 1997 09:55:06 -0600
29Message-ID: <1234@local.machine.example>
30
31This is a message just to say hello.
32So, "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
45func TestParsing(t *testing.T) {
46 for i, test := range parseTests {
47 msg, err := ReadMessage(bytes.NewBuffer([]byte(test.in)))
48 if err != nil {
49 t.Errorf("test #%d: Failed parsing message: %v", i, err)
50 continue
51 }
52 if !headerEq(msg.Header, test.header) {
53 t.Errorf("test #%d: Incorrectly parsed message header.\nGot:\n%+v\nWant:\n%+v",
54 i, msg.Header, test.header)
55 }
56 body, err := ioutil.ReadAll(msg.Body)
57 if err != nil {
58 t.Errorf("test #%d: Failed reading body: %v", i, err)
59 continue
60 }
61 bodyStr := string(body)
62 if bodyStr != test.body {
63 t.Errorf("test #%d: Incorrectly parsed message body.\nGot:\n%+v\nWant:\n%+v",
64 i, bodyStr, test.body)
65 }
66 }
67}
68
69func headerEq(a, b Header) bool {
70 if len(a) != len(b) {
71 return false
72 }
73 for k, as := range a {
74 bs, ok := b[k]
75 if !ok {
76 return false
77 }
78 if !reflect.DeepEqual(as, bs) {
79 return false
80 }
81 }
82 return true
83}
84
85func TestDateParsing(t *testing.T) {
86 tests := []struct {
87 dateStr string
Russ Cox03823b82011-11-30 12:01:46 -050088 exp time.Time
David Symonds32e36442011-06-01 14:10:21 +100089 }{
90 // RFC 5322, Appendix A.1.1
91 {
92 "Fri, 21 Nov 1997 09:55:06 -0600",
Russ Cox03823b82011-11-30 12:01:46 -050093 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
David Symonds32e36442011-06-01 14:10:21 +100094 },
Dan Peterson022548c2016-04-12 16:58:56 -030095 // RFC 5322, Appendix A.6.2
David Symonds32e36442011-06-01 14:10:21 +100096 // Obsolete date.
97 {
98 "21 Nov 97 09:55:06 GMT",
Russ Cox03823b82011-11-30 12:01:46 -050099 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)),
David Symonds32e36442011-06-01 14:10:21 +1000100 },
Bill Thiede132dbb62012-05-25 09:19:21 +1000101 // Commonly found format not specified by RFC 5322.
102 {
103 "Fri, 21 Nov 1997 09:55:06 -0600 (MDT)",
104 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
105 },
David Symonds32e36442011-06-01 14:10:21 +1000106 }
107 for _, test := range tests {
108 hdr := Header{
109 "Date": []string{test.dateStr},
110 }
111 date, err := hdr.Date()
112 if err != nil {
Russ Coxfc88a0f2016-10-20 16:43:09 -0400113 t.Errorf("Header(Date: %s).Date(): %v", test.dateStr, err)
114 } else if !date.Equal(test.exp) {
115 t.Errorf("Header(Date: %s).Date() = %+v, want %+v", test.dateStr, date, test.exp)
David Symonds32e36442011-06-01 14:10:21 +1000116 }
Russ Coxfc88a0f2016-10-20 16:43:09 -0400117
118 date, err = ParseDate(test.dateStr)
119 if err != nil {
120 t.Errorf("ParseDate(%s): %v", test.dateStr, err)
121 } else if !date.Equal(test.exp) {
122 t.Errorf("ParseDate(%s) = %+v, want %+v", test.dateStr, date, test.exp)
David Symonds32e36442011-06-01 14:10:21 +1000123 }
124 }
125}
David Symondsff0198b2011-06-06 16:46:14 +1000126
David Crawshaw8bc1bfb2014-05-07 05:58:36 -0400127func TestAddressParsingError(t *testing.T) {
Hiroshi Iokae7538df2016-03-04 02:54:51 +0900128 mustErrTestCases := [...]struct {
129 text string
130 wantErrText string
131 }{
Minaev Mike85983962017-08-07 08:22:21 +0000132 0: {"=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown@gmail.com>", "charset not supported"},
133 1: {"a@gmail.com b@gmail.com", "expected single address"},
134 2: {string([]byte{0xed, 0xa0, 0x80}) + " <micro@example.net>", "invalid utf-8 in address"},
135 3: {"\"" + string([]byte{0xed, 0xa0, 0x80}) + "\" <half-surrogate@example.com>", "invalid utf-8 in quoted-string"},
136 4: {"\"\\" + string([]byte{0x80}) + "\" <escaped-invalid-unicode@example.net>", "invalid utf-8 in quoted-string"},
137 5: {"\"\x00\" <null@example.net>", "bad character in quoted-string"},
138 6: {"\"\\\x00\" <escaped-null@example.net>", "bad character in quoted-string"},
139 7: {"John Doe", "no angle-addr"},
140 8: {`<jdoe#machine.example>`, "missing @ in addr-spec"},
141 9: {`John <middle> Doe <jdoe@machine.example>`, "missing @ in addr-spec"},
142 10: {"cfws@example.com (", "misformatted parenthetical comment"},
Mihail Minaev6f2e5f92017-09-26 11:47:49 +0000143 11: {"empty group: ;", "empty group"},
144 12: {"root group: embed group: null@example.com;", "no angle-addr"},
145 13: {"group not closed: null@example.com", "expected comma"},
146 14: {"group: first@example.com, second@example.com;", "group with multiple addresses"},
David Crawshaw8bc1bfb2014-05-07 05:58:36 -0400147 }
David Crawshaw8bc1bfb2014-05-07 05:58:36 -0400148
Hiroshi Iokae7538df2016-03-04 02:54:51 +0900149 for i, tc := range mustErrTestCases {
150 _, err := ParseAddress(tc.text)
151 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) {
152 t.Errorf(`mail.ParseAddress(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err)
153 }
Ingo Oesera4f057b2015-09-04 18:22:56 +0200154 }
155}
156
David Symondsff0198b2011-06-06 16:46:14 +1000157func TestAddressParsing(t *testing.T) {
158 tests := []struct {
159 addrsStr string
160 exp []*Address
161 }{
162 // Bare address
163 {
164 `jdoe@machine.example`,
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500165 []*Address{{
David Symondsff0198b2011-06-06 16:46:14 +1000166 Address: "jdoe@machine.example",
167 }},
168 },
169 // RFC 5322, Appendix A.1.1
170 {
171 `John Doe <jdoe@machine.example>`,
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500172 []*Address{{
David Symondsff0198b2011-06-06 16:46:14 +1000173 Name: "John Doe",
174 Address: "jdoe@machine.example",
175 }},
176 },
177 // RFC 5322, Appendix A.1.2
178 {
179 `"Joe Q. Public" <john.q.public@example.com>`,
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500180 []*Address{{
David Symondsff0198b2011-06-06 16:46:14 +1000181 Name: "Joe Q. Public",
182 Address: "john.q.public@example.com",
183 }},
184 },
185 {
Guilherme Rezende738acbc2017-07-24 17:04:02 -0300186 `"John (middle) Doe" <jdoe@machine.example>`,
187 []*Address{{
188 Name: "John (middle) Doe",
189 Address: "jdoe@machine.example",
190 }},
191 },
192 {
193 `John (middle) Doe <jdoe@machine.example>`,
194 []*Address{{
195 Name: "John (middle) Doe",
196 Address: "jdoe@machine.example",
197 }},
198 },
199 {
200 `John !@M@! Doe <jdoe@machine.example>`,
201 []*Address{{
202 Name: "John !@M@! Doe",
203 Address: "jdoe@machine.example",
204 }},
205 },
206 {
207 `"John <middle> Doe" <jdoe@machine.example>`,
208 []*Address{{
209 Name: "John <middle> Doe",
210 Address: "jdoe@machine.example",
211 }},
212 },
213 {
David Symondsff0198b2011-06-06 16:46:14 +1000214 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
215 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500216 {
David Symondsff0198b2011-06-06 16:46:14 +1000217 Name: "Mary Smith",
218 Address: "mary@x.test",
219 },
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500220 {
David Symondsff0198b2011-06-06 16:46:14 +1000221 Address: "jdoe@example.org",
222 },
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500223 {
David Symondsff0198b2011-06-06 16:46:14 +1000224 Name: "Who?",
225 Address: "one@y.test",
226 },
227 },
228 },
229 {
230 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
231 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500232 {
David Symondsff0198b2011-06-06 16:46:14 +1000233 Address: "boss@nil.test",
234 },
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500235 {
David Symondsff0198b2011-06-06 16:46:14 +1000236 Name: `Giant; "Big" Box`,
237 Address: "sysservices@example.net",
238 },
239 },
240 },
Guilherme Rezende738acbc2017-07-24 17:04:02 -0300241 // RFC 5322, Appendix A.6.1
242 {
243 `Joe Q. Public <john.q.public@example.com>`,
244 []*Address{{
245 Name: "Joe Q. Public",
246 Address: "john.q.public@example.com",
247 }},
248 },
David Symondsff0198b2011-06-06 16:46:14 +1000249 // RFC 5322, Appendix A.1.3
Mihail Minaev6f2e5f92017-09-26 11:47:49 +0000250 {
251 `group1: groupaddr1@example.com;`,
252 []*Address{
253 {
254 Name: "",
255 Address: "groupaddr1@example.com",
256 },
257 },
258 },
259 {
260 `empty group: ;`,
261 []*Address(nil),
262 },
263 {
264 `A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;`,
265 []*Address{
266 {
267 Name: "Ed Jones",
268 Address: "c@a.test",
269 },
270 {
271 Name: "",
272 Address: "joe@where.test",
273 },
274 {
275 Name: "John",
276 Address: "jdoe@one.test",
277 },
278 },
279 },
280 {
281 `Group1: <addr1@example.com>;, Group 2: addr2@example.com;, John <addr3@example.com>`,
282 []*Address{
283 {
284 Name: "",
285 Address: "addr1@example.com",
286 },
287 {
288 Name: "",
289 Address: "addr2@example.com",
290 },
291 {
292 Name: "John",
293 Address: "addr3@example.com",
294 },
295 },
296 },
David Symondsffd01f22011-06-09 10:18:36 +1000297 // RFC 2047 "Q"-encoded ISO-8859-1 address.
298 {
299 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
300 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500301 {
David Symondsffd01f22011-06-09 10:18:36 +1000302 Name: `Jörg Doe`,
303 Address: "joerg@example.com",
304 },
305 },
306 },
Russ Cox06e4b062014-09-16 17:40:33 -0400307 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal.
308 {
309 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
310 []*Address{
311 {
312 Name: `Jorg Doe`,
313 Address: "joerg@example.com",
314 },
315 },
316 },
David Symondsffd01f22011-06-09 10:18:36 +1000317 // RFC 2047 "Q"-encoded UTF-8 address.
318 {
319 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`,
320 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500321 {
David Symondsffd01f22011-06-09 10:18:36 +1000322 Name: `Jörg Doe`,
323 Address: "joerg@example.com",
324 },
325 },
326 },
Hiroshi Ioka64914962017-03-04 19:47:19 +0900327 // RFC 2047 "Q"-encoded UTF-8 address with multiple encoded-words.
328 {
329 `=?utf-8?q?J=C3=B6rg?= =?utf-8?q?Doe?= <joerg@example.com>`,
330 []*Address{
331 {
332 Name: `JörgDoe`,
333 Address: "joerg@example.com",
334 },
335 },
336 },
David Symonds63639dd2011-06-10 08:47:27 +1000337 // RFC 2047, Section 8.
338 {
339 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`,
340 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500341 {
David Symonds63639dd2011-06-10 08:47:27 +1000342 Name: `André Pirard`,
343 Address: "PIRARD@vm1.ulg.ac.be",
344 },
345 },
346 },
David Symonds371aa142011-06-14 17:32:47 +1000347 // Custom example of RFC 2047 "B"-encoded ISO-8859-1 address.
348 {
349 `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`,
350 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500351 {
David Symonds371aa142011-06-14 17:32:47 +1000352 Name: `Jörg`,
353 Address: "joerg@example.com",
354 },
355 },
356 },
357 // Custom example of RFC 2047 "B"-encoded UTF-8 address.
358 {
David Symonds371aa142011-06-14 17:32:47 +1000359 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`,
360 []*Address{
Russ Coxdcf1d7b2011-12-02 14:14:25 -0500361 {
David Symonds371aa142011-06-14 17:32:47 +1000362 Name: `Jörg`,
363 Address: "joerg@example.com",
364 },
365 },
366 },
Ryan Slade73b8baa2013-08-08 10:00:24 -0700367 // Custom example with "." in name. For issue 4938
368 {
369 `Asem H. <noreply@example.com>`,
370 []*Address{
371 {
372 Name: `Asem H.`,
373 Address: "noreply@example.com",
374 },
375 },
376 },
Conrad Irwin7f52b432016-02-19 10:12:44 -0800377 // RFC 6532 3.2.3, qtext /= UTF8-non-ascii
378 {
379 `"Gø Pher" <gopher@example.com>`,
380 []*Address{
381 {
382 Name: `Gø Pher`,
383 Address: "gopher@example.com",
384 },
385 },
386 },
387 // RFC 6532 3.2, atext /= UTF8-non-ascii
388 {
389 `µ <micro@example.com>`,
390 []*Address{
391 {
392 Name: `µ`,
393 Address: "micro@example.com",
394 },
395 },
396 },
397 // RFC 6532 3.2.2, local address parts allow UTF-8
398 {
399 `Micro <µ@example.com>`,
400 []*Address{
401 {
402 Name: `Micro`,
403 Address: "µ@example.com",
404 },
405 },
406 },
407 // RFC 6532 3.2.4, domains parts allow UTF-8
408 {
409 `Micro <micro@µ.example.com>`,
410 []*Address{
411 {
412 Name: `Micro`,
413 Address: "micro@µ.example.com",
414 },
415 },
416 },
Russ Cox2bafbe12016-10-26 16:48:53 -0400417 // Issue 14866
418 {
419 `"" <emptystring@example.com>`,
420 []*Address{
421 {
422 Name: "",
423 Address: "emptystring@example.com",
424 },
425 },
426 },
Minaev Mike85983962017-08-07 08:22:21 +0000427 // CFWS
428 {
Michael Stapelbergfcee1892017-11-14 04:46:03 -0800429 `<cfws@example.com> (CFWS (cfws)) (another comment)`,
Minaev Mike85983962017-08-07 08:22:21 +0000430 []*Address{
431 {
432 Name: "",
433 Address: "cfws@example.com",
434 },
435 },
436 },
437 {
Michael Stapelbergfcee1892017-11-14 04:46:03 -0800438 `<cfws@example.com> () (another comment), <cfws2@example.com> (another)`,
Minaev Mike85983962017-08-07 08:22:21 +0000439 []*Address{
440 {
441 Name: "",
442 Address: "cfws@example.com",
443 },
444 {
445 Name: "",
446 Address: "cfws2@example.com",
447 },
448 },
449 },
Michael Stapelbergfcee1892017-11-14 04:46:03 -0800450 // Comment as display name
451 {
452 `john@example.com (John Doe)`,
453 []*Address{
454 {
455 Name: "John Doe",
456 Address: "john@example.com",
457 },
458 },
459 },
460 // Comment and display name
461 {
462 `John Doe <john@example.com> (Joey)`,
463 []*Address{
464 {
465 Name: "John Doe",
466 Address: "john@example.com",
467 },
468 },
469 },
470 // Comment as display name, no space
471 {
472 `john@example.com(John Doe)`,
473 []*Address{
474 {
475 Name: "John Doe",
476 Address: "john@example.com",
477 },
478 },
479 },
480 // Comment as display name, Q-encoded
481 {
482 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
483 []*Address{
484 {
485 Name: "Adam Sjøgren",
486 Address: "asjo@example.com",
487 },
488 },
489 },
490 // Comment as display name, Q-encoded and tab-separated
491 {
492 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?=)`,
493 []*Address{
494 {
495 Name: "Adam Sjøgren",
496 Address: "asjo@example.com",
497 },
498 },
499 },
500 // Nested comment as display name, Q-encoded
501 {
502 `asjo@example.com (Adam =?utf-8?Q?Sj=C3=B8gren?= (Debian))`,
503 []*Address{
504 {
505 Name: "Adam Sjøgren (Debian)",
506 Address: "asjo@example.com",
507 },
508 },
509 },
David Symondsff0198b2011-06-06 16:46:14 +1000510 }
511 for _, test := range tests {
Graham Miller9f807fc2012-10-05 10:08:54 +1000512 if len(test.exp) == 1 {
513 addr, err := ParseAddress(test.addrsStr)
514 if err != nil {
515 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
516 continue
517 }
518 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
519 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
520 }
521 }
522
523 addrs, err := ParseAddressList(test.addrsStr)
David Symondsff0198b2011-06-06 16:46:14 +1000524 if err != nil {
Graham Miller9f807fc2012-10-05 10:08:54 +1000525 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
David Symondsff0198b2011-06-06 16:46:14 +1000526 continue
527 }
528 if !reflect.DeepEqual(addrs, test.exp) {
Graham Miller9f807fc2012-10-05 10:08:54 +1000529 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
David Symondsff0198b2011-06-06 16:46:14 +1000530 }
531 }
532}
David Symonds5c32c962011-06-08 13:32:47 +1000533
Alexandre Cesaro1defd222015-05-25 18:58:19 +0200534func TestAddressParser(t *testing.T) {
535 tests := []struct {
536 addrsStr string
537 exp []*Address
538 }{
539 // Bare address
540 {
541 `jdoe@machine.example`,
542 []*Address{{
543 Address: "jdoe@machine.example",
544 }},
545 },
546 // RFC 5322, Appendix A.1.1
547 {
548 `John Doe <jdoe@machine.example>`,
549 []*Address{{
550 Name: "John Doe",
551 Address: "jdoe@machine.example",
552 }},
553 },
554 // RFC 5322, Appendix A.1.2
555 {
556 `"Joe Q. Public" <john.q.public@example.com>`,
557 []*Address{{
558 Name: "Joe Q. Public",
559 Address: "john.q.public@example.com",
560 }},
561 },
562 {
563 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`,
564 []*Address{
565 {
566 Name: "Mary Smith",
567 Address: "mary@x.test",
568 },
569 {
570 Address: "jdoe@example.org",
571 },
572 {
573 Name: "Who?",
574 Address: "one@y.test",
575 },
576 },
577 },
578 {
579 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`,
580 []*Address{
581 {
582 Address: "boss@nil.test",
583 },
584 {
585 Name: `Giant; "Big" Box`,
586 Address: "sysservices@example.net",
587 },
588 },
589 },
590 // RFC 2047 "Q"-encoded ISO-8859-1 address.
591 {
592 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`,
593 []*Address{
594 {
595 Name: `Jörg Doe`,
596 Address: "joerg@example.com",
597 },
598 },
599 },
600 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal.
601 {
602 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`,
603 []*Address{
604 {
605 Name: `Jorg Doe`,
606 Address: "joerg@example.com",
607 },
608 },
609 },
610 // RFC 2047 "Q"-encoded ISO-8859-15 address.
611 {
612 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`,
613 []*Address{
614 {
615 Name: `Jörg Doe`,
616 Address: "joerg@example.com",
617 },
618 },
619 },
620 // RFC 2047 "B"-encoded windows-1252 address.
621 {
622 `=?windows-1252?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 // Custom example of RFC 2047 "B"-encoded ISO-8859-15 address.
631 {
632 `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`,
633 []*Address{
634 {
635 Name: `Jörg`,
636 Address: "joerg@example.com",
637 },
638 },
639 },
640 // Custom example of RFC 2047 "B"-encoded UTF-8 address.
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 // Custom example with "." in name. For issue 4938
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 ap := AddressParser{WordDecoder: &mime.WordDecoder{
663 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
664 in, err := ioutil.ReadAll(input)
665 if err != nil {
666 return nil, err
667 }
668
669 switch charset {
670 case "iso-8859-15":
671 in = bytes.Replace(in, []byte("\xf6"), []byte("ö"), -1)
672 case "windows-1252":
673 in = bytes.Replace(in, []byte("\xe9"), []byte("é"), -1)
674 }
675
676 return bytes.NewReader(in), nil
677 },
678 }}
679
680 for _, test := range tests {
681 if len(test.exp) == 1 {
682 addr, err := ap.Parse(test.addrsStr)
683 if err != nil {
684 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err)
685 continue
686 }
687 if !reflect.DeepEqual([]*Address{addr}, test.exp) {
688 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp)
689 }
690 }
691
692 addrs, err := ap.ParseList(test.addrsStr)
693 if err != nil {
694 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err)
695 continue
696 }
697 if !reflect.DeepEqual(addrs, test.exp) {
698 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp)
699 }
700 }
701}
702
Russ Coxe8cc0832015-12-03 16:00:04 -0500703func TestAddressString(t *testing.T) {
David Symonds5c32c962011-06-08 13:32:47 +1000704 tests := []struct {
705 addr *Address
706 exp string
707 }{
708 {
709 &Address{Address: "bob@example.com"},
710 "<bob@example.com>",
711 },
MathiasBdaaa4502015-04-10 12:14:16 +0200712 { // quoted local parts: RFC 5322, 3.4.1. and 3.2.4.
713 &Address{Address: `my@idiot@address@example.com`},
714 `<"my@idiot@address"@example.com>`,
715 },
716 { // quoted local parts
717 &Address{Address: ` @example.com`},
718 `<" "@example.com>`,
719 },
David Symonds5c32c962011-06-08 13:32:47 +1000720 {
721 &Address{Name: "Bob", Address: "bob@example.com"},
722 `"Bob" <bob@example.com>`,
723 },
724 {
725 // note the ö (o with an umlaut)
726 &Address{Name: "Böb", Address: "bob@example.com"},
727 `=?utf-8?q?B=C3=B6b?= <bob@example.com>`,
728 },
Jakub Ryszard Czarnowiczd3b95672014-02-07 10:49:10 +1100729 {
730 &Address{Name: "Bob Jane", Address: "bob@example.com"},
731 `"Bob Jane" <bob@example.com>`,
732 },
733 {
734 &Address{Name: "Böb Jacöb", Address: "bob@example.com"},
735 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`,
736 },
David Symonds1d75b402015-08-11 15:05:12 +1000737 { // https://golang.org/issue/12098
738 &Address{Name: "Rob", Address: ""},
David Symonds1052b432015-08-11 16:36:40 +1000739 `"Rob" <@>`,
David Symonds1d75b402015-08-11 15:05:12 +1000740 },
741 { // https://golang.org/issue/12098
742 &Address{Name: "Rob", Address: "@"},
743 `"Rob" <@>`,
744 },
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200745 {
746 &Address{Name: "Böb, Jacöb", Address: "bob@example.com"},
747 `=?utf-8?b?QsO2YiwgSmFjw7Zi?= <bob@example.com>`,
748 },
Russ Coxe8cc0832015-12-03 16:00:04 -0500749 {
750 &Address{Name: "=??Q?x?=", Address: "hello@world.com"},
751 `"=??Q?x?=" <hello@world.com>`,
752 },
753 {
754 &Address{Name: "=?hello", Address: "hello@world.com"},
755 `"=?hello" <hello@world.com>`,
756 },
757 {
758 &Address{Name: "world?=", Address: "hello@world.com"},
759 `"world?=" <hello@world.com>`,
760 },
Conrad Irwin7f52b432016-02-19 10:12:44 -0800761 {
762 // should q-encode even for invalid utf-8.
763 &Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"},
764 "=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>",
765 },
David Symonds5c32c962011-06-08 13:32:47 +1000766 }
767 for _, test := range tests {
768 s := test.addr.String()
769 if s != test.exp {
770 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp)
Russ Coxe8cc0832015-12-03 16:00:04 -0500771 continue
772 }
773
774 // Check round-trip.
775 if test.addr.Address != "" && test.addr.Address != "@" {
776 a, err := ParseAddress(test.exp)
777 if err != nil {
778 t.Errorf("ParseAddress(%#q): %v", test.exp, err)
779 continue
780 }
781 if a.Name != test.addr.Name || a.Address != test.addr.Address {
782 t.Errorf("ParseAddress(%#q) = %#v, want %#v", test.exp, a, test.addr)
783 }
David Symonds5c32c962011-06-08 13:32:47 +1000784 }
785 }
786}
MathiasBdaaa4502015-04-10 12:14:16 +0200787
788// Check if all valid addresses can be parsed, formatted and parsed again
789func TestAddressParsingAndFormatting(t *testing.T) {
Joe Tsaib53088a2017-12-01 00:59:45 +0000790
MathiasBdaaa4502015-04-10 12:14:16 +0200791 // Should pass
792 tests := []string{
793 `<Bob@example.com>`,
794 `<bob.bob@example.com>`,
795 `<".bob"@example.com>`,
796 `<" "@example.com>`,
797 `<some.mail-with-dash@example.com>`,
798 `<"dot.and space"@example.com>`,
799 `<"very.unusual.@.unusual.com"@example.com>`,
800 `<admin@mailserver1>`,
801 `<postmaster@localhost>`,
802 "<#!$%&'*+-/=?^_`{}|~@example.org>",
803 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`, // escaped quotes
804 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`, // escaped backslashes
805 `<"Abc\\@def"@example.com>`,
806 `<"Joe\\Blow"@example.com>`,
807 `<test1/test2=test3@example.com>`,
808 `<def!xyz%abc@example.com>`,
809 `<_somename@example.com>`,
810 `<joe@uk>`,
811 `<~@example.com>`,
812 `<"..."@test.com>`,
813 `<"john..doe"@example.com>`,
814 `<"john.doe."@example.com>`,
815 `<".john.doe"@example.com>`,
816 `<"."@example.com>`,
817 `<".."@example.com>`,
MathiasBbd1efd52015-07-31 12:25:06 +0200818 `<"0:"@0>`,
MathiasBdaaa4502015-04-10 12:14:16 +0200819 }
820
821 for _, test := range tests {
822 addr, err := ParseAddress(test)
823 if err != nil {
824 t.Errorf("Couldn't parse address %s: %s", test, err.Error())
825 continue
826 }
827 str := addr.String()
828 addr, err = ParseAddress(str)
829 if err != nil {
830 t.Errorf("ParseAddr(%q) error: %v", test, err)
831 continue
832 }
833
834 if addr.String() != test {
835 t.Errorf("String() round-trip = %q; want %q", addr, test)
836 continue
837 }
838
839 }
840
841 // Should fail
842 badTests := []string{
843 `<Abc.example.com>`,
844 `<A@b@c@example.com>`,
845 `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`,
846 `<just"not"right@example.com>`,
847 `<this is"not\allowed@example.com>`,
848 `<this\ still\"not\\allowed@example.com>`,
849 `<john..doe@example.com>`,
850 `<john.doe@example..com>`,
851 `<john.doe@example..com>`,
852 `<john.doe.@example.com>`,
853 `<john.doe.@.example.com>`,
854 `<.john.doe@example.com>`,
855 `<@example.com>`,
856 `<.@example.com>`,
857 `<test@.>`,
858 `< @example.com>`,
859 `<""test""blah""@example.com>`,
MathiasBbd1efd52015-07-31 12:25:06 +0200860 `<""@0>`,
MathiasBdaaa4502015-04-10 12:14:16 +0200861 }
862
863 for _, test := range badTests {
864 _, err := ParseAddress(test)
865 if err == nil {
866 t.Errorf("Should have failed to parse address: %s", test)
867 continue
868 }
869
870 }
871
872}
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200873
874func TestAddressFormattingAndParsing(t *testing.T) {
875 tests := []*Address{
Mikio Harad41d4732015-12-04 10:58:13 +0900876 {Name: "@lïce", Address: "alice@example.com"},
877 {Name: "Böb O'Connor", Address: "bob@example.com"},
878 {Name: "???", Address: "bob@example.com"},
879 {Name: "Böb ???", Address: "bob@example.com"},
880 {Name: "Böb (Jacöb)", Address: "bob@example.com"},
881 {Name: "à#$%&'(),.:;<>@[]^`{|}~'", Address: "bob@example.com"},
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200882 // https://golang.org/issue/11292
Mikio Harad41d4732015-12-04 10:58:13 +0900883 {Name: "\"\\\x1f,\"", Address: "0@0"},
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200884 // https://golang.org/issue/12782
Mikio Harad41d4732015-12-04 10:58:13 +0900885 {Name: "naé, mée", Address: "test.mail@gmail.com"},
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200886 }
887
Alexandre Cesaro69c09af2015-12-02 23:28:49 +0100888 for i, test := range tests {
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200889 parsed, err := ParseAddress(test.String())
890 if err != nil {
Alexandre Cesaro69c09af2015-12-02 23:28:49 +0100891 t.Errorf("test #%d: ParseAddr(%q) error: %v", i, test.String(), err)
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200892 continue
893 }
894 if parsed.Name != test.Name {
Alexandre Cesaro69c09af2015-12-02 23:28:49 +0100895 t.Errorf("test #%d: Parsed name = %q; want %q", i, parsed.Name, test.Name)
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200896 }
897 if parsed.Address != test.Address {
Alexandre Cesaro69c09af2015-12-02 23:28:49 +0100898 t.Errorf("test #%d: Parsed address = %q; want %q", i, parsed.Address, test.Address)
Alexandre Cesaro2cb265d2015-10-20 17:30:21 +0200899 }
900 }
901}