1package mail 2 3import ( 4 "bytes" 5 "encoding/base64" 6 "io" 7 "io/ioutil" 8 "path/filepath" 9 "regexp" 10 "strconv" 11 "strings" 12 "testing" 13 "time" 14) 15 16func init() { 17 now = func() time.Time { 18 return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC) 19 } 20} 21 22type message struct { 23 from string 24 to []string 25 content string 26} 27 28func TestMessage(t *testing.T) { 29 m := NewMessage() 30 m.SetAddressHeader("From", "from@example.com", "Señor From") 31 m.SetHeader("To", m.FormatAddress("to@example.com", "Señor To"), "tobis@example.com") 32 m.SetAddressHeader("Cc", "cc@example.com", "A, B") 33 m.SetAddressHeader("X-To", "ccbis@example.com", "à, b") 34 m.SetDateHeader("X-Date", now()) 35 m.SetHeader("X-Date-2", m.FormatDate(now())) 36 m.SetHeader("Subject", "¡Hola, señor!") 37 m.SetHeaders(map[string][]string{ 38 "X-Headers": {"Test", "Café"}, 39 }) 40 m.SetBody("text/plain", "¡Hola, señor!") 41 42 want := &message{ 43 from: "from@example.com", 44 to: []string{ 45 "to@example.com", 46 "tobis@example.com", 47 "cc@example.com", 48 }, 49 content: "From: =?UTF-8?q?Se=C3=B1or_From?= <from@example.com>\r\n" + 50 "To: =?UTF-8?q?Se=C3=B1or_To?= <to@example.com>, tobis@example.com\r\n" + 51 "Cc: \"A, B\" <cc@example.com>\r\n" + 52 "X-To: =?UTF-8?b?w6AsIGI=?= <ccbis@example.com>\r\n" + 53 "X-Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" + 54 "X-Date-2: Wed, 25 Jun 2014 17:46:00 +0000\r\n" + 55 "X-Headers: Test, =?UTF-8?q?Caf=C3=A9?=\r\n" + 56 "Subject: =?UTF-8?q?=C2=A1Hola,_se=C3=B1or!?=\r\n" + 57 "Content-Type: text/plain; charset=UTF-8\r\n" + 58 "Content-Transfer-Encoding: quoted-printable\r\n" + 59 "\r\n" + 60 "=C2=A1Hola, se=C3=B1or!", 61 } 62 63 testMessage(t, m, 0, want) 64} 65 66func TestCustomMessage(t *testing.T) { 67 m := NewMessage(SetCharset("ISO-8859-1"), SetEncoding(Base64)) 68 m.SetHeaders(map[string][]string{ 69 "From": {"from@example.com"}, 70 "To": {"to@example.com"}, 71 "Subject": {"Café"}, 72 }) 73 m.SetBody("text/html", "¡Hola, señor!") 74 75 want := &message{ 76 from: "from@example.com", 77 to: []string{"to@example.com"}, 78 content: "From: from@example.com\r\n" + 79 "To: to@example.com\r\n" + 80 "Subject: =?ISO-8859-1?b?Q2Fmw6k=?=\r\n" + 81 "Content-Type: text/html; charset=ISO-8859-1\r\n" + 82 "Content-Transfer-Encoding: base64\r\n" + 83 "\r\n" + 84 "wqFIb2xhLCBzZcOxb3Ih", 85 } 86 87 testMessage(t, m, 0, want) 88} 89 90func TestUnencodedMessage(t *testing.T) { 91 m := NewMessage(SetEncoding(Unencoded)) 92 m.SetHeaders(map[string][]string{ 93 "From": {"from@example.com"}, 94 "To": {"to@example.com"}, 95 "Subject": {"Café"}, 96 }) 97 m.SetBody("text/html", "¡Hola, señor!") 98 99 want := &message{ 100 from: "from@example.com", 101 to: []string{"to@example.com"}, 102 content: "From: from@example.com\r\n" + 103 "To: to@example.com\r\n" + 104 "Subject: =?UTF-8?q?Caf=C3=A9?=\r\n" + 105 "Content-Type: text/html; charset=UTF-8\r\n" + 106 "Content-Transfer-Encoding: 8bit\r\n" + 107 "\r\n" + 108 "¡Hola, señor!", 109 } 110 111 testMessage(t, m, 0, want) 112} 113 114func TestRecipients(t *testing.T) { 115 m := NewMessage() 116 m.SetHeaders(map[string][]string{ 117 "From": {"from@example.com"}, 118 "To": {"to@example.com"}, 119 "Cc": {"cc@example.com"}, 120 "Bcc": {"bcc1@example.com", "bcc2@example.com"}, 121 "Subject": {"Hello!"}, 122 }) 123 m.SetBody("text/plain", "Test message") 124 125 want := &message{ 126 from: "from@example.com", 127 to: []string{"to@example.com", "cc@example.com", "bcc1@example.com", "bcc2@example.com"}, 128 content: "From: from@example.com\r\n" + 129 "To: to@example.com\r\n" + 130 "Cc: cc@example.com\r\n" + 131 "Subject: Hello!\r\n" + 132 "Content-Type: text/plain; charset=UTF-8\r\n" + 133 "Content-Transfer-Encoding: quoted-printable\r\n" + 134 "\r\n" + 135 "Test message", 136 } 137 138 testMessage(t, m, 0, want) 139} 140 141func TestAlternative(t *testing.T) { 142 m := NewMessage() 143 m.SetHeader("From", "from@example.com") 144 m.SetHeader("To", "to@example.com") 145 m.SetBody("text/plain", "¡Hola, señor!") 146 m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>") 147 148 want := &message{ 149 from: "from@example.com", 150 to: []string{"to@example.com"}, 151 content: "From: from@example.com\r\n" + 152 "To: to@example.com\r\n" + 153 "Content-Type: multipart/alternative;\r\n" + 154 " boundary=_BOUNDARY_1_\r\n" + 155 "\r\n" + 156 "--_BOUNDARY_1_\r\n" + 157 "Content-Type: text/plain; charset=UTF-8\r\n" + 158 "Content-Transfer-Encoding: quoted-printable\r\n" + 159 "\r\n" + 160 "=C2=A1Hola, se=C3=B1or!\r\n" + 161 "--_BOUNDARY_1_\r\n" + 162 "Content-Type: text/html; charset=UTF-8\r\n" + 163 "Content-Transfer-Encoding: quoted-printable\r\n" + 164 "\r\n" + 165 "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" + 166 "--_BOUNDARY_1_--\r\n", 167 } 168 169 testMessage(t, m, 1, want) 170} 171 172func TestPartSetting(t *testing.T) { 173 m := NewMessage() 174 m.SetHeader("From", "from@example.com") 175 m.SetHeader("To", "to@example.com") 176 m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded)) 177 m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>") 178 179 want := &message{ 180 from: "from@example.com", 181 to: []string{"to@example.com"}, 182 content: "From: from@example.com\r\n" + 183 "To: to@example.com\r\n" + 184 "Content-Type: multipart/alternative;\r\n" + 185 " boundary=_BOUNDARY_1_\r\n" + 186 "\r\n" + 187 "--_BOUNDARY_1_\r\n" + 188 "Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" + 189 "Content-Transfer-Encoding: 8bit\r\n" + 190 "\r\n" + 191 "¡Hola, señor!\r\n" + 192 "--_BOUNDARY_1_\r\n" + 193 "Content-Type: text/html; charset=UTF-8\r\n" + 194 "Content-Transfer-Encoding: quoted-printable\r\n" + 195 "\r\n" + 196 "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" + 197 "--_BOUNDARY_1_--\r\n", 198 } 199 200 testMessage(t, m, 1, want) 201} 202 203func TestPartSettingWithCustomBoundary(t *testing.T) { 204 m := NewMessage() 205 m.SetBoundary("lalalaDaiMne3Ryblya") 206 m.SetHeader("From", "from@example.com") 207 m.SetHeader("To", "to@example.com") 208 m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded)) 209 m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>") 210 211 want := &message{ 212 from: "from@example.com", 213 to: []string{"to@example.com"}, 214 content: "From: from@example.com\r\n" + 215 "To: to@example.com\r\n" + 216 "Content-Type: multipart/alternative;\r\n" + 217 " boundary=lalalaDaiMne3Ryblya\r\n" + 218 "\r\n" + 219 "--lalalaDaiMne3Ryblya\r\n" + 220 "Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" + 221 "Content-Transfer-Encoding: 8bit\r\n" + 222 "\r\n" + 223 "¡Hola, señor!\r\n" + 224 "--lalalaDaiMne3Ryblya\r\n" + 225 "Content-Type: text/html; charset=UTF-8\r\n" + 226 "Content-Transfer-Encoding: quoted-printable\r\n" + 227 "\r\n" + 228 "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" + 229 "--lalalaDaiMne3Ryblya--\r\n", 230 } 231 232 testMessage(t, m, 1, want) 233} 234 235func TestBodyWriter(t *testing.T) { 236 m := NewMessage() 237 m.SetHeader("From", "from@example.com") 238 m.SetHeader("To", "to@example.com") 239 m.SetBodyWriter("text/plain", func(w io.Writer) error { 240 _, err := w.Write([]byte("Test message")) 241 return err 242 }) 243 m.AddAlternativeWriter("text/html", func(w io.Writer) error { 244 _, err := w.Write([]byte("Test HTML")) 245 return err 246 }) 247 248 want := &message{ 249 from: "from@example.com", 250 to: []string{"to@example.com"}, 251 content: "From: from@example.com\r\n" + 252 "To: to@example.com\r\n" + 253 "Content-Type: multipart/alternative;\r\n" + 254 " boundary=_BOUNDARY_1_\r\n" + 255 "\r\n" + 256 "--_BOUNDARY_1_\r\n" + 257 "Content-Type: text/plain; charset=UTF-8\r\n" + 258 "Content-Transfer-Encoding: quoted-printable\r\n" + 259 "\r\n" + 260 "Test message\r\n" + 261 "--_BOUNDARY_1_\r\n" + 262 "Content-Type: text/html; charset=UTF-8\r\n" + 263 "Content-Transfer-Encoding: quoted-printable\r\n" + 264 "\r\n" + 265 "Test HTML\r\n" + 266 "--_BOUNDARY_1_--\r\n", 267 } 268 269 testMessage(t, m, 1, want) 270} 271 272func TestAttachmentReader(t *testing.T) { 273 m := NewMessage() 274 m.SetHeader("From", "from@example.com") 275 m.SetHeader("To", "to@example.com") 276 277 var b bytes.Buffer 278 b.Write([]byte("Test file")) 279 m.AttachReader("file.txt", &b) 280 281 want := &message{ 282 from: "from@example.com", 283 to: []string{"to@example.com"}, 284 content: "From: from@example.com\r\n" + 285 "To: to@example.com\r\n" + 286 "Content-Type: text/plain; charset=utf-8; name=\"file.txt\"\r\n" + 287 "Content-Disposition: attachment; filename=\"file.txt\"\r\n" + 288 "Content-Transfer-Encoding: base64\r\n" + 289 "\r\n" + 290 base64.StdEncoding.EncodeToString([]byte("Test file")), 291 } 292 293 testMessage(t, m, 0, want) 294} 295 296func TestAttachmentOnly(t *testing.T) { 297 m := NewMessage() 298 m.SetHeader("From", "from@example.com") 299 m.SetHeader("To", "to@example.com") 300 m.Attach(mockCopyFile("/tmp/test.pdf")) 301 302 want := &message{ 303 from: "from@example.com", 304 to: []string{"to@example.com"}, 305 content: "From: from@example.com\r\n" + 306 "To: to@example.com\r\n" + 307 "Content-Type: application/pdf; name=\"test.pdf\"\r\n" + 308 "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" + 309 "Content-Transfer-Encoding: base64\r\n" + 310 "\r\n" + 311 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")), 312 } 313 314 testMessage(t, m, 0, want) 315} 316 317func TestAttachment(t *testing.T) { 318 m := NewMessage() 319 m.SetHeader("From", "from@example.com") 320 m.SetHeader("To", "to@example.com") 321 m.SetBody("text/plain", "Test") 322 m.Attach(mockCopyFile("/tmp/test.pdf")) 323 324 want := &message{ 325 from: "from@example.com", 326 to: []string{"to@example.com"}, 327 content: "From: from@example.com\r\n" + 328 "To: to@example.com\r\n" + 329 "Content-Type: multipart/mixed;\r\n" + 330 " boundary=_BOUNDARY_1_\r\n" + 331 "\r\n" + 332 "--_BOUNDARY_1_\r\n" + 333 "Content-Type: text/plain; charset=UTF-8\r\n" + 334 "Content-Transfer-Encoding: quoted-printable\r\n" + 335 "\r\n" + 336 "Test\r\n" + 337 "--_BOUNDARY_1_\r\n" + 338 "Content-Type: application/pdf; name=\"test.pdf\"\r\n" + 339 "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" + 340 "Content-Transfer-Encoding: base64\r\n" + 341 "\r\n" + 342 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" + 343 "--_BOUNDARY_1_--\r\n", 344 } 345 346 testMessage(t, m, 1, want) 347} 348 349func TestRename(t *testing.T) { 350 m := NewMessage() 351 m.SetHeader("From", "from@example.com") 352 m.SetHeader("To", "to@example.com") 353 m.SetBody("text/plain", "Test") 354 name, copy := mockCopyFile("/tmp/test.pdf") 355 rename := Rename("another.pdf") 356 m.Attach(name, copy, rename) 357 358 want := &message{ 359 from: "from@example.com", 360 to: []string{"to@example.com"}, 361 content: "From: from@example.com\r\n" + 362 "To: to@example.com\r\n" + 363 "Content-Type: multipart/mixed;\r\n" + 364 " boundary=_BOUNDARY_1_\r\n" + 365 "\r\n" + 366 "--_BOUNDARY_1_\r\n" + 367 "Content-Type: text/plain; charset=UTF-8\r\n" + 368 "Content-Transfer-Encoding: quoted-printable\r\n" + 369 "\r\n" + 370 "Test\r\n" + 371 "--_BOUNDARY_1_\r\n" + 372 "Content-Type: application/pdf; name=\"another.pdf\"\r\n" + 373 "Content-Disposition: attachment; filename=\"another.pdf\"\r\n" + 374 "Content-Transfer-Encoding: base64\r\n" + 375 "\r\n" + 376 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" + 377 "--_BOUNDARY_1_--\r\n", 378 } 379 380 testMessage(t, m, 1, want) 381} 382 383func TestAttachmentsOnly(t *testing.T) { 384 m := NewMessage() 385 m.SetHeader("From", "from@example.com") 386 m.SetHeader("To", "to@example.com") 387 m.Attach(mockCopyFile("/tmp/test.pdf")) 388 m.Attach(mockCopyFile("/tmp/test.zip")) 389 390 want := &message{ 391 from: "from@example.com", 392 to: []string{"to@example.com"}, 393 content: "From: from@example.com\r\n" + 394 "To: to@example.com\r\n" + 395 "Content-Type: multipart/mixed;\r\n" + 396 " boundary=_BOUNDARY_1_\r\n" + 397 "\r\n" + 398 "--_BOUNDARY_1_\r\n" + 399 "Content-Type: application/pdf; name=\"test.pdf\"\r\n" + 400 "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" + 401 "Content-Transfer-Encoding: base64\r\n" + 402 "\r\n" + 403 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" + 404 "--_BOUNDARY_1_\r\n" + 405 "Content-Type: application/zip; name=\"test.zip\"\r\n" + 406 "Content-Disposition: attachment; filename=\"test.zip\"\r\n" + 407 "Content-Transfer-Encoding: base64\r\n" + 408 "\r\n" + 409 base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" + 410 "--_BOUNDARY_1_--\r\n", 411 } 412 413 testMessage(t, m, 1, want) 414} 415 416func TestAttachments(t *testing.T) { 417 m := NewMessage() 418 m.SetHeader("From", "from@example.com") 419 m.SetHeader("To", "to@example.com") 420 m.SetBody("text/plain", "Test") 421 m.Attach(mockCopyFile("/tmp/test.pdf")) 422 m.Attach(mockCopyFile("/tmp/test.zip")) 423 424 want := &message{ 425 from: "from@example.com", 426 to: []string{"to@example.com"}, 427 content: "From: from@example.com\r\n" + 428 "To: to@example.com\r\n" + 429 "Content-Type: multipart/mixed;\r\n" + 430 " boundary=_BOUNDARY_1_\r\n" + 431 "\r\n" + 432 "--_BOUNDARY_1_\r\n" + 433 "Content-Type: text/plain; charset=UTF-8\r\n" + 434 "Content-Transfer-Encoding: quoted-printable\r\n" + 435 "\r\n" + 436 "Test\r\n" + 437 "--_BOUNDARY_1_\r\n" + 438 "Content-Type: application/pdf; name=\"test.pdf\"\r\n" + 439 "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" + 440 "Content-Transfer-Encoding: base64\r\n" + 441 "\r\n" + 442 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" + 443 "--_BOUNDARY_1_\r\n" + 444 "Content-Type: application/zip; name=\"test.zip\"\r\n" + 445 "Content-Disposition: attachment; filename=\"test.zip\"\r\n" + 446 "Content-Transfer-Encoding: base64\r\n" + 447 "\r\n" + 448 base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" + 449 "--_BOUNDARY_1_--\r\n", 450 } 451 452 testMessage(t, m, 1, want) 453} 454 455func TestEmbeddedReader(t *testing.T) { 456 m := NewMessage() 457 m.SetHeader("From", "from@example.com") 458 m.SetHeader("To", "to@example.com") 459 460 var b bytes.Buffer 461 b.Write([]byte("Test file")) 462 m.EmbedReader("file.txt", &b) 463 464 want := &message{ 465 from: "from@example.com", 466 to: []string{"to@example.com"}, 467 content: "From: from@example.com\r\n" + 468 "To: to@example.com\r\n" + 469 "Content-Type: text/plain; charset=utf-8; name=\"file.txt\"\r\n" + 470 "Content-Transfer-Encoding: base64\r\n" + 471 "Content-Disposition: inline; filename=\"file.txt\"\r\n" + 472 "Content-ID: <file.txt>\r\n" + 473 "\r\n" + 474 base64.StdEncoding.EncodeToString([]byte("Test file")), 475 } 476 477 testMessage(t, m, 0, want) 478} 479 480func TestEmbedded(t *testing.T) { 481 m := NewMessage() 482 m.SetHeader("From", "from@example.com") 483 m.SetHeader("To", "to@example.com") 484 m.Embed(mockCopyFileWithHeader(m, "image1.jpg", map[string][]string{"Content-ID": {"<test-content-id>"}})) 485 m.Embed(mockCopyFile("image2.jpg")) 486 m.SetBody("text/plain", "Test") 487 488 want := &message{ 489 from: "from@example.com", 490 to: []string{"to@example.com"}, 491 content: "From: from@example.com\r\n" + 492 "To: to@example.com\r\n" + 493 "Content-Type: multipart/related;\r\n" + 494 " boundary=_BOUNDARY_1_\r\n" + 495 "\r\n" + 496 "--_BOUNDARY_1_\r\n" + 497 "Content-Type: text/plain; charset=UTF-8\r\n" + 498 "Content-Transfer-Encoding: quoted-printable\r\n" + 499 "\r\n" + 500 "Test\r\n" + 501 "--_BOUNDARY_1_\r\n" + 502 "Content-Type: image/jpeg; name=\"image1.jpg\"\r\n" + 503 "Content-Disposition: inline; filename=\"image1.jpg\"\r\n" + 504 "Content-ID: <test-content-id>\r\n" + 505 "Content-Transfer-Encoding: base64\r\n" + 506 "\r\n" + 507 base64.StdEncoding.EncodeToString([]byte("Content of image1.jpg")) + "\r\n" + 508 "--_BOUNDARY_1_\r\n" + 509 "Content-Type: image/jpeg; name=\"image2.jpg\"\r\n" + 510 "Content-Disposition: inline; filename=\"image2.jpg\"\r\n" + 511 "Content-ID: <image2.jpg>\r\n" + 512 "Content-Transfer-Encoding: base64\r\n" + 513 "\r\n" + 514 base64.StdEncoding.EncodeToString([]byte("Content of image2.jpg")) + "\r\n" + 515 "--_BOUNDARY_1_--\r\n", 516 } 517 518 testMessage(t, m, 1, want) 519} 520 521func TestFullMessage(t *testing.T) { 522 m := NewMessage() 523 m.SetHeader("From", "from@example.com") 524 m.SetHeader("To", "to@example.com") 525 m.SetBody("text/plain", "¡Hola, señor!") 526 m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>") 527 m.Attach(mockCopyFile("test.pdf")) 528 m.Embed(mockCopyFile("image.jpg")) 529 530 want := &message{ 531 from: "from@example.com", 532 to: []string{"to@example.com"}, 533 content: "From: from@example.com\r\n" + 534 "To: to@example.com\r\n" + 535 "Content-Type: multipart/mixed;\r\n" + 536 " boundary=_BOUNDARY_1_\r\n" + 537 "\r\n" + 538 "--_BOUNDARY_1_\r\n" + 539 "Content-Type: multipart/related;\r\n" + 540 " boundary=_BOUNDARY_2_\r\n" + 541 "\r\n" + 542 "--_BOUNDARY_2_\r\n" + 543 "Content-Type: multipart/alternative;\r\n" + 544 " boundary=_BOUNDARY_3_\r\n" + 545 "\r\n" + 546 "--_BOUNDARY_3_\r\n" + 547 "Content-Type: text/plain; charset=UTF-8\r\n" + 548 "Content-Transfer-Encoding: quoted-printable\r\n" + 549 "\r\n" + 550 "=C2=A1Hola, se=C3=B1or!\r\n" + 551 "--_BOUNDARY_3_\r\n" + 552 "Content-Type: text/html; charset=UTF-8\r\n" + 553 "Content-Transfer-Encoding: quoted-printable\r\n" + 554 "\r\n" + 555 "=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" + 556 "--_BOUNDARY_3_--\r\n" + 557 "\r\n" + 558 "--_BOUNDARY_2_\r\n" + 559 "Content-Type: image/jpeg; name=\"image.jpg\"\r\n" + 560 "Content-Disposition: inline; filename=\"image.jpg\"\r\n" + 561 "Content-ID: <image.jpg>\r\n" + 562 "Content-Transfer-Encoding: base64\r\n" + 563 "\r\n" + 564 base64.StdEncoding.EncodeToString([]byte("Content of image.jpg")) + "\r\n" + 565 "--_BOUNDARY_2_--\r\n" + 566 "\r\n" + 567 "--_BOUNDARY_1_\r\n" + 568 "Content-Type: application/pdf; name=\"test.pdf\"\r\n" + 569 "Content-Disposition: attachment; filename=\"test.pdf\"\r\n" + 570 "Content-Transfer-Encoding: base64\r\n" + 571 "\r\n" + 572 base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" + 573 "--_BOUNDARY_1_--\r\n", 574 } 575 576 testMessage(t, m, 3, want) 577 578 want = &message{ 579 from: "from@example.com", 580 to: []string{"to@example.com"}, 581 content: "From: from@example.com\r\n" + 582 "To: to@example.com\r\n" + 583 "Content-Type: text/plain; charset=UTF-8\r\n" + 584 "Content-Transfer-Encoding: quoted-printable\r\n" + 585 "\r\n" + 586 "Test reset", 587 } 588 m.Reset() 589 m.SetHeader("From", "from@example.com") 590 m.SetHeader("To", "to@example.com") 591 m.SetBody("text/plain", "Test reset") 592 testMessage(t, m, 0, want) 593} 594 595func TestQpLineLength(t *testing.T) { 596 m := NewMessage() 597 m.SetHeader("From", "from@example.com") 598 m.SetHeader("To", "to@example.com") 599 m.SetBody("text/plain", 600 strings.Repeat("0", 76)+"\r\n"+ 601 strings.Repeat("0", 75)+"à\r\n"+ 602 strings.Repeat("0", 74)+"à\r\n"+ 603 strings.Repeat("0", 73)+"à\r\n"+ 604 strings.Repeat("0", 72)+"à\r\n"+ 605 strings.Repeat("0", 75)+"\r\n"+ 606 strings.Repeat("0", 76)+"\n") 607 608 want := &message{ 609 from: "from@example.com", 610 to: []string{"to@example.com"}, 611 content: "From: from@example.com\r\n" + 612 "To: to@example.com\r\n" + 613 "Content-Type: text/plain; charset=UTF-8\r\n" + 614 "Content-Transfer-Encoding: quoted-printable\r\n" + 615 "\r\n" + 616 strings.Repeat("0", 75) + "=\r\n0\r\n" + 617 strings.Repeat("0", 75) + "=\r\n=C3=A0\r\n" + 618 strings.Repeat("0", 74) + "=\r\n=C3=A0\r\n" + 619 strings.Repeat("0", 73) + "=\r\n=C3=A0\r\n" + 620 strings.Repeat("0", 72) + "=C3=\r\n=A0\r\n" + 621 strings.Repeat("0", 75) + "\r\n" + 622 strings.Repeat("0", 75) + "=\r\n0\r\n", 623 } 624 625 testMessage(t, m, 0, want) 626} 627 628func TestBase64LineLength(t *testing.T) { 629 m := NewMessage(SetCharset("UTF-8"), SetEncoding(Base64)) 630 m.SetHeader("From", "from@example.com") 631 m.SetHeader("To", "to@example.com") 632 m.SetBody("text/plain", strings.Repeat("0", 58)) 633 634 want := &message{ 635 from: "from@example.com", 636 to: []string{"to@example.com"}, 637 content: "From: from@example.com\r\n" + 638 "To: to@example.com\r\n" + 639 "Content-Type: text/plain; charset=UTF-8\r\n" + 640 "Content-Transfer-Encoding: base64\r\n" + 641 "\r\n" + 642 strings.Repeat("MDAw", 19) + "\r\nMA==", 643 } 644 645 testMessage(t, m, 0, want) 646} 647 648func TestEmptyName(t *testing.T) { 649 m := NewMessage() 650 m.SetAddressHeader("From", "from@example.com", "") 651 652 want := &message{ 653 from: "from@example.com", 654 content: "From: from@example.com\r\n", 655 } 656 657 testMessage(t, m, 0, want) 658} 659 660func TestEmptyHeader(t *testing.T) { 661 m := NewMessage() 662 m.SetHeaders(map[string][]string{ 663 "From": {"from@example.com"}, 664 "X-Empty": nil, 665 }) 666 667 want := &message{ 668 from: "from@example.com", 669 content: "From: from@example.com\r\n" + 670 "X-Empty:\r\n", 671 } 672 673 testMessage(t, m, 0, want) 674} 675 676func testMessage(t *testing.T, m *Message, bCount int, want *message) { 677 err := Send(stubSendMail(t, bCount, want), m) 678 if err != nil { 679 t.Error(err) 680 } 681} 682 683func stubSendMail(t *testing.T, bCount int, want *message) SendFunc { 684 return func(from string, to []string, m io.WriterTo) error { 685 if from != want.from { 686 t.Fatalf("Invalid from, got %q, want %q", from, want.from) 687 } 688 689 if len(to) != len(want.to) { 690 t.Fatalf("Invalid recipient count, \ngot %d: %q\nwant %d: %q", 691 len(to), to, 692 len(want.to), want.to, 693 ) 694 } 695 for i := range want.to { 696 if to[i] != want.to[i] { 697 t.Fatalf("Invalid recipient, got %q, want %q", 698 to[i], want.to[i], 699 ) 700 } 701 } 702 703 buf := new(bytes.Buffer) 704 _, err := m.WriteTo(buf) 705 if err != nil { 706 t.Error(err) 707 } 708 got := buf.String() 709 wantMsg := string("MIME-Version: 1.0\r\n" + 710 "Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" + 711 want.content) 712 if bCount > 0 { 713 boundaries := getBoundaries(t, bCount, got) 714 for i, b := range boundaries { 715 wantMsg = strings.Replace(wantMsg, "_BOUNDARY_"+strconv.Itoa(i+1)+"_", b, -1) 716 } 717 } 718 719 compareBodies(t, got, wantMsg) 720 721 return nil 722 } 723} 724 725func compareBodies(t *testing.T, got, want string) { 726 // We cannot do a simple comparison since the ordering of headers' fields 727 // is random. 728 gotLines := strings.Split(got, "\r\n") 729 wantLines := strings.Split(want, "\r\n") 730 731 // We only test for too many lines, missing lines are tested after 732 if len(gotLines) > len(wantLines) { 733 t.Fatalf("Message has too many lines, \ngot %d:\n%s\nwant %d:\n%s", len(gotLines), got, len(wantLines), want) 734 } 735 736 isInHeader := true 737 headerStart := 0 738 for i, line := range wantLines { 739 if line == gotLines[i] { 740 if line == "" { 741 isInHeader = false 742 } else if !isInHeader && len(line) > 2 && line[:2] == "--" { 743 isInHeader = true 744 headerStart = i + 1 745 } 746 continue 747 } 748 749 if !isInHeader { 750 missingLine(t, line, got, want) 751 } 752 753 isMissing := true 754 for j := headerStart; j < len(gotLines); j++ { 755 if gotLines[j] == "" { 756 break 757 } 758 if gotLines[j] == line { 759 isMissing = false 760 break 761 } 762 } 763 if isMissing { 764 missingLine(t, line, got, want) 765 } 766 } 767} 768 769func missingLine(t *testing.T, line, got, want string) { 770 t.Fatalf("Missing line %q\ngot:\n%s\nwant:\n%s", line, got, want) 771} 772 773func getBoundaries(t *testing.T, count int, m string) []string { 774 if matches := boundaryRegExp.FindAllStringSubmatch(m, count); matches != nil { 775 boundaries := make([]string, count) 776 for i, match := range matches { 777 boundaries[i] = match[1] 778 } 779 return boundaries 780 } 781 782 t.Fatal("Boundary not found in body") 783 return []string{""} 784} 785 786var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)") 787 788func mockCopyFile(name string) (string, FileSetting) { 789 return name, SetCopyFunc(func(w io.Writer) error { 790 _, err := w.Write([]byte("Content of " + filepath.Base(name))) 791 return err 792 }) 793} 794 795func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (string, FileSetting, FileSetting) { 796 name, f := mockCopyFile(name) 797 return name, f, SetHeader(h) 798} 799 800func BenchmarkFull(b *testing.B) { 801 discardFunc := SendFunc(func(from string, to []string, m io.WriterTo) error { 802 _, err := m.WriteTo(ioutil.Discard) 803 return err 804 }) 805 806 m := NewMessage() 807 b.ResetTimer() 808 for n := 0; n < b.N; n++ { 809 m.SetAddressHeader("From", "from@example.com", "Señor From") 810 m.SetHeaders(map[string][]string{ 811 "To": {"to@example.com"}, 812 "Cc": {"cc@example.com"}, 813 "Bcc": {"bcc1@example.com", "bcc2@example.com"}, 814 "Subject": {"¡Hola, señor!"}, 815 }) 816 m.SetBody("text/plain", "¡Hola, señor!") 817 m.AddAlternative("text/html", "<p>¡Hola, señor!</p>") 818 m.Attach(mockCopyFile("benchmark.txt")) 819 m.Embed(mockCopyFile("benchmark.jpg")) 820 821 if err := Send(discardFunc, m); err != nil { 822 panic(err) 823 } 824 m.Reset() 825 } 826} 827