David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 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/ioutil" |
| 10 | "reflect" |
| 11 | "testing" |
| 12 | "time" |
| 13 | ) |
| 14 | |
| 15 | var parseTests = []struct { |
| 16 | in string |
| 17 | header Header |
| 18 | body string |
| 19 | }{ |
| 20 | { |
| 21 | // RFC 5322, Appendix A.1.1 |
| 22 | in: `From: John Doe <jdoe@machine.example> |
| 23 | To: Mary Smith <mary@example.net> |
| 24 | Subject: Saying Hello |
| 25 | Date: Fri, 21 Nov 1997 09:55:06 -0600 |
| 26 | Message-ID: <1234@local.machine.example> |
| 27 | |
| 28 | This is a message just to say hello. |
| 29 | So, "Hello". |
| 30 | `, |
| 31 | header: Header{ |
| 32 | "From": []string{"John Doe <jdoe@machine.example>"}, |
| 33 | "To": []string{"Mary Smith <mary@example.net>"}, |
| 34 | "Subject": []string{"Saying Hello"}, |
| 35 | "Date": []string{"Fri, 21 Nov 1997 09:55:06 -0600"}, |
| 36 | "Message-Id": []string{"<1234@local.machine.example>"}, |
| 37 | }, |
| 38 | body: "This is a message just to say hello.\nSo, \"Hello\".\n", |
| 39 | }, |
| 40 | } |
| 41 | |
| 42 | func TestParsing(t *testing.T) { |
| 43 | for i, test := range parseTests { |
| 44 | msg, err := ReadMessage(bytes.NewBuffer([]byte(test.in))) |
| 45 | if err != nil { |
| 46 | t.Errorf("test #%d: Failed parsing message: %v", i, err) |
| 47 | continue |
| 48 | } |
| 49 | if !headerEq(msg.Header, test.header) { |
| 50 | t.Errorf("test #%d: Incorrectly parsed message header.\nGot:\n%+v\nWant:\n%+v", |
| 51 | i, msg.Header, test.header) |
| 52 | } |
| 53 | body, err := ioutil.ReadAll(msg.Body) |
| 54 | if err != nil { |
| 55 | t.Errorf("test #%d: Failed reading body: %v", i, err) |
| 56 | continue |
| 57 | } |
| 58 | bodyStr := string(body) |
| 59 | if bodyStr != test.body { |
| 60 | t.Errorf("test #%d: Incorrectly parsed message body.\nGot:\n%+v\nWant:\n%+v", |
| 61 | i, bodyStr, test.body) |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | func headerEq(a, b Header) bool { |
| 67 | if len(a) != len(b) { |
| 68 | return false |
| 69 | } |
| 70 | for k, as := range a { |
| 71 | bs, ok := b[k] |
| 72 | if !ok { |
| 73 | return false |
| 74 | } |
| 75 | if !reflect.DeepEqual(as, bs) { |
| 76 | return false |
| 77 | } |
| 78 | } |
| 79 | return true |
| 80 | } |
| 81 | |
| 82 | func TestDateParsing(t *testing.T) { |
| 83 | tests := []struct { |
| 84 | dateStr string |
Russ Cox | 03823b8 | 2011-11-30 12:01:46 -0500 | [diff] [blame] | 85 | exp time.Time |
David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 86 | }{ |
| 87 | // RFC 5322, Appendix A.1.1 |
| 88 | { |
| 89 | "Fri, 21 Nov 1997 09:55:06 -0600", |
Russ Cox | 03823b8 | 2011-11-30 12:01:46 -0500 | [diff] [blame] | 90 | time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)), |
David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 91 | }, |
| 92 | // RFC5322, Appendix A.6.2 |
| 93 | // Obsolete date. |
| 94 | { |
| 95 | "21 Nov 97 09:55:06 GMT", |
Russ Cox | 03823b8 | 2011-11-30 12:01:46 -0500 | [diff] [blame] | 96 | time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("GMT", 0)), |
David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 97 | }, |
Bill Thiede | 132dbb6 | 2012-05-25 09:19:21 +1000 | [diff] [blame] | 98 | // Commonly found format not specified by RFC 5322. |
| 99 | { |
| 100 | "Fri, 21 Nov 1997 09:55:06 -0600 (MDT)", |
| 101 | time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)), |
| 102 | }, |
David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 103 | } |
| 104 | for _, test := range tests { |
| 105 | hdr := Header{ |
| 106 | "Date": []string{test.dateStr}, |
| 107 | } |
| 108 | date, err := hdr.Date() |
| 109 | if err != nil { |
| 110 | t.Errorf("Failed parsing %q: %v", test.dateStr, err) |
| 111 | continue |
| 112 | } |
David Symonds | dbaeb0c | 2011-12-05 10:05:29 +1100 | [diff] [blame] | 113 | if !date.Equal(test.exp) { |
David Symonds | 32e3644 | 2011-06-01 14:10:21 +1000 | [diff] [blame] | 114 | t.Errorf("Parse of %q: got %+v, want %+v", test.dateStr, date, test.exp) |
| 115 | } |
| 116 | } |
| 117 | } |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 118 | |
| 119 | func TestAddressParsing(t *testing.T) { |
| 120 | tests := []struct { |
| 121 | addrsStr string |
| 122 | exp []*Address |
| 123 | }{ |
| 124 | // Bare address |
| 125 | { |
| 126 | `jdoe@machine.example`, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 127 | []*Address{{ |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 128 | Address: "jdoe@machine.example", |
| 129 | }}, |
| 130 | }, |
| 131 | // RFC 5322, Appendix A.1.1 |
| 132 | { |
| 133 | `John Doe <jdoe@machine.example>`, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 134 | []*Address{{ |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 135 | Name: "John Doe", |
| 136 | Address: "jdoe@machine.example", |
| 137 | }}, |
| 138 | }, |
| 139 | // RFC 5322, Appendix A.1.2 |
| 140 | { |
| 141 | `"Joe Q. Public" <john.q.public@example.com>`, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 142 | []*Address{{ |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 143 | Name: "Joe Q. Public", |
| 144 | Address: "john.q.public@example.com", |
| 145 | }}, |
| 146 | }, |
| 147 | { |
| 148 | `Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>`, |
| 149 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 150 | { |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 151 | Name: "Mary Smith", |
| 152 | Address: "mary@x.test", |
| 153 | }, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 154 | { |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 155 | Address: "jdoe@example.org", |
| 156 | }, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 157 | { |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 158 | Name: "Who?", |
| 159 | Address: "one@y.test", |
| 160 | }, |
| 161 | }, |
| 162 | }, |
| 163 | { |
| 164 | `<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>`, |
| 165 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 166 | { |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 167 | Address: "boss@nil.test", |
| 168 | }, |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 169 | { |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 170 | Name: `Giant; "Big" Box`, |
| 171 | Address: "sysservices@example.net", |
| 172 | }, |
| 173 | }, |
| 174 | }, |
| 175 | // RFC 5322, Appendix A.1.3 |
| 176 | // TODO(dsymonds): Group addresses. |
David Symonds | ffd01f2 | 2011-06-09 10:18:36 +1000 | [diff] [blame] | 177 | |
| 178 | // RFC 2047 "Q"-encoded ISO-8859-1 address. |
| 179 | { |
| 180 | `=?iso-8859-1?q?J=F6rg_Doe?= <joerg@example.com>`, |
| 181 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 182 | { |
David Symonds | ffd01f2 | 2011-06-09 10:18:36 +1000 | [diff] [blame] | 183 | Name: `Jörg Doe`, |
| 184 | Address: "joerg@example.com", |
| 185 | }, |
| 186 | }, |
| 187 | }, |
| 188 | // RFC 2047 "Q"-encoded UTF-8 address. |
| 189 | { |
| 190 | `=?utf-8?q?J=C3=B6rg_Doe?= <joerg@example.com>`, |
| 191 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 192 | { |
David Symonds | ffd01f2 | 2011-06-09 10:18:36 +1000 | [diff] [blame] | 193 | Name: `Jörg Doe`, |
| 194 | Address: "joerg@example.com", |
| 195 | }, |
| 196 | }, |
| 197 | }, |
David Symonds | 63639dd | 2011-06-10 08:47:27 +1000 | [diff] [blame] | 198 | // RFC 2047, Section 8. |
| 199 | { |
| 200 | `=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>`, |
| 201 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 202 | { |
David Symonds | 63639dd | 2011-06-10 08:47:27 +1000 | [diff] [blame] | 203 | Name: `André Pirard`, |
| 204 | Address: "PIRARD@vm1.ulg.ac.be", |
| 205 | }, |
| 206 | }, |
| 207 | }, |
David Symonds | 371aa14 | 2011-06-14 17:32:47 +1000 | [diff] [blame] | 208 | // Custom example of RFC 2047 "B"-encoded ISO-8859-1 address. |
| 209 | { |
| 210 | `=?ISO-8859-1?B?SvZyZw==?= <joerg@example.com>`, |
| 211 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 212 | { |
David Symonds | 371aa14 | 2011-06-14 17:32:47 +1000 | [diff] [blame] | 213 | Name: `Jörg`, |
| 214 | Address: "joerg@example.com", |
| 215 | }, |
| 216 | }, |
| 217 | }, |
| 218 | // Custom example of RFC 2047 "B"-encoded UTF-8 address. |
| 219 | { |
David Symonds | 371aa14 | 2011-06-14 17:32:47 +1000 | [diff] [blame] | 220 | `=?UTF-8?B?SsO2cmc=?= <joerg@example.com>`, |
| 221 | []*Address{ |
Russ Cox | dcf1d7b | 2011-12-02 14:14:25 -0500 | [diff] [blame] | 222 | { |
David Symonds | 371aa14 | 2011-06-14 17:32:47 +1000 | [diff] [blame] | 223 | Name: `Jörg`, |
| 224 | Address: "joerg@example.com", |
| 225 | }, |
| 226 | }, |
| 227 | }, |
Ryan Slade | 73b8baa | 2013-08-08 10:00:24 -0700 | [diff] [blame] | 228 | // Custom example with "." in name. For issue 4938 |
| 229 | { |
| 230 | `Asem H. <noreply@example.com>`, |
| 231 | []*Address{ |
| 232 | { |
| 233 | Name: `Asem H.`, |
| 234 | Address: "noreply@example.com", |
| 235 | }, |
| 236 | }, |
| 237 | }, |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 238 | } |
| 239 | for _, test := range tests { |
Graham Miller | 9f807fc | 2012-10-05 10:08:54 +1000 | [diff] [blame] | 240 | if len(test.exp) == 1 { |
| 241 | addr, err := ParseAddress(test.addrsStr) |
| 242 | if err != nil { |
| 243 | t.Errorf("Failed parsing (single) %q: %v", test.addrsStr, err) |
| 244 | continue |
| 245 | } |
| 246 | if !reflect.DeepEqual([]*Address{addr}, test.exp) { |
| 247 | t.Errorf("Parse (single) of %q: got %+v, want %+v", test.addrsStr, addr, test.exp) |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | addrs, err := ParseAddressList(test.addrsStr) |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 252 | if err != nil { |
Graham Miller | 9f807fc | 2012-10-05 10:08:54 +1000 | [diff] [blame] | 253 | t.Errorf("Failed parsing (list) %q: %v", test.addrsStr, err) |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 254 | continue |
| 255 | } |
| 256 | if !reflect.DeepEqual(addrs, test.exp) { |
Graham Miller | 9f807fc | 2012-10-05 10:08:54 +1000 | [diff] [blame] | 257 | t.Errorf("Parse (list) of %q: got %+v, want %+v", test.addrsStr, addrs, test.exp) |
David Symonds | ff0198b | 2011-06-06 16:46:14 +1000 | [diff] [blame] | 258 | } |
| 259 | } |
| 260 | } |
David Symonds | 5c32c96 | 2011-06-08 13:32:47 +1000 | [diff] [blame] | 261 | |
| 262 | func TestAddressFormatting(t *testing.T) { |
| 263 | tests := []struct { |
| 264 | addr *Address |
| 265 | exp string |
| 266 | }{ |
| 267 | { |
| 268 | &Address{Address: "bob@example.com"}, |
| 269 | "<bob@example.com>", |
| 270 | }, |
| 271 | { |
| 272 | &Address{Name: "Bob", Address: "bob@example.com"}, |
| 273 | `"Bob" <bob@example.com>`, |
| 274 | }, |
| 275 | { |
| 276 | // note the ö (o with an umlaut) |
| 277 | &Address{Name: "Böb", Address: "bob@example.com"}, |
| 278 | `=?utf-8?q?B=C3=B6b?= <bob@example.com>`, |
| 279 | }, |
| 280 | } |
| 281 | for _, test := range tests { |
| 282 | s := test.addr.String() |
| 283 | if s != test.exp { |
| 284 | t.Errorf("Address%+v.String() = %v, want %v", *test.addr, s, test.exp) |
| 285 | } |
| 286 | } |
| 287 | } |