1 package mail
2
3 import (
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
16 func init() {
17 now = func() time.Time {
18 return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
19 }
20 }
21
22 type message struct {
23 from string
24 to []string
25 content string
26 }
27
28 func 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
66 func 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
90 func 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
114 func 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
141 func 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
172 func 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
203 func 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
235 func 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
272 func 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
296 func 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
317 func 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
349 func 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
383 func 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
416 func 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
455 func 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
480 func 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
521 func 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
595 func 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
628 func 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
648 func 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
660 func 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
676 func 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
683 func 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
725 func compareBodies(t *testing.T, got, want string) {
726
727
728 gotLines := strings.Split(got, "\r\n")
729 wantLines := strings.Split(want, "\r\n")
730
731
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
769 func 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
773 func 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
786 var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
787
788 func 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
795 func 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
800 func 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
View as plain text