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