github.com/sbinet/go@v0.0.0-20160827155028-54d7de7dd62b/src/net/mail/message_test.go (about) 1 // 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 5 package mail 6 7 import ( 8 "bytes" 9 "io" 10 "io/ioutil" 11 "mime" 12 "reflect" 13 "strings" 14 "testing" 15 "time" 16 ) 17 18 var 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> 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 func 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 69 func 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 85 func TestDateParsing(t *testing.T) { 86 tests := []struct { 87 dateStr string 88 exp time.Time 89 }{ 90 // RFC 5322, Appendix A.1.1 91 { 92 "Fri, 21 Nov 1997 09:55:06 -0600", 93 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)), 94 }, 95 // RFC 5322, Appendix A.6.2 96 // Obsolete date. 97 { 98 "21 Nov 97 09:55:06 GMT", 99 time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)), 100 }, 101 // 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 }, 106 } 107 for _, test := range tests { 108 hdr := Header{ 109 "Date": []string{test.dateStr}, 110 } 111 date, err := hdr.Date() 112 if err != nil { 113 t.Errorf("Failed parsing %q: %v", test.dateStr, err) 114 continue 115 } 116 if !date.Equal(test.exp) { 117 t.Errorf("Parse of %q: got %+v, want %+v", test.dateStr, date, test.exp) 118 } 119 } 120 } 121 122 func TestAddressParsingError(t *testing.T) { 123 mustErrTestCases := [...]struct { 124 text string 125 wantErrText string 126 }{ 127 0: {"=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown@gmail.com>", "charset not supported"}, 128 1: {"a@gmail.com b@gmail.com", "expected single address"}, 129 2: {string([]byte{0xed, 0xa0, 0x80}) + " <micro@example.net>", "invalid utf-8 in address"}, 130 3: {"\"" + string([]byte{0xed, 0xa0, 0x80}) + "\" <half-surrogate@example.com>", "invalid utf-8 in quoted-string"}, 131 4: {"\"\\" + string([]byte{0x80}) + "\" <escaped-invalid-unicode@example.net>", "invalid utf-8 in quoted-string"}, 132 5: {"\"\x00\" <null@example.net>", "bad character in quoted-string"}, 133 6: {"\"\\\x00\" <escaped-null@example.net>", "bad character in quoted-string"}, 134 } 135 136 for i, tc := range mustErrTestCases { 137 _, err := ParseAddress(tc.text) 138 if err == nil || !strings.Contains(err.Error(), tc.wantErrText) { 139 t.Errorf(`mail.ParseAddress(%q) #%d want %q, got %v`, tc.text, i, tc.wantErrText, err) 140 } 141 } 142 } 143 144 func TestAddressParsing(t *testing.T) { 145 tests := []struct { 146 addrsStr string 147 exp []*Address 148 }{ 149 // Bare address 150 { 151 `jdoe@machine.example`, 152 []*Address{{ 153 Address: "jdoe@machine.example", 154 }}, 155 }, 156 // RFC 5322, Appendix A.1.1 157 { 158 `John Doe <jdoe@machine.example>`, 159 []*Address{{ 160 Name: "John Doe", 161 Address: "jdoe@machine.example", 162 }}, 163 }, 164 // RFC 5322, Appendix A.1.2 165 { 166 `"Joe Q. Public" <john.q.public@example.com>`, 167 []*Address{{ 168 Name: "Joe Q. Public", 169 Address: "john.q.public@example.com", 170 }}, 171 }, 172 { 173 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`, 174 []*Address{ 175 { 176 Name: "Mary Smith", 177 Address: "mary@x.test", 178 }, 179 { 180 Address: "jdoe@example.org", 181 }, 182 { 183 Name: "Who?", 184 Address: "one@y.test", 185 }, 186 }, 187 }, 188 { 189 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`, 190 []*Address{ 191 { 192 Address: "boss@nil.test", 193 }, 194 { 195 Name: `Giant; "Big" Box`, 196 Address: "sysservices@example.net", 197 }, 198 }, 199 }, 200 // RFC 5322, Appendix A.1.3 201 // TODO(dsymonds): Group addresses. 202 203 // RFC 2047 "Q"-encoded ISO-8859-1 address. 204 { 205 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`, 206 []*Address{ 207 { 208 Name: `Jörg Doe`, 209 Address: "joerg@example.com", 210 }, 211 }, 212 }, 213 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. 214 { 215 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`, 216 []*Address{ 217 { 218 Name: `Jorg Doe`, 219 Address: "joerg@example.com", 220 }, 221 }, 222 }, 223 // RFC 2047 "Q"-encoded UTF-8 address. 224 { 225 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`, 226 []*Address{ 227 { 228 Name: `Jörg Doe`, 229 Address: "joerg@example.com", 230 }, 231 }, 232 }, 233 // RFC 2047, Section 8. 234 { 235 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`, 236 []*Address{ 237 { 238 Name: `André Pirard`, 239 Address: "PIRARD@vm1.ulg.ac.be", 240 }, 241 }, 242 }, 243 // Custom example of RFC 2047 "B"-encoded ISO-8859-1 address. 244 { 245 `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`, 246 []*Address{ 247 { 248 Name: `Jörg`, 249 Address: "joerg@example.com", 250 }, 251 }, 252 }, 253 // Custom example of RFC 2047 "B"-encoded UTF-8 address. 254 { 255 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`, 256 []*Address{ 257 { 258 Name: `Jörg`, 259 Address: "joerg@example.com", 260 }, 261 }, 262 }, 263 // Custom example with "." in name. For issue 4938 264 { 265 `Asem H. <noreply@example.com>`, 266 []*Address{ 267 { 268 Name: `Asem H.`, 269 Address: "noreply@example.com", 270 }, 271 }, 272 }, 273 // RFC 6532 3.2.3, qtext /= UTF8-non-ascii 274 { 275 `"Gø Pher" <gopher@example.com>`, 276 []*Address{ 277 { 278 Name: `Gø Pher`, 279 Address: "gopher@example.com", 280 }, 281 }, 282 }, 283 // RFC 6532 3.2, atext /= UTF8-non-ascii 284 { 285 `µ <micro@example.com>`, 286 []*Address{ 287 { 288 Name: `µ`, 289 Address: "micro@example.com", 290 }, 291 }, 292 }, 293 // RFC 6532 3.2.2, local address parts allow UTF-8 294 { 295 `Micro <µ@example.com>`, 296 []*Address{ 297 { 298 Name: `Micro`, 299 Address: "µ@example.com", 300 }, 301 }, 302 }, 303 // RFC 6532 3.2.4, domains parts allow UTF-8 304 { 305 `Micro <micro@µ.example.com>`, 306 []*Address{ 307 { 308 Name: `Micro`, 309 Address: "micro@µ.example.com", 310 }, 311 }, 312 }, 313 } 314 for _, test := range tests { 315 if len(test.exp) == 1 { 316 addr, err := ParseAddress(test.addrsStr) 317 if err != nil { 318 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) 319 continue 320 } 321 if !reflect.DeepEqual([]*Address{addr}, test.exp) { 322 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) 323 } 324 } 325 326 addrs, err := ParseAddressList(test.addrsStr) 327 if err != nil { 328 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) 329 continue 330 } 331 if !reflect.DeepEqual(addrs, test.exp) { 332 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) 333 } 334 } 335 } 336 337 func TestAddressParser(t *testing.T) { 338 tests := []struct { 339 addrsStr string 340 exp []*Address 341 }{ 342 // Bare address 343 { 344 `jdoe@machine.example`, 345 []*Address{{ 346 Address: "jdoe@machine.example", 347 }}, 348 }, 349 // RFC 5322, Appendix A.1.1 350 { 351 `John Doe <jdoe@machine.example>`, 352 []*Address{{ 353 Name: "John Doe", 354 Address: "jdoe@machine.example", 355 }}, 356 }, 357 // RFC 5322, Appendix A.1.2 358 { 359 `"Joe Q. Public" <john.q.public@example.com>`, 360 []*Address{{ 361 Name: "Joe Q. Public", 362 Address: "john.q.public@example.com", 363 }}, 364 }, 365 { 366 `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`, 367 []*Address{ 368 { 369 Name: "Mary Smith", 370 Address: "mary@x.test", 371 }, 372 { 373 Address: "jdoe@example.org", 374 }, 375 { 376 Name: "Who?", 377 Address: "one@y.test", 378 }, 379 }, 380 }, 381 { 382 `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`, 383 []*Address{ 384 { 385 Address: "boss@nil.test", 386 }, 387 { 388 Name: `Giant; "Big" Box`, 389 Address: "sysservices@example.net", 390 }, 391 }, 392 }, 393 // RFC 2047 "Q"-encoded ISO-8859-1 address. 394 { 395 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`, 396 []*Address{ 397 { 398 Name: `Jörg Doe`, 399 Address: "joerg@example.com", 400 }, 401 }, 402 }, 403 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. 404 { 405 `=?us-ascii?q?J=6Frg_Doe?= <joerg@example.com>`, 406 []*Address{ 407 { 408 Name: `Jorg Doe`, 409 Address: "joerg@example.com", 410 }, 411 }, 412 }, 413 // RFC 2047 "Q"-encoded ISO-8859-15 address. 414 { 415 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg@example.com>`, 416 []*Address{ 417 { 418 Name: `Jörg Doe`, 419 Address: "joerg@example.com", 420 }, 421 }, 422 }, 423 // RFC 2047 "B"-encoded windows-1252 address. 424 { 425 `=?windows-1252?q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`, 426 []*Address{ 427 { 428 Name: `André Pirard`, 429 Address: "PIRARD@vm1.ulg.ac.be", 430 }, 431 }, 432 }, 433 // Custom example of RFC 2047 "B"-encoded ISO-8859-15 address. 434 { 435 `=?ISO-8859-15?B?SvZyZw==?= <joerg@example.com>`, 436 []*Address{ 437 { 438 Name: `Jörg`, 439 Address: "joerg@example.com", 440 }, 441 }, 442 }, 443 // Custom example of RFC 2047 "B"-encoded UTF-8 address. 444 { 445 `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`, 446 []*Address{ 447 { 448 Name: `Jörg`, 449 Address: "joerg@example.com", 450 }, 451 }, 452 }, 453 // Custom example with "." in name. For issue 4938 454 { 455 `Asem H. <noreply@example.com>`, 456 []*Address{ 457 { 458 Name: `Asem H.`, 459 Address: "noreply@example.com", 460 }, 461 }, 462 }, 463 } 464 465 ap := AddressParser{WordDecoder: &mime.WordDecoder{ 466 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) { 467 in, err := ioutil.ReadAll(input) 468 if err != nil { 469 return nil, err 470 } 471 472 switch charset { 473 case "iso-8859-15": 474 in = bytes.Replace(in, []byte("\xf6"), []byte("ö"), -1) 475 case "windows-1252": 476 in = bytes.Replace(in, []byte("\xe9"), []byte("é"), -1) 477 } 478 479 return bytes.NewReader(in), nil 480 }, 481 }} 482 483 for _, test := range tests { 484 if len(test.exp) == 1 { 485 addr, err := ap.Parse(test.addrsStr) 486 if err != nil { 487 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) 488 continue 489 } 490 if !reflect.DeepEqual([]*Address{addr}, test.exp) { 491 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) 492 } 493 } 494 495 addrs, err := ap.ParseList(test.addrsStr) 496 if err != nil { 497 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) 498 continue 499 } 500 if !reflect.DeepEqual(addrs, test.exp) { 501 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) 502 } 503 } 504 } 505 506 func TestAddressString(t *testing.T) { 507 tests := []struct { 508 addr *Address 509 exp string 510 }{ 511 { 512 &Address{Address: "bob@example.com"}, 513 "<bob@example.com>", 514 }, 515 { // quoted local parts: RFC 5322, 3.4.1. and 3.2.4. 516 &Address{Address: `my@idiot@address@example.com`}, 517 `<"my@idiot@address"@example.com>`, 518 }, 519 { // quoted local parts 520 &Address{Address: ` @example.com`}, 521 `<" "@example.com>`, 522 }, 523 { 524 &Address{Name: "Bob", Address: "bob@example.com"}, 525 `"Bob" <bob@example.com>`, 526 }, 527 { 528 // note the ö (o with an umlaut) 529 &Address{Name: "Böb", Address: "bob@example.com"}, 530 `=?utf-8?q?B=C3=B6b?= <bob@example.com>`, 531 }, 532 { 533 &Address{Name: "Bob Jane", Address: "bob@example.com"}, 534 `"Bob Jane" <bob@example.com>`, 535 }, 536 { 537 &Address{Name: "Böb Jacöb", Address: "bob@example.com"}, 538 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob@example.com>`, 539 }, 540 { // https://golang.org/issue/12098 541 &Address{Name: "Rob", Address: ""}, 542 `"Rob" <@>`, 543 }, 544 { // https://golang.org/issue/12098 545 &Address{Name: "Rob", Address: "@"}, 546 `"Rob" <@>`, 547 }, 548 { 549 &Address{Name: "Böb, Jacöb", Address: "bob@example.com"}, 550 `=?utf-8?b?QsO2YiwgSmFjw7Zi?= <bob@example.com>`, 551 }, 552 { 553 &Address{Name: "=??Q?x?=", Address: "hello@world.com"}, 554 `"=??Q?x?=" <hello@world.com>`, 555 }, 556 { 557 &Address{Name: "=?hello", Address: "hello@world.com"}, 558 `"=?hello" <hello@world.com>`, 559 }, 560 { 561 &Address{Name: "world?=", Address: "hello@world.com"}, 562 `"world?=" <hello@world.com>`, 563 }, 564 { 565 // should q-encode even for invalid utf-8. 566 &Address{Name: string([]byte{0xed, 0xa0, 0x80}), Address: "invalid-utf8@example.net"}, 567 "=?utf-8?q?=ED=A0=80?= <invalid-utf8@example.net>", 568 }, 569 } 570 for _, test := range tests { 571 s := test.addr.String() 572 if s != test.exp { 573 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp) 574 continue 575 } 576 577 // Check round-trip. 578 if test.addr.Address != "" && test.addr.Address != "@" { 579 a, err := ParseAddress(test.exp) 580 if err != nil { 581 t.Errorf("ParseAddress(%#q): %v", test.exp, err) 582 continue 583 } 584 if a.Name != test.addr.Name || a.Address != test.addr.Address { 585 t.Errorf("ParseAddress(%#q) = %#v, want %#v", test.exp, a, test.addr) 586 } 587 } 588 } 589 } 590 591 // Check if all valid addresses can be parsed, formatted and parsed again 592 func TestAddressParsingAndFormatting(t *testing.T) { 593 594 // Should pass 595 tests := []string{ 596 `<Bob@example.com>`, 597 `<bob.bob@example.com>`, 598 `<".bob"@example.com>`, 599 `<" "@example.com>`, 600 `<some.mail-with-dash@example.com>`, 601 `<"dot.and space"@example.com>`, 602 `<"very.unusual.@.unusual.com"@example.com>`, 603 `<admin@mailserver1>`, 604 `<postmaster@localhost>`, 605 "<#!$%&'*+-/=?^_`{}|~@example.org>", 606 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`, // escaped quotes 607 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`, // escaped backslashes 608 `<"Abc\\@def"@example.com>`, 609 `<"Joe\\Blow"@example.com>`, 610 `<test1/test2=test3@example.com>`, 611 `<def!xyz%abc@example.com>`, 612 `<_somename@example.com>`, 613 `<joe@uk>`, 614 `<~@example.com>`, 615 `<"..."@test.com>`, 616 `<"john..doe"@example.com>`, 617 `<"john.doe."@example.com>`, 618 `<".john.doe"@example.com>`, 619 `<"."@example.com>`, 620 `<".."@example.com>`, 621 `<"0:"@0>`, 622 } 623 624 for _, test := range tests { 625 addr, err := ParseAddress(test) 626 if err != nil { 627 t.Errorf("Couldn't parse address %s: %s", test, err.Error()) 628 continue 629 } 630 str := addr.String() 631 addr, err = ParseAddress(str) 632 if err != nil { 633 t.Errorf("ParseAddr(%q) error: %v", test, err) 634 continue 635 } 636 637 if addr.String() != test { 638 t.Errorf("String() round-trip = %q; want %q", addr, test) 639 continue 640 } 641 642 } 643 644 // Should fail 645 badTests := []string{ 646 `<Abc.example.com>`, 647 `<A@b@c@example.com>`, 648 `<a"b(c)d,e:f;g<h>i[j\k]l@example.com>`, 649 `<just"not"right@example.com>`, 650 `<this is"not\allowed@example.com>`, 651 `<this\ still\"not\\allowed@example.com>`, 652 `<john..doe@example.com>`, 653 `<john.doe@example..com>`, 654 `<john.doe@example..com>`, 655 `<john.doe.@example.com>`, 656 `<john.doe.@.example.com>`, 657 `<.john.doe@example.com>`, 658 `<@example.com>`, 659 `<.@example.com>`, 660 `<test@.>`, 661 `< @example.com>`, 662 `<""test""blah""@example.com>`, 663 `<""@0>`, 664 } 665 666 for _, test := range badTests { 667 _, err := ParseAddress(test) 668 if err == nil { 669 t.Errorf("Should have failed to parse address: %s", test) 670 continue 671 } 672 673 } 674 675 } 676 677 func TestAddressFormattingAndParsing(t *testing.T) { 678 tests := []*Address{ 679 {Name: "@lïce", Address: "alice@example.com"}, 680 {Name: "Böb O'Connor", Address: "bob@example.com"}, 681 {Name: "???", Address: "bob@example.com"}, 682 {Name: "Böb ???", Address: "bob@example.com"}, 683 {Name: "Böb (Jacöb)", Address: "bob@example.com"}, 684 {Name: "à#$%&'(),.:;<>@[]^`{|}~'", Address: "bob@example.com"}, 685 // https://golang.org/issue/11292 686 {Name: "\"\\\x1f,\"", Address: "0@0"}, 687 // https://golang.org/issue/12782 688 {Name: "naé, mée", Address: "test.mail@gmail.com"}, 689 } 690 691 for i, test := range tests { 692 parsed, err := ParseAddress(test.String()) 693 if err != nil { 694 t.Errorf("test #%d: ParseAddr(%q) error: %v", i, test.String(), err) 695 continue 696 } 697 if parsed.Name != test.Name { 698 t.Errorf("test #%d: Parsed name = %q; want %q", i, parsed.Name, test.Name) 699 } 700 if parsed.Address != test.Address { 701 t.Errorf("test #%d: Parsed address = %q; want %q", i, parsed.Address, test.Address) 702 } 703 } 704 }