email_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. package email
  2. import (
  3. "strings"
  4. "testing"
  5. "bufio"
  6. "bytes"
  7. "crypto/rand"
  8. "io"
  9. "io/ioutil"
  10. "mime"
  11. "mime/multipart"
  12. "mime/quotedprintable"
  13. "net/mail"
  14. "net/smtp"
  15. "net/textproto"
  16. )
  17. func prepareEmail() *Email {
  18. e := NewEmail()
  19. e.From = "Jordan Wright <test@example.com>"
  20. e.To = []string{"test@example.com"}
  21. e.Bcc = []string{"test_bcc@example.com"}
  22. e.Cc = []string{"test_cc@example.com"}
  23. e.Subject = "Awesome Subject"
  24. return e
  25. }
  26. func basicTests(t *testing.T, e *Email) *mail.Message {
  27. raw, err := e.Bytes()
  28. if err != nil {
  29. t.Fatal("Failed to render message: ", e)
  30. }
  31. msg, err := mail.ReadMessage(bytes.NewBuffer(raw))
  32. if err != nil {
  33. t.Fatal("Could not parse rendered message: ", err)
  34. }
  35. expectedHeaders := map[string]string{
  36. "To": "test@example.com",
  37. "From": "Jordan Wright <test@example.com>",
  38. "Cc": "test_cc@example.com",
  39. "Subject": "Awesome Subject",
  40. }
  41. for header, expected := range expectedHeaders {
  42. if val := msg.Header.Get(header); val != expected {
  43. t.Errorf("Wrong value for message header %s: %v != %v", header, expected, val)
  44. }
  45. }
  46. return msg
  47. }
  48. func TestEmailText(t *testing.T) {
  49. e := prepareEmail()
  50. e.Text = []byte("Text Body is, of course, supported!\n")
  51. msg := basicTests(t, e)
  52. // Were the right headers set?
  53. ct := msg.Header.Get("Content-type")
  54. mt, _, err := mime.ParseMediaType(ct)
  55. if err != nil {
  56. t.Fatal("Content-type header is invalid: ", ct)
  57. } else if mt != "text/plain" {
  58. t.Fatalf("Content-type expected \"text/plain\", not %v", mt)
  59. }
  60. }
  61. func TestEmailWithHTMLAttachments(t *testing.T) {
  62. e := prepareEmail()
  63. // Set plain text to exercise "mime/alternative"
  64. e.Text = []byte("Text Body is, of course, supported!\n")
  65. e.HTML = []byte("<html><body>This is a text.</body></html>")
  66. // Set HTML attachment to exercise "mime/related".
  67. attachment, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "image/png; charset=utf-8")
  68. if err != nil {
  69. t.Fatal("Could not add an attachment to the message: ", err)
  70. }
  71. attachment.HTMLRelated = true
  72. b, err := e.Bytes()
  73. if err != nil {
  74. t.Fatal("Could not serialize e-mail:", err)
  75. }
  76. // Print the bytes for ocular validation and make sure no errors.
  77. //fmt.Println(string(b))
  78. // TODO: Verify the attachments.
  79. s := trimReader{rd: bytes.NewBuffer(b)}
  80. tp := textproto.NewReader(bufio.NewReader(s))
  81. // Parse the main headers
  82. hdrs, err := tp.ReadMIMEHeader()
  83. if err != nil {
  84. t.Fatal("Could not parse the headers:", err)
  85. }
  86. // Recursively parse the MIME parts
  87. ps, err := parseMIMEParts(hdrs, tp.R)
  88. if err != nil {
  89. t.Fatal("Could not parse the MIME parts recursively:", err)
  90. }
  91. plainTextFound := false
  92. htmlFound := false
  93. imageFound := false
  94. if expected, actual := 3, len(ps); actual != expected {
  95. t.Error("Unexpected number of parts. Expected:", expected, "Was:", actual)
  96. }
  97. for _, part := range ps {
  98. // part has "header" and "body []byte"
  99. ct := part.header.Get("Content-Type")
  100. if strings.Contains(ct, "image/png") {
  101. imageFound = true
  102. }
  103. if strings.Contains(ct, "text/html") {
  104. htmlFound = true
  105. }
  106. if strings.Contains(ct, "text/plain") {
  107. plainTextFound = true
  108. }
  109. }
  110. if !plainTextFound {
  111. t.Error("Did not find plain text part.")
  112. }
  113. if !htmlFound {
  114. t.Error("Did not find HTML part.")
  115. }
  116. if !imageFound {
  117. t.Error("Did not find image part.")
  118. }
  119. }
  120. func TestEmailHTML(t *testing.T) {
  121. e := prepareEmail()
  122. e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
  123. msg := basicTests(t, e)
  124. // Were the right headers set?
  125. ct := msg.Header.Get("Content-type")
  126. mt, _, err := mime.ParseMediaType(ct)
  127. if err != nil {
  128. t.Fatalf("Content-type header is invalid: %#v", ct)
  129. } else if mt != "text/html" {
  130. t.Fatalf("Content-type expected \"text/html\", not %v", mt)
  131. }
  132. }
  133. func TestEmailTextAttachment(t *testing.T) {
  134. e := prepareEmail()
  135. e.Text = []byte("Text Body is, of course, supported!\n")
  136. _, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
  137. if err != nil {
  138. t.Fatal("Could not add an attachment to the message: ", err)
  139. }
  140. msg := basicTests(t, e)
  141. // Were the right headers set?
  142. ct := msg.Header.Get("Content-type")
  143. mt, params, err := mime.ParseMediaType(ct)
  144. if err != nil {
  145. t.Fatal("Content-type header is invalid: ", ct)
  146. } else if mt != "multipart/mixed" {
  147. t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
  148. }
  149. b := params["boundary"]
  150. if b == "" {
  151. t.Fatalf("Invalid or missing boundary parameter: %#v", b)
  152. }
  153. if len(params) != 1 {
  154. t.Fatal("Unexpected content-type parameters")
  155. }
  156. // Is the generated message parsable?
  157. mixed := multipart.NewReader(msg.Body, params["boundary"])
  158. text, err := mixed.NextPart()
  159. if err != nil {
  160. t.Fatalf("Could not find text component of email: %s", err)
  161. }
  162. // Does the text portion match what we expect?
  163. mt, _, err = mime.ParseMediaType(text.Header.Get("Content-type"))
  164. if err != nil {
  165. t.Fatal("Could not parse message's Content-Type")
  166. } else if mt != "text/plain" {
  167. t.Fatal("Message missing text/plain")
  168. }
  169. plainText, err := ioutil.ReadAll(text)
  170. if err != nil {
  171. t.Fatal("Could not read plain text component of message: ", err)
  172. }
  173. if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) {
  174. t.Fatalf("Plain text is broken: %#q", plainText)
  175. }
  176. // Check attachments.
  177. _, err = mixed.NextPart()
  178. if err != nil {
  179. t.Fatalf("Could not find attachment component of email: %s", err)
  180. }
  181. if _, err = mixed.NextPart(); err != io.EOF {
  182. t.Error("Expected only text and one attachment!")
  183. }
  184. }
  185. func TestEmailTextHtmlAttachment(t *testing.T) {
  186. e := prepareEmail()
  187. e.Text = []byte("Text Body is, of course, supported!\n")
  188. e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
  189. _, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
  190. if err != nil {
  191. t.Fatal("Could not add an attachment to the message: ", err)
  192. }
  193. msg := basicTests(t, e)
  194. // Were the right headers set?
  195. ct := msg.Header.Get("Content-type")
  196. mt, params, err := mime.ParseMediaType(ct)
  197. if err != nil {
  198. t.Fatal("Content-type header is invalid: ", ct)
  199. } else if mt != "multipart/mixed" {
  200. t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
  201. }
  202. b := params["boundary"]
  203. if b == "" {
  204. t.Fatal("Unexpected empty boundary parameter")
  205. }
  206. if len(params) != 1 {
  207. t.Fatal("Unexpected content-type parameters")
  208. }
  209. // Is the generated message parsable?
  210. mixed := multipart.NewReader(msg.Body, params["boundary"])
  211. text, err := mixed.NextPart()
  212. if err != nil {
  213. t.Fatalf("Could not find text component of email: %s", err)
  214. }
  215. // Does the text portion match what we expect?
  216. mt, params, err = mime.ParseMediaType(text.Header.Get("Content-type"))
  217. if err != nil {
  218. t.Fatal("Could not parse message's Content-Type")
  219. } else if mt != "multipart/alternative" {
  220. t.Fatal("Message missing multipart/alternative")
  221. }
  222. mpReader := multipart.NewReader(text, params["boundary"])
  223. part, err := mpReader.NextPart()
  224. if err != nil {
  225. t.Fatal("Could not read plain text component of message: ", err)
  226. }
  227. plainText, err := ioutil.ReadAll(part)
  228. if err != nil {
  229. t.Fatal("Could not read plain text component of message: ", err)
  230. }
  231. if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) {
  232. t.Fatalf("Plain text is broken: %#q", plainText)
  233. }
  234. // Check attachments.
  235. _, err = mixed.NextPart()
  236. if err != nil {
  237. t.Fatalf("Could not find attachment component of email: %s", err)
  238. }
  239. if _, err = mixed.NextPart(); err != io.EOF {
  240. t.Error("Expected only text and one attachment!")
  241. }
  242. }
  243. func TestEmailAttachment(t *testing.T) {
  244. e := prepareEmail()
  245. _, err := e.Attach(bytes.NewBufferString("Rad attachment"), "rad.txt", "text/plain; charset=utf-8")
  246. if err != nil {
  247. t.Fatal("Could not add an attachment to the message: ", err)
  248. }
  249. msg := basicTests(t, e)
  250. // Were the right headers set?
  251. ct := msg.Header.Get("Content-type")
  252. mt, params, err := mime.ParseMediaType(ct)
  253. if err != nil {
  254. t.Fatal("Content-type header is invalid: ", ct)
  255. } else if mt != "multipart/mixed" {
  256. t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
  257. }
  258. b := params["boundary"]
  259. if b == "" {
  260. t.Fatal("Unexpected empty boundary parameter")
  261. }
  262. if len(params) != 1 {
  263. t.Fatal("Unexpected content-type parameters")
  264. }
  265. // Is the generated message parsable?
  266. mixed := multipart.NewReader(msg.Body, params["boundary"])
  267. // Check attachments.
  268. _, err = mixed.NextPart()
  269. if err != nil {
  270. t.Fatalf("Could not find attachment component of email: %s", err)
  271. }
  272. if _, err = mixed.NextPart(); err != io.EOF {
  273. t.Error("Expected only one attachment!")
  274. }
  275. }
  276. func TestEmailFromReader(t *testing.T) {
  277. ex := &Email{
  278. Subject: "Test Subject",
  279. To: []string{"Jordan Wright <jmwright798@gmail.com>"},
  280. From: "Jordan Wright <jmwright798@gmail.com>",
  281. Text: []byte("This is a test email with HTML Formatting. It also has very long lines so\nthat the content must be wrapped if using quoted-printable decoding.\n"),
  282. HTML: []byte("<div dir=\"ltr\">This is a test email with <b>HTML Formatting.</b>\u00a0It also has very long lines so that the content must be wrapped if using quoted-printable decoding.</div>\n"),
  283. }
  284. raw := []byte(`
  285. MIME-Version: 1.0
  286. Subject: Test Subject
  287. From: Jordan Wright <jmwright798@gmail.com>
  288. To: Jordan Wright <jmwright798@gmail.com>
  289. Content-Type: multipart/alternative; boundary=001a114fb3fc42fd6b051f834280
  290. --001a114fb3fc42fd6b051f834280
  291. Content-Type: text/plain; charset=UTF-8
  292. This is a test email with HTML Formatting. It also has very long lines so
  293. that the content must be wrapped if using quoted-printable decoding.
  294. --001a114fb3fc42fd6b051f834280
  295. Content-Type: text/html; charset=UTF-8
  296. Content-Transfer-Encoding: quoted-printable
  297. <div dir=3D"ltr">This is a test email with <b>HTML Formatting.</b>=C2=A0It =
  298. also has very long lines so that the content must be wrapped if using quote=
  299. d-printable decoding.</div>
  300. --001a114fb3fc42fd6b051f834280--`)
  301. e, err := NewEmailFromReader(bytes.NewReader(raw))
  302. if err != nil {
  303. t.Fatalf("Error creating email %s", err.Error())
  304. }
  305. if e.Subject != ex.Subject {
  306. t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
  307. }
  308. if !bytes.Equal(e.Text, ex.Text) {
  309. t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
  310. }
  311. if !bytes.Equal(e.HTML, ex.HTML) {
  312. t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
  313. }
  314. if e.From != ex.From {
  315. t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
  316. }
  317. }
  318. func TestNonAsciiEmailFromReader(t *testing.T) {
  319. ex := &Email{
  320. Subject: "Test Subject",
  321. To: []string{"Anaïs <anais@example.org>"},
  322. Cc: []string{"Patrik Fältström <paf@example.com>"},
  323. From: "Mrs Valérie Dupont <valerie.dupont@example.com>",
  324. Text: []byte("This is a test message!"),
  325. }
  326. raw := []byte(`
  327. MIME-Version: 1.0
  328. Subject: =?UTF-8?Q?Test Subject?=
  329. From: Mrs =?ISO-8859-1?Q?Val=C3=A9rie=20Dupont?= <valerie.dupont@example.com>
  330. To: =?utf-8?q?Ana=C3=AFs?= <anais@example.org>
  331. Cc: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@example.com>
  332. Content-type: text/plain; charset=ISO-8859-1
  333. This is a test message!`)
  334. e, err := NewEmailFromReader(bytes.NewReader(raw))
  335. if err != nil {
  336. t.Fatalf("Error creating email %s", err.Error())
  337. }
  338. if e.Subject != ex.Subject {
  339. t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
  340. }
  341. if e.From != ex.From {
  342. t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
  343. }
  344. if e.To[0] != ex.To[0] {
  345. t.Fatalf("Incorrect \"To\": %#q != %#q", e.To, ex.To)
  346. }
  347. if e.Cc[0] != ex.Cc[0] {
  348. t.Fatalf("Incorrect \"Cc\": %#q != %#q", e.Cc, ex.Cc)
  349. }
  350. }
  351. func TestNonMultipartEmailFromReader(t *testing.T) {
  352. ex := &Email{
  353. Text: []byte("This is a test message!"),
  354. Subject: "Example Subject (no MIME Type)",
  355. Headers: textproto.MIMEHeader{},
  356. }
  357. ex.Headers.Add("Content-Type", "text/plain; charset=us-ascii")
  358. ex.Headers.Add("Message-ID", "<foobar@example.com>")
  359. raw := []byte(`From: "Foo Bar" <foobar@example.com>
  360. Content-Type: text/plain
  361. To: foobar@example.com
  362. Subject: Example Subject (no MIME Type)
  363. Message-ID: <foobar@example.com>
  364. This is a test message!`)
  365. e, err := NewEmailFromReader(bytes.NewReader(raw))
  366. if err != nil {
  367. t.Fatalf("Error creating email %s", err.Error())
  368. }
  369. if ex.Subject != e.Subject {
  370. t.Errorf("Incorrect subject. %#q != %#q\n", ex.Subject, e.Subject)
  371. }
  372. if !bytes.Equal(ex.Text, e.Text) {
  373. t.Errorf("Incorrect body. %#q != %#q\n", ex.Text, e.Text)
  374. }
  375. if ex.Headers.Get("Message-ID") != e.Headers.Get("Message-ID") {
  376. t.Errorf("Incorrect message ID. %#q != %#q\n", ex.Headers.Get("Message-ID"), e.Headers.Get("Message-ID"))
  377. }
  378. }
  379. func TestBase64EmailFromReader(t *testing.T) {
  380. ex := &Email{
  381. Subject: "Test Subject",
  382. To: []string{"Jordan Wright <jmwright798@gmail.com>"},
  383. From: "Jordan Wright <jmwright798@gmail.com>",
  384. Text: []byte("This is a test email with HTML Formatting. It also has very long lines so that the content must be wrapped if using quoted-printable decoding."),
  385. HTML: []byte("<div dir=\"ltr\">This is a test email with <b>HTML Formatting.</b>\u00a0It also has very long lines so that the content must be wrapped if using quoted-printable decoding.</div>\n"),
  386. }
  387. raw := []byte(`
  388. MIME-Version: 1.0
  389. Subject: Test Subject
  390. From: Jordan Wright <jmwright798@gmail.com>
  391. To: Jordan Wright <jmwright798@gmail.com>
  392. Content-Type: multipart/alternative; boundary=001a114fb3fc42fd6b051f834280
  393. --001a114fb3fc42fd6b051f834280
  394. Content-Type: text/plain; charset=UTF-8
  395. Content-Transfer-Encoding: base64
  396. VGhpcyBpcyBhIHRlc3QgZW1haWwgd2l0aCBIVE1MIEZvcm1hdHRpbmcuIEl0IGFsc28gaGFzIHZl
  397. cnkgbG9uZyBsaW5lcyBzbyB0aGF0IHRoZSBjb250ZW50IG11c3QgYmUgd3JhcHBlZCBpZiB1c2lu
  398. ZyBxdW90ZWQtcHJpbnRhYmxlIGRlY29kaW5nLg==
  399. --001a114fb3fc42fd6b051f834280
  400. Content-Type: text/html; charset=UTF-8
  401. Content-Transfer-Encoding: quoted-printable
  402. <div dir=3D"ltr">This is a test email with <b>HTML Formatting.</b>=C2=A0It =
  403. also has very long lines so that the content must be wrapped if using quote=
  404. d-printable decoding.</div>
  405. --001a114fb3fc42fd6b051f834280--`)
  406. e, err := NewEmailFromReader(bytes.NewReader(raw))
  407. if err != nil {
  408. t.Fatalf("Error creating email %s", err.Error())
  409. }
  410. if e.Subject != ex.Subject {
  411. t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
  412. }
  413. if !bytes.Equal(e.Text, ex.Text) {
  414. t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
  415. }
  416. if !bytes.Equal(e.HTML, ex.HTML) {
  417. t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
  418. }
  419. if e.From != ex.From {
  420. t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
  421. }
  422. }
  423. func TestAttachmentEmailFromReader (t *testing.T) {
  424. ex := &Email{
  425. Subject: "Test Subject",
  426. To: []string{"Jordan Wright <jmwright798@gmail.com>"},
  427. From: "Jordan Wright <jmwright798@gmail.com>",
  428. Text: []byte("Simple text body"),
  429. HTML: []byte("<div dir=\"ltr\">Simple HTML body</div>\n"),
  430. }
  431. a, err := ex.Attach(bytes.NewReader([]byte("Let's just pretend this is raw JPEG data.")), "cat.jpeg", "image/jpeg")
  432. if err != nil {
  433. t.Fatalf("Error attaching image %s", err.Error())
  434. }
  435. raw := []byte(`
  436. From: Jordan Wright <jmwright798@gmail.com>
  437. Date: Thu, 17 Oct 2019 08:55:37 +0100
  438. Mime-Version: 1.0
  439. Content-Type: multipart/mixed;
  440. boundary=35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
  441. To: Jordan Wright <jmwright798@gmail.com>
  442. Subject: Test Subject
  443. --35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
  444. Content-Type: multipart/alternative;
  445. boundary=b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
  446. --b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
  447. Content-Transfer-Encoding: quoted-printable
  448. Content-Type: text/plain; charset=UTF-8
  449. Simple text body
  450. --b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c
  451. Content-Transfer-Encoding: quoted-printable
  452. Content-Type: text/html; charset=UTF-8
  453. <div dir=3D"ltr">Simple HTML body</div>
  454. --b10ca5b1072908cceb667e8968d3af04503b7ab07d61c9f579c15b416d7c--
  455. --35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7
  456. Content-Disposition: attachment;
  457. filename="cat.jpeg"
  458. Content-Id: <cat.jpeg>
  459. Content-Transfer-Encoding: base64
  460. Content-Type: image/jpeg
  461. TGV0J3MganVzdCBwcmV0ZW5kIHRoaXMgaXMgcmF3IEpQRUcgZGF0YS4=
  462. --35d10c2224bd787fe700c2c6f4769ddc936eb8a0b58e9c8717e406c5abb7--`)
  463. e, err := NewEmailFromReader(bytes.NewReader(raw))
  464. if err != nil {
  465. t.Fatalf("Error creating email %s", err.Error())
  466. }
  467. if e.Subject != ex.Subject {
  468. t.Fatalf("Incorrect subject. %#q != %#q", e.Subject, ex.Subject)
  469. }
  470. if !bytes.Equal(e.Text, ex.Text) {
  471. t.Fatalf("Incorrect text: %#q != %#q", e.Text, ex.Text)
  472. }
  473. if !bytes.Equal(e.HTML, ex.HTML) {
  474. t.Fatalf("Incorrect HTML: %#q != %#q", e.HTML, ex.HTML)
  475. }
  476. if e.From != ex.From {
  477. t.Fatalf("Incorrect \"From\": %#q != %#q", e.From, ex.From)
  478. }
  479. if len(e.Attachments) != 1 {
  480. t.Fatalf("Incorrect number of attachments %d != %d", len(e.Attachments), 1)
  481. }
  482. if e.Attachments[0].Filename != a.Filename {
  483. t.Fatalf("Incorrect attachment filename %s != %s", e.Attachments[0].Filename, a.Filename)
  484. }
  485. if !bytes.Equal(e.Attachments[0].Content, a.Content) {
  486. t.Fatalf("Incorrect attachment content %#q != %#q", e.Attachments[0].Content, a.Content)
  487. }
  488. }
  489. func ExampleGmail() {
  490. e := NewEmail()
  491. e.From = "Jordan Wright <test@gmail.com>"
  492. e.To = []string{"test@example.com"}
  493. e.Bcc = []string{"test_bcc@example.com"}
  494. e.Cc = []string{"test_cc@example.com"}
  495. e.Subject = "Awesome Subject"
  496. e.Text = []byte("Text Body is, of course, supported!\n")
  497. e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
  498. e.Send("smtp.gmail.com:587", smtp.PlainAuth("", e.From, "password123", "smtp.gmail.com"))
  499. }
  500. func ExampleAttach() {
  501. e := NewEmail()
  502. e.AttachFile("test.txt")
  503. }
  504. func Test_base64Wrap(t *testing.T) {
  505. file := "I'm a file long enough to force the function to wrap a\n" +
  506. "couple of lines, but I stop short of the end of one line and\n" +
  507. "have some padding dangling at the end."
  508. encoded := "SSdtIGEgZmlsZSBsb25nIGVub3VnaCB0byBmb3JjZSB0aGUgZnVuY3Rpb24gdG8gd3JhcCBhCmNv\r\n" +
  509. "dXBsZSBvZiBsaW5lcywgYnV0IEkgc3RvcCBzaG9ydCBvZiB0aGUgZW5kIG9mIG9uZSBsaW5lIGFu\r\n" +
  510. "ZApoYXZlIHNvbWUgcGFkZGluZyBkYW5nbGluZyBhdCB0aGUgZW5kLg==\r\n"
  511. var buf bytes.Buffer
  512. base64Wrap(&buf, []byte(file))
  513. if !bytes.Equal(buf.Bytes(), []byte(encoded)) {
  514. t.Fatalf("Encoded file does not match expected: %#q != %#q", string(buf.Bytes()), encoded)
  515. }
  516. }
  517. // *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
  518. func Test_quotedPrintEncode(t *testing.T) {
  519. var buf bytes.Buffer
  520. text := []byte("Dear reader!\n\n" +
  521. "This is a test email to try and capture some of the corner cases that exist within\n" +
  522. "the quoted-printable encoding.\n" +
  523. "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
  524. "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n")
  525. expected := []byte("Dear reader!\r\n\r\n" +
  526. "This is a test email to try and capture some of the corner cases that exist=\r\n" +
  527. " within\r\n" +
  528. "the quoted-printable encoding.\r\n" +
  529. "There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
  530. "s so\r\n" +
  531. "it can come out a little weird. Also, we need to support unicode so here's=\r\n" +
  532. " a fish: =F0=9F=90=9F\r\n")
  533. qp := quotedprintable.NewWriter(&buf)
  534. if _, err := qp.Write(text); err != nil {
  535. t.Fatal("quotePrintEncode: ", err)
  536. }
  537. if err := qp.Close(); err != nil {
  538. t.Fatal("Error closing writer", err)
  539. }
  540. if b := buf.Bytes(); !bytes.Equal(b, expected) {
  541. t.Errorf("quotedPrintEncode generated incorrect results: %#q != %#q", b, expected)
  542. }
  543. }
  544. func TestMultipartNoContentType(t *testing.T) {
  545. raw := []byte(`From: Mikhail Gusarov <dottedmag@dottedmag.net>
  546. To: notmuch@notmuchmail.org
  547. References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
  548. Date: Wed, 18 Nov 2009 01:02:38 +0600
  549. Message-ID: <87iqd9rn3l.fsf@vertex.dottedmag>
  550. MIME-Version: 1.0
  551. Subject: Re: [notmuch] Working with Maildir storage?
  552. Content-Type: multipart/mixed; boundary="===============1958295626=="
  553. --===============1958295626==
  554. Content-Type: multipart/signed; boundary="=-=-=";
  555. micalg=pgp-sha1; protocol="application/pgp-signature"
  556. --=-=-=
  557. Content-Transfer-Encoding: quoted-printable
  558. Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did g=
  559. yre and gimble:
  560. --=-=-=
  561. Content-Type: application/pgp-signature
  562. -----BEGIN PGP SIGNATURE-----
  563. Version: GnuPG v1.4.9 (GNU/Linux)
  564. iQIcBAEBAgAGBQJLAvNOAAoJEJ0g9lA+M4iIjLYQAKp0PXEgl3JMOEBisH52AsIK
  565. =/ksP
  566. -----END PGP SIGNATURE-----
  567. --=-=-=--
  568. --===============1958295626==
  569. Content-Type: text/plain; charset="us-ascii"
  570. MIME-Version: 1.0
  571. Content-Transfer-Encoding: 7bit
  572. Content-Disposition: inline
  573. Testing!
  574. --===============1958295626==--
  575. `)
  576. e, err := NewEmailFromReader(bytes.NewReader(raw))
  577. if err != nil {
  578. t.Fatalf("Error when parsing email %s", err.Error())
  579. }
  580. if !bytes.Equal(e.Text, []byte("Testing!")) {
  581. t.Fatalf("Error incorrect text: %#q != %#q\n", e.Text, "Testing!")
  582. }
  583. }
  584. // *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
  585. func Test_quotedPrintDecode(t *testing.T) {
  586. text := []byte("Dear reader!\r\n\r\n" +
  587. "This is a test email to try and capture some of the corner cases that exist=\r\n" +
  588. " within\r\n" +
  589. "the quoted-printable encoding.\r\n" +
  590. "There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
  591. "s so\r\n" +
  592. "it can come out a little weird. Also, we need to support unicode so here's=\r\n" +
  593. " a fish: =F0=9F=90=9F\r\n")
  594. expected := []byte("Dear reader!\r\n\r\n" +
  595. "This is a test email to try and capture some of the corner cases that exist within\r\n" +
  596. "the quoted-printable encoding.\r\n" +
  597. "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
  598. "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\r\n")
  599. qp := quotedprintable.NewReader(bytes.NewReader(text))
  600. got, err := ioutil.ReadAll(qp)
  601. if err != nil {
  602. t.Fatal("quotePrintDecode: ", err)
  603. }
  604. if !bytes.Equal(got, expected) {
  605. t.Errorf("quotedPrintDecode generated incorrect results: %#q != %#q", got, expected)
  606. }
  607. }
  608. func Benchmark_base64Wrap(b *testing.B) {
  609. // Reasonable base case; 128K random bytes
  610. file := make([]byte, 128*1024)
  611. if _, err := rand.Read(file); err != nil {
  612. panic(err)
  613. }
  614. for i := 0; i <= b.N; i++ {
  615. base64Wrap(ioutil.Discard, file)
  616. }
  617. }
  618. func TestParseSender(t *testing.T) {
  619. var cases = []struct {
  620. e Email
  621. want string
  622. haserr bool
  623. }{
  624. {
  625. Email{From: "from@test.com"},
  626. "from@test.com",
  627. false,
  628. },
  629. {
  630. Email{Sender: "sender@test.com", From: "from@test.com"},
  631. "sender@test.com",
  632. false,
  633. },
  634. {
  635. Email{Sender: "bad_address_sender"},
  636. "",
  637. true,
  638. },
  639. {
  640. Email{Sender: "good@sender.com", From: "bad_address_from"},
  641. "good@sender.com",
  642. false,
  643. },
  644. }
  645. for i, testcase := range cases {
  646. got, err := testcase.e.parseSender()
  647. if got != testcase.want || (err != nil) != testcase.haserr {
  648. t.Errorf(`%d: got %s != want %s or error "%t" != "%t"`, i+1, got, testcase.want, err != nil, testcase.haserr)
  649. }
  650. }
  651. }