Home | History | Annotate | Download | only in mail
      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