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