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 (a] machine.example> 26 To: Mary Smith <mary (a] example.net> 27 Subject: Saying Hello 28 Date: Fri, 21 Nov 1997 09:55:06 -0600 29 Message-ID: <1234 (a] 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 (a] machine.example>"}, 36 "To": []string{"Mary Smith <mary (a] example.net>"}, 37 "Subject": []string{"Saying Hello"}, 38 "Date": []string{"Fri, 21 Nov 1997 09:55:06 -0600"}, 39 "Message-Id": []string{"<1234 (a] 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 // RFC5322, 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 const txt = "=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown (a] gmail.com>" 124 _, err := ParseAddress(txt) 125 if err == nil || !strings.Contains(err.Error(), "charset not supported") { 126 t.Errorf(`mail.ParseAddress(%q) err: %q, want ".*charset not supported.*"`, txt, err) 127 } 128 } 129 130 func TestAddressParsing(t *testing.T) { 131 tests := []struct { 132 addrsStr string 133 exp []*Address 134 }{ 135 // Bare address 136 { 137 `jdoe (a] machine.example`, 138 []*Address{{ 139 Address: "jdoe (a] machine.example", 140 }}, 141 }, 142 // RFC 5322, Appendix A.1.1 143 { 144 `John Doe <jdoe (a] machine.example>`, 145 []*Address{{ 146 Name: "John Doe", 147 Address: "jdoe (a] machine.example", 148 }}, 149 }, 150 // RFC 5322, Appendix A.1.2 151 { 152 `"Joe Q. Public" <john.q.public (a] example.com>`, 153 []*Address{{ 154 Name: "Joe Q. Public", 155 Address: "john.q.public (a] example.com", 156 }}, 157 }, 158 { 159 `Mary Smith <mary (a] x.test>, jdoe (a] example.org, Who? <one (a] y.test>`, 160 []*Address{ 161 { 162 Name: "Mary Smith", 163 Address: "mary (a] x.test", 164 }, 165 { 166 Address: "jdoe (a] example.org", 167 }, 168 { 169 Name: "Who?", 170 Address: "one (a] y.test", 171 }, 172 }, 173 }, 174 { 175 `<boss (a] nil.test>, "Giant; \"Big\" Box" <sysservices (a] example.net>`, 176 []*Address{ 177 { 178 Address: "boss (a] nil.test", 179 }, 180 { 181 Name: `Giant; "Big" Box`, 182 Address: "sysservices (a] example.net", 183 }, 184 }, 185 }, 186 // RFC 5322, Appendix A.1.3 187 // TODO(dsymonds): Group addresses. 188 189 // RFC 2047 "Q"-encoded ISO-8859-1 address. 190 { 191 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg (a] example.com>`, 192 []*Address{ 193 { 194 Name: `Jrg Doe`, 195 Address: "joerg (a] example.com", 196 }, 197 }, 198 }, 199 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. 200 { 201 `=?us-ascii?q?J=6Frg_Doe?= <joerg (a] example.com>`, 202 []*Address{ 203 { 204 Name: `Jorg Doe`, 205 Address: "joerg (a] example.com", 206 }, 207 }, 208 }, 209 // RFC 2047 "Q"-encoded UTF-8 address. 210 { 211 `=?utf-8?q?J=C3=B6rg_Doe?= <joerg (a] example.com>`, 212 []*Address{ 213 { 214 Name: `Jrg Doe`, 215 Address: "joerg (a] example.com", 216 }, 217 }, 218 }, 219 // RFC 2047, Section 8. 220 { 221 `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD (a] vm1.ulg.ac.be>`, 222 []*Address{ 223 { 224 Name: `Andr Pirard`, 225 Address: "PIRARD (a] vm1.ulg.ac.be", 226 }, 227 }, 228 }, 229 // Custom example of RFC 2047 "B"-encoded ISO-8859-1 address. 230 { 231 `=?ISO-8859-1?B?SvZyZw==?= <joerg (a] example.com>`, 232 []*Address{ 233 { 234 Name: `Jrg`, 235 Address: "joerg (a] example.com", 236 }, 237 }, 238 }, 239 // Custom example of RFC 2047 "B"-encoded UTF-8 address. 240 { 241 `=?UTF-8?B?SsO2cmc=?= <joerg (a] example.com>`, 242 []*Address{ 243 { 244 Name: `Jrg`, 245 Address: "joerg (a] example.com", 246 }, 247 }, 248 }, 249 // Custom example with "." in name. For issue 4938 250 { 251 `Asem H. <noreply (a] example.com>`, 252 []*Address{ 253 { 254 Name: `Asem H.`, 255 Address: "noreply (a] example.com", 256 }, 257 }, 258 }, 259 } 260 for _, test := range tests { 261 if len(test.exp) == 1 { 262 addr, err := ParseAddress(test.addrsStr) 263 if err != nil { 264 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) 265 continue 266 } 267 if !reflect.DeepEqual([]*Address{addr}, test.exp) { 268 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) 269 } 270 } 271 272 addrs, err := ParseAddressList(test.addrsStr) 273 if err != nil { 274 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) 275 continue 276 } 277 if !reflect.DeepEqual(addrs, test.exp) { 278 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) 279 } 280 } 281 } 282 283 func TestAddressParser(t *testing.T) { 284 tests := []struct { 285 addrsStr string 286 exp []*Address 287 }{ 288 // Bare address 289 { 290 `jdoe (a] machine.example`, 291 []*Address{{ 292 Address: "jdoe (a] machine.example", 293 }}, 294 }, 295 // RFC 5322, Appendix A.1.1 296 { 297 `John Doe <jdoe (a] machine.example>`, 298 []*Address{{ 299 Name: "John Doe", 300 Address: "jdoe (a] machine.example", 301 }}, 302 }, 303 // RFC 5322, Appendix A.1.2 304 { 305 `"Joe Q. Public" <john.q.public (a] example.com>`, 306 []*Address{{ 307 Name: "Joe Q. Public", 308 Address: "john.q.public (a] example.com", 309 }}, 310 }, 311 { 312 `Mary Smith <mary (a] x.test>, jdoe (a] example.org, Who? <one (a] y.test>`, 313 []*Address{ 314 { 315 Name: "Mary Smith", 316 Address: "mary (a] x.test", 317 }, 318 { 319 Address: "jdoe (a] example.org", 320 }, 321 { 322 Name: "Who?", 323 Address: "one (a] y.test", 324 }, 325 }, 326 }, 327 { 328 `<boss (a] nil.test>, "Giant; \"Big\" Box" <sysservices (a] example.net>`, 329 []*Address{ 330 { 331 Address: "boss (a] nil.test", 332 }, 333 { 334 Name: `Giant; "Big" Box`, 335 Address: "sysservices (a] example.net", 336 }, 337 }, 338 }, 339 // RFC 2047 "Q"-encoded ISO-8859-1 address. 340 { 341 `=?iso-8859-1?q?J=F6rg_Doe?= <joerg (a] example.com>`, 342 []*Address{ 343 { 344 Name: `Jrg Doe`, 345 Address: "joerg (a] example.com", 346 }, 347 }, 348 }, 349 // RFC 2047 "Q"-encoded US-ASCII address. Dumb but legal. 350 { 351 `=?us-ascii?q?J=6Frg_Doe?= <joerg (a] example.com>`, 352 []*Address{ 353 { 354 Name: `Jorg Doe`, 355 Address: "joerg (a] example.com", 356 }, 357 }, 358 }, 359 // RFC 2047 "Q"-encoded ISO-8859-15 address. 360 { 361 `=?ISO-8859-15?Q?J=F6rg_Doe?= <joerg (a] example.com>`, 362 []*Address{ 363 { 364 Name: `Jrg Doe`, 365 Address: "joerg (a] example.com", 366 }, 367 }, 368 }, 369 // RFC 2047 "B"-encoded windows-1252 address. 370 { 371 `=?windows-1252?q?Andr=E9?= Pirard <PIRARD (a] vm1.ulg.ac.be>`, 372 []*Address{ 373 { 374 Name: `Andr Pirard`, 375 Address: "PIRARD (a] vm1.ulg.ac.be", 376 }, 377 }, 378 }, 379 // Custom example of RFC 2047 "B"-encoded ISO-8859-15 address. 380 { 381 `=?ISO-8859-15?B?SvZyZw==?= <joerg (a] example.com>`, 382 []*Address{ 383 { 384 Name: `Jrg`, 385 Address: "joerg (a] example.com", 386 }, 387 }, 388 }, 389 // Custom example of RFC 2047 "B"-encoded UTF-8 address. 390 { 391 `=?UTF-8?B?SsO2cmc=?= <joerg (a] example.com>`, 392 []*Address{ 393 { 394 Name: `Jrg`, 395 Address: "joerg (a] example.com", 396 }, 397 }, 398 }, 399 // Custom example with "." in name. For issue 4938 400 { 401 `Asem H. <noreply (a] example.com>`, 402 []*Address{ 403 { 404 Name: `Asem H.`, 405 Address: "noreply (a] example.com", 406 }, 407 }, 408 }, 409 } 410 411 ap := AddressParser{WordDecoder: &mime.WordDecoder{ 412 CharsetReader: func(charset string, input io.Reader) (io.Reader, error) { 413 in, err := ioutil.ReadAll(input) 414 if err != nil { 415 return nil, err 416 } 417 418 switch charset { 419 case "iso-8859-15": 420 in = bytes.Replace(in, []byte("\xf6"), []byte(""), -1) 421 case "windows-1252": 422 in = bytes.Replace(in, []byte("\xe9"), []byte(""), -1) 423 } 424 425 return bytes.NewReader(in), nil 426 }, 427 }} 428 429 for _, test := range tests { 430 if len(test.exp) == 1 { 431 addr, err := ap.Parse(test.addrsStr) 432 if err != nil { 433 t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) 434 continue 435 } 436 if !reflect.DeepEqual([]*Address{addr}, test.exp) { 437 t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) 438 } 439 } 440 441 addrs, err := ap.ParseList(test.addrsStr) 442 if err != nil { 443 t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) 444 continue 445 } 446 if !reflect.DeepEqual(addrs, test.exp) { 447 t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) 448 } 449 } 450 } 451 452 func TestAddressFormatting(t *testing.T) { 453 tests := []struct { 454 addr *Address 455 exp string 456 }{ 457 { 458 &Address{Address: "bob (a] example.com"}, 459 "<bob (a] example.com>", 460 }, 461 { // quoted local parts: RFC 5322, 3.4.1. and 3.2.4. 462 &Address{Address: `my@idiot@address (a] example.com`}, 463 `<"my@idiot@address"@example.com>`, 464 }, 465 { // quoted local parts 466 &Address{Address: ` @example.com`}, 467 `<" "@example.com>`, 468 }, 469 { 470 &Address{Name: "Bob", Address: "bob (a] example.com"}, 471 `"Bob" <bob (a] example.com>`, 472 }, 473 { 474 // note the (o with an umlaut) 475 &Address{Name: "Bb", Address: "bob (a] example.com"}, 476 `=?utf-8?q?B=C3=B6b?= <bob (a] example.com>`, 477 }, 478 { 479 &Address{Name: "Bob Jane", Address: "bob (a] example.com"}, 480 `"Bob Jane" <bob (a] example.com>`, 481 }, 482 { 483 &Address{Name: "Bb Jacb", Address: "bob (a] example.com"}, 484 `=?utf-8?q?B=C3=B6b_Jac=C3=B6b?= <bob (a] example.com>`, 485 }, 486 { // https://golang.org/issue/12098 487 &Address{Name: "Rob", Address: ""}, 488 `"Rob" <@>`, 489 }, 490 { // https://golang.org/issue/12098 491 &Address{Name: "Rob", Address: "@"}, 492 `"Rob" <@>`, 493 }, 494 } 495 for _, test := range tests { 496 s := test.addr.String() 497 if s != test.exp { 498 t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp) 499 } 500 } 501 } 502 503 // Check if all valid addresses can be parsed, formatted and parsed again 504 func TestAddressParsingAndFormatting(t *testing.T) { 505 506 // Should pass 507 tests := []string{ 508 `<Bob (a] example.com>`, 509 `<bob.bob (a] example.com>`, 510 `<".bob"@example.com>`, 511 `<" "@example.com>`, 512 `<some.mail-with-dash (a] example.com>`, 513 `<"dot.and space"@example.com>`, 514 `<"very.unusual. (a] .unusual.com"@example.com>`, 515 `<admin@mailserver1>`, 516 `<postmaster@localhost>`, 517 "<#!$%&'*+-/=?^_`{}|~@example.org>", 518 `<"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com>`, // escaped quotes 519 `<"()<>[]:,;@\\\"!#$%&'*+-/=?^_{}| ~.a"@example.org>`, // escaped backslashes 520 `<"Abc\\@def"@example.com>`, 521 `<"Joe\\Blow"@example.com>`, 522 `<test1/test2=test3 (a] example.com>`, 523 `<def!xyz%abc (a] example.com>`, 524 `<_somename (a] example.com>`, 525 `<joe@uk>`, 526 `<~@example.com>`, 527 `<"..."@test.com>`, 528 `<"john..doe"@example.com>`, 529 `<"john.doe."@example.com>`, 530 `<".john.doe"@example.com>`, 531 `<"."@example.com>`, 532 `<".."@example.com>`, 533 `<"0:"@0>`, 534 } 535 536 for _, test := range tests { 537 addr, err := ParseAddress(test) 538 if err != nil { 539 t.Errorf("Couldn't parse address %s: %s", test, err.Error()) 540 continue 541 } 542 str := addr.String() 543 addr, err = ParseAddress(str) 544 if err != nil { 545 t.Errorf("ParseAddr(%q) error: %v", test, err) 546 continue 547 } 548 549 if addr.String() != test { 550 t.Errorf("String() round-trip = %q; want %q", addr, test) 551 continue 552 } 553 554 } 555 556 // Should fail 557 badTests := []string{ 558 `<Abc.example.com>`, 559 `<A@b@c (a] example.com>`, 560 `<a"b(c)d,e:f;g<h>i[j\k]l (a] example.com>`, 561 `<just"not"right (a] example.com>`, 562 `<this is"not\allowed (a] example.com>`, 563 `<this\ still\"not\\allowed (a] example.com>`, 564 `<john..doe (a] example.com>`, 565 `<john.doe (a] example..com>`, 566 `<john.doe (a] example..com>`, 567 `<john.doe. (a] example.com>`, 568 `<john.doe. (a] .example.com>`, 569 `<.john.doe (a] example.com>`, 570 `<@example.com>`, 571 `<. (a] example.com>`, 572 `<test@.>`, 573 `< @example.com>`, 574 `<""test""blah""@example.com>`, 575 `<""@0>`, 576 "<\"\t0\"@0>", 577 } 578 579 for _, test := range badTests { 580 _, err := ParseAddress(test) 581 if err == nil { 582 t.Errorf("Should have failed to parse address: %s", test) 583 continue 584 } 585 586 } 587 588 } 589