From a9a056c17a84687f233cd6435f39bbbcc6b17fab Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 23 Oct 2022 17:11:10 -0500 Subject: [PATCH 01/10] Add support for uppercase encodings Fixes the issue for: ``` unknown encoding: BASE64 ``` --- parsemail.go | 2 +- parsemail_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/parsemail.go b/parsemail.go index 6a60192..e752dd3 100644 --- a/parsemail.go +++ b/parsemail.go @@ -345,7 +345,7 @@ func decodeAttachment(part *multipart.Part) (at Attachment, err error) { } func decodeContent(content io.Reader, encoding string) (io.Reader, error) { - switch encoding { + switch strings.ToLower(encoding) { case "base64": decoded := base64.NewDecoder(base64.StdEncoding, content) b, err := ioutil.ReadAll(decoded) diff --git a/parsemail_test.go b/parsemail_test.go index 109e734..cb088aa 100644 --- a/parsemail_test.go +++ b/parsemail_test.go @@ -403,6 +403,34 @@ So, "Hello".`, htmlBody: "
Time for the egg.



", textBody: "Time for the egg.", }, + 14: { + mailData: data3, + contentType: `multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf`, + content: "", + subject: "Peter Paholík", + from: []mail.Address{ + { + Name: "Peter Paholík", + Address: "peter.paholik@gmail.com", + }, + }, + to: []mail.Address{ + { + Name: "", + Address: "dusan@kasan.sk", + }, + }, + messageID: "CACtgX4kNXE7T5XKSKeH_zEcfUUmf2vXVASxYjaaK9cCn-3zb_g@mail.gmail.com", + date: parseDate("Fri, 07 Apr 2017 09:17:26 +0200"), + htmlBody: "

", + attachments: []attachmentData{ + { + filename: "Peter Paholík 1 4 2017 2017-04-07.json", + contentType: "application/json", + data: "[1, 2, 3]", + }, + }, + }, } for index, td := range testData { @@ -750,6 +778,41 @@ YKUKF+Os3baUndC0pDnwNAmLy1SUr2Gw0luxQuV/AwC6cEhVV5VRrwAAAABJRU5ErkJggg== --------------C70C0458A558E585ACB75FB4-- ` +var data3 = `From: =?UTF-8?Q?Peter_Pahol=C3=ADk?= +Date: Fri, 7 Apr 2017 09:17:26 +0200 +Message-ID: +Subject: =?UTF-8?Q?Peter_Pahol=C3=ADk?= +To: dusan@kasan.sk +Content-Type: multipart/mixed; boundary=f403045f1dcc043a44054c8e6bbf + +--f403045f1dcc043a44054c8e6bbf +Content-Type: multipart/alternative; boundary=f403045f1dcc043a3f054c8e6bbd + +--f403045f1dcc043a3f054c8e6bbd +Content-Type: text/plain; charset=UTF-8 + + + +--f403045f1dcc043a3f054c8e6bbd +Content-Type: text/html; charset=UTF-8 + +

+ +--f403045f1dcc043a3f054c8e6bbd-- +--f403045f1dcc043a44054c8e6bbf +Content-Type: application/json; + name="=?UTF-8?Q?Peter_Paholi=CC=81k_1?= + =?UTF-8?Q?_4_2017_2017=2D04=2D07=2Ejson?=" +Content-Disposition: attachment; + filename="=?UTF-8?Q?Peter_Paholi=CC=81k_1?= + =?UTF-8?Q?_4_2017_2017=2D04=2D07=2Ejson?=" +Content-Transfer-Encoding: BASE64 +X-Attachment-Id: f_j17i0f0d0 + +WzEsIDIsIDNd +--f403045f1dcc043a44054c8e6bbf-- +` + var textPlainInMultipart = `From: Rares Date: Thu, 2 May 2019 11:25:35 +0300 Subject: Re: kern/54143 (virtualbox) From 43b432f2d6fd7e1ef95d14afda09147dd608a6f3 Mon Sep 17 00:00:00 2001 From: Mikita Shchyhelski Date: Mon, 5 Sep 2022 12:31:10 +0200 Subject: [PATCH 02/10] This fix includes simple change, where we initially check whether part is the attachments. Rather doing that in the last step, otherwise attachments with mimetype of text/html would not be extracted out of the eml file --- parsemail.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/parsemail.go b/parsemail.go index e752dd3..b48a2ce 100644 --- a/parsemail.go +++ b/parsemail.go @@ -227,6 +227,16 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str return textBody, htmlBody, attachments, embeddedFiles, err } + if isAttachment(part) { + at, err := decodeAttachment(part) + if err != nil { + return textBody, htmlBody, attachments, embeddedFiles, err + } + + attachments = append(attachments, at) + continue + } + contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { return textBody, htmlBody, attachments, embeddedFiles, err @@ -256,13 +266,6 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str } htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n") - } else if isAttachment(part) { - at, err := decodeAttachment(part) - if err != nil { - return textBody, htmlBody, attachments, embeddedFiles, err - } - - attachments = append(attachments, at) } else { return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType) } @@ -483,11 +486,11 @@ type Email struct { ResentMessageID string ContentType string - Content io.Reader + Content io.Reader HTMLBody string TextBody string Attachments []Attachment EmbeddedFiles []EmbeddedFile -} \ No newline at end of file +} From 01e02d953d8d5628aa1268a8ef469d33af892511 Mon Sep 17 00:00:00 2001 From: Ali Josie <33804388+0xc0d@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:50:38 +0200 Subject: [PATCH 03/10] Add 8bit and binary decoding support --- parsemail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsemail.go b/parsemail.go index b48a2ce..3aebf13 100644 --- a/parsemail.go +++ b/parsemail.go @@ -357,7 +357,7 @@ func decodeContent(content io.Reader, encoding string) (io.Reader, error) { } return bytes.NewReader(b), nil - case "7bit": + case "7bit", "8bit", "binary: dd, err := ioutil.ReadAll(content) if err != nil { return nil, err From abb3b2354cc56fc90da6006301ae7886096b8983 Mon Sep 17 00:00:00 2001 From: isch Date: Thu, 14 Jan 2021 17:53:10 +0500 Subject: [PATCH 04/10] Added parsing attachments in non-multipart emails --- parsemail.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/parsemail.go b/parsemail.go index 3aebf13..3d473e4 100644 --- a/parsemail.go +++ b/parsemail.go @@ -18,6 +18,7 @@ const contentTypeMultipartAlternative = "multipart/alternative" const contentTypeMultipartRelated = "multipart/related" const contentTypeTextHtml = "text/html" const contentTypeTextPlain = "text/plain" +const contentTypeOctetStream = "application/octet-stream" // Parse an email message read from io.Reader into parsemail.Email struct func Parse(r io.Reader) (email Email, err error) { @@ -50,6 +51,8 @@ func Parse(r io.Reader) (email Email, err error) { case contentTypeTextHtml: message, _ := ioutil.ReadAll(msg.Body) email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n") + case contentTypeOctetStream: + email.Attachments, err = parseAttachmentOnlyEmail(msg.Body, msg.Header) default: email.Content, err = decodeContent(msg.Body, msg.Header.Get("Content-Transfer-Encoding")) } @@ -103,6 +106,29 @@ func parseContentType(contentTypeHeader string) (contentType string, params map[ return mime.ParseMediaType(contentTypeHeader) } +func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments []Attachment, err error) { + attachmentData, err := decodeContent(body, header.Get("Content-Transfer-Encoding")) + contentDisposition := header.Get("Content-Disposition") + + if err != nil { + return attachments, err + } + + if len(contentDisposition) > 0 && strings.Contains(contentDisposition, "attachment;") { + fileName := strings.Replace(contentDisposition, "attachment; filename=\"", "", -1) + fileName = strings.TrimRight(fileName, "\"") + + at := Attachment{ + Filename: fileName, + ContentType: "application/octet-stream", + Data: attachmentData, + } + attachments = append(attachments, at) + } + + return attachments, nil +} + func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) { pmr := multipart.NewReader(msg, boundary) for { From 47ee9e8fe4c7998c566484f90fecf2e5d1979238 Mon Sep 17 00:00:00 2001 From: isch Date: Fri, 15 Jan 2021 09:18:06 +0500 Subject: [PATCH 05/10] Small fix --- parsemail.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/parsemail.go b/parsemail.go index 3d473e4..bc19e93 100644 --- a/parsemail.go +++ b/parsemail.go @@ -107,14 +107,15 @@ func parseContentType(contentTypeHeader string) (contentType string, params map[ } func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments []Attachment, err error) { - attachmentData, err := decodeContent(body, header.Get("Content-Transfer-Encoding")) contentDisposition := header.Get("Content-Disposition") - if err != nil { - return attachments, err - } - if len(contentDisposition) > 0 && strings.Contains(contentDisposition, "attachment;") { + + attachmentData, err := decodeContent(body, header.Get("Content-Transfer-Encoding")) + if err != nil { + return attachments, err + } + fileName := strings.Replace(contentDisposition, "attachment; filename=\"", "", -1) fileName = strings.TrimRight(fileName, "\"") @@ -123,6 +124,7 @@ func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments [ ContentType: "application/octet-stream", Data: attachmentData, } + attachments = append(attachments, at) } From f5530b9131cb760d7b1ab7551cea31b0b44d847c Mon Sep 17 00:00:00 2001 From: isch Date: Fri, 15 Jan 2021 12:12:30 +0500 Subject: [PATCH 06/10] Changed module --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5b91a53..5c9602d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/DusanKasan/parsemail +module github.com/get-net/parsemail go 1.12 \ No newline at end of file From 7e1e2a7f77e67ca27dcbf43facdc50252f486c9b Mon Sep 17 00:00:00 2001 From: ArkaGPL Date: Sun, 27 Sep 2020 11:33:22 +0200 Subject: [PATCH 07/10] use decodeContent on every part, NextRawPart for mime types (manual merge of arkagpl:master #28 and matoubidou:master #26) --- parsemail.go | 105 ++++++++++++++++++++++++++++++++++++++-------- parsemail_test.go | 30 +++++++------ 2 files changed, 105 insertions(+), 30 deletions(-) diff --git a/parsemail.go b/parsemail.go index bc19e93..6289f70 100644 --- a/parsemail.go +++ b/parsemail.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "mime" "mime/multipart" + "mime/quotedprintable" "net/mail" "strings" "time" @@ -38,6 +39,8 @@ func Parse(r io.Reader) (email Email, err error) { return } + cte := msg.Header.Get("Content-Transfer-Encoding") + switch contentType { case contentTypeMultipartMixed: email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"]) @@ -47,14 +50,36 @@ func Parse(r io.Reader) (email Email, err error) { email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartRelated(msg.Body, params["boundary"]) case contentTypeTextPlain: message, _ := ioutil.ReadAll(msg.Body) + var reader io.Reader + reader, err = decodeContent(strings.NewReader(string(message[:])), cte) + if err != nil { + return + } + + message, err = ioutil.ReadAll(reader) + if err != nil { + return + } + email.TextBody = strings.TrimSuffix(string(message[:]), "\n") case contentTypeTextHtml: message, _ := ioutil.ReadAll(msg.Body) + var reader io.Reader + reader, err = decodeContent(strings.NewReader(string(message[:])), cte) + if err != nil { + return + } + + message, err = ioutil.ReadAll(reader) + if err != nil { + return + } + email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n") case contentTypeOctetStream: email.Attachments, err = parseAttachmentOnlyEmail(msg.Body, msg.Header) default: - email.Content, err = decodeContent(msg.Body, msg.Header.Get("Content-Transfer-Encoding")) + email.Content, err = decodeContent(msg.Body, cte) } return @@ -134,7 +159,7 @@ func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments [ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) { pmr := multipart.NewReader(msg, boundary) for { - part, err := pmr.NextPart() + part, err := pmr.NextRawPart() if err == io.EOF { break @@ -142,6 +167,8 @@ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody s return textBody, htmlBody, embeddedFiles, err } + cte := part.Header.Get("Content-Transfer-Encoding") + contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { return textBody, htmlBody, embeddedFiles, err @@ -149,14 +176,22 @@ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody s switch contentType { case contentTypeTextPlain: - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, embeddedFiles, err } textBody += strings.TrimSuffix(string(ppContent[:]), "\n") case contentTypeTextHtml: - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, embeddedFiles, err } @@ -191,7 +226,7 @@ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody s func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) { pmr := multipart.NewReader(msg, boundary) for { - part, err := pmr.NextPart() + part, err := pmr.NextRawPart() if err == io.EOF { break @@ -199,6 +234,8 @@ func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBo return textBody, htmlBody, embeddedFiles, err } + cte := part.Header.Get("Content-Transfer-Encoding") + contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { return textBody, htmlBody, embeddedFiles, err @@ -206,14 +243,22 @@ func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBo switch contentType { case contentTypeTextPlain: - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, embeddedFiles, err } textBody += strings.TrimSuffix(string(ppContent[:]), "\n") case contentTypeTextHtml: - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, embeddedFiles, err } @@ -248,7 +293,7 @@ func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBo func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) { mr := multipart.NewReader(msg, boundary) for { - part, err := mr.NextPart() + part, err := mr.NextRawPart() if err == io.EOF { break } else if err != nil { @@ -265,11 +310,21 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str continue } + cte := part.Header.Get("Content-Transfer-Encoding") + contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) if err != nil { return textBody, htmlBody, attachments, embeddedFiles, err } + if isAttachment(part) { + at, err := decodeAttachment(part) + if err != nil { + return textBody, htmlBody, attachments, embeddedFiles, err + } + attachments = append(attachments, at) + } + if contentType == contentTypeMultipartAlternative { textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"]) if err != nil { @@ -281,14 +336,22 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str return textBody, htmlBody, attachments, embeddedFiles, err } } else if contentType == contentTypeTextPlain { - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, attachments, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, attachments, embeddedFiles, err } textBody += strings.TrimSuffix(string(ppContent[:]), "\n") } else if contentType == contentTypeTextHtml { - ppContent, err := ioutil.ReadAll(part) + decoded, err := decodeContent(part, cte) + if err != nil { + return textBody, htmlBody, attachments, embeddedFiles, err + } + ppContent, err := ioutil.ReadAll(decoded) if err != nil { return textBody, htmlBody, attachments, embeddedFiles, err } @@ -383,17 +446,25 @@ func decodeContent(content io.Reader, encoding string) (io.Reader, error) { if err != nil { return nil, err } - return bytes.NewReader(b), nil - case "7bit", "8bit", "binary: - dd, err := ioutil.ReadAll(content) + case "quoted-printable": + decoded := quotedprintable.NewReader(content) + b, err := ioutil.ReadAll(decoded) if err != nil { return nil, err } - - return bytes.NewReader(dd), nil - case "": - return content, nil + return bytes.NewReader(b), nil + // The values "8bit", "7bit", and "binary" all imply that NO encoding has been performed and data need to be read as bytes. + // "7bit" means that the data is all represented as short lines of US-ASCII data. + // "8bit" means that the lines are short, but there may be non-ASCII characters (octets with the high-order bit set). + // "Binary" means that not only may non-ASCII characters be present, but also that the lines are not necessarily short enough for SMTP transport. + case "", "7bit", "8bit", "binary": + decoded := quotedprintable.NewReader(content) + b, err := ioutil.ReadAll(decoded) + if err != nil { + return nil, err + } + return bytes.NewReader(b), nil default: return nil, fmt.Errorf("unknown encoding: %s", encoding) } diff --git a/parsemail_test.go b/parsemail_test.go index cb088aa..973efa7 100644 --- a/parsemail_test.go +++ b/parsemail_test.go @@ -372,15 +372,15 @@ So, "Hello".`, htmlBody: "

", attachments: []attachmentData{ { - filename: "unencoded.csv", - contentType: "application/csv", - data: fmt.Sprintf("\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n", "Some", "Data", "In", "Csv", "Format", "Foo", "Bar", "Baz", "Bum", "Poo"), + filename: "unencoded.csv", + contentType: "application/csv", + data: fmt.Sprintf("\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n"+`"%s", "%s", "%s", "%s", "%s"`+"\n", "Some", "Data", "In", "Csv", "Format", "Foo", "Bar", "Baz", "Bum", "Poo"), }, }, }, 13: { contentType: "multipart/related; boundary=\"000000000000ab2e2205a26de587\"", - mailData: multipartRelatedExample, + mailData: multipartRelatedExample, subject: "Saying Hello", from: []mail.Address{ { @@ -389,7 +389,7 @@ So, "Hello".`, }, }, sender: mail.Address{ - Name: "Michael Jones", + Name: "Michael Jones", Address: "mjones@machine.example", }, to: []mail.Address{ @@ -401,7 +401,7 @@ So, "Hello".`, messageID: "1234@local.machine.example", date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"), htmlBody: "
Time for the egg.



", - textBody: "Time for the egg.", + textBody: "Time for the egg.", }, 14: { mailData: data3, @@ -563,10 +563,14 @@ So, "Hello".`, t.Error(err) } - if ra.Filename == ad.filename && string(b) == ad.data && ra.ContentType == ad.contentType { + if ra.Filename == ad.filename && ra.ContentType == ad.contentType { found = true attachs = append(attachs[:i], attachs[i+1:]...) } + + if string(b) != ad.data { + t.Errorf("[Test Case %v] Bad data for attachment: \nEXPECTED:\n%s\nHAVE:\n%s", index, ad.data, string(b)) + } } if !found { @@ -623,9 +627,9 @@ func parseDate(in string) time.Time { } type attachmentData struct { - filename string - contentType string - data string + filename string + contentType string + data string } type embeddedFileData struct { @@ -869,8 +873,8 @@ Message-ID: <5678.21-Nov-1997@example.com> Hi everyone. ` -//todo: not yet implemented in net/mail -//once there is support for this, add it +// todo: not yet implemented in net/mail +// once there is support for this, add it var rfc5322exampleA13 = `From: Pete To: A Group:Ed Jones ,joe@where.test,John ; Cc: Undisclosed recipients:; @@ -880,7 +884,7 @@ Message-ID: Testing. ` -//we skipped the first message bcause it's the same as A 1.1 +// we skipped the first message bcause it's the same as A 1.1 var rfc5322exampleA2a = `From: Mary Smith To: John Doe Reply-To: "Mary Smith: Personal Account" From 8b067473cccd0e03be2f5a49f286d50d99c48030 Mon Sep 17 00:00:00 2001 From: Mario Hros Date: Sat, 26 Aug 2023 17:13:52 +0200 Subject: [PATCH 08/10] add test cases for quoted-printable (from ovadbar:quoted-printable #39) --- parsemail_test.go | 297 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) diff --git a/parsemail_test.go b/parsemail_test.go index 973efa7..281437f 100644 --- a/parsemail_test.go +++ b/parsemail_test.go @@ -431,6 +431,78 @@ So, "Hello".`, }, }, }, + 15: { + contentType: "text/plain; charset=utf-8", + mailData: rfc2045exampleA, + subject: "Lead from Allstate LeadVantage", + from: []mail.Address{ + { + Address: "LVsupport@allstateleadvantage.com", + }, + }, + to: []mail.Address{ + { + Address: "test@email.com", + }, + }, + replyTo: []mail.Address{ + { + Address: "no-reply@allstateleadvantage.com", + }, + }, + messageID: "0100017fcf817777-481efc68-4a9a-4c11-ba2c-40ff0357e7b1-000000@email.amazonses.com", + date: parseDate("Mon, 28 Mar 2022 07:50:42 +0000"), + textBody: rfc2045exampleAtext, + }, + 16: { + contentType: `text/html; charset="utf-8"`, + mailData: rfc2045exampleB, + subject: "New Business Property/Casualty Lead Received (#245200111)", + from: []mail.Address{ + { + Name: "AllWebLeads", + Address: "no-reply@allwebleads.com", + }, + }, + to: []mail.Address{ + { + Address: "sample@example.com", + }, + }, + replyTo: []mail.Address{ + { + Address: "no-reply@allwebleads.com", + }, + }, + messageID: "1187856165.40703531648591546580.JavaMail.app@rapp51.atlis1", + date: parseDate("Tue, 29 Mar 2022 22:05:46 +0000"), + htmlBody: rfc2045exampleBhtml, + }, + 17: { + contentType: "multipart/related; boundary=\"000000000000ab2e2205a26de587\"", + mailData: multipartRelatedExampleQuoted, + subject: "Saying Hello", + from: []mail.Address{ + { + Name: "John Doe", + Address: "jdoe@machine.example", + }, + }, + sender: mail.Address{ + Name: "Michael Jones", + Address: "mjones@machine.example", + }, + to: []mail.Address{ + { + Name: "Mary Smith", + Address: "mary@example.net", + }, + }, + messageID: "1234@local.machine.example", + date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"), + htmlBody: rfc2045exampleBhtml, + textBody: "Time for the egg. Should we hardboil the egg or fry it. We can scramble it or poach it.", + }, } for index, td := range testData { @@ -1013,3 +1085,228 @@ Content-Disposition: attachment; --f403045f1dcc043a44054c8e6bbf-- ` + +var rfc2045exampleA = `From 0100017fcf817777-481efc68-4a9a-4c11-ba2c-40ff0357e7b1-000000@amazonses.com Mon Mar 28 07:50:43 2022 +Return-Path: <0100017fcf817777-481efc68-4a9a-4c11-ba2c-40ff0357e7b1-000000@amazonses.com> +X-Original-To: test@email.com +Delivered-To: leads@reciever.com +Message-ID: <0100017fcf817777-481efc68-4a9a-4c11-ba2c-40ff0357e7b1-000000@email.amazonses.com> +Date: Mon, 28 Mar 2022 07:50:42 +0000 +Subject: Lead from Allstate LeadVantage +From: LVsupport@allstateleadvantage.com +Reply-To: no-reply@allstateleadvantage.com +To: test@email.com +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + +You just received a lead! Please check your lead management system, or u= +se the contact information +below. Please do not respond to this email ad= +dress, as it is not active. You may also view your leads +in Allstate Lead= +Vantage. Please call Allstate LeadVantage Support at 855-317-4233 or sign u= +p here: +https://allstateleadvantage.com/#/orders/list + +Lead Informati= +on: +Unique ID: 138296007 +Vertical: Auto Insurance +Alliance URL: https= +://agencygateway.allstate.com/ALLIANCE/launch?AgentNumber=3DA0c3858&ST=3DNV= +&FunctionType=3DAF&SourceOfLaunchPoint=3D01&ControlNumber=3D198220870336180= + +Contact Information: +First Name: Brenda +Last Name: Qualls +Phone Nu= +mber: (702) 485-1038 +Email Address: brendaqualls29@yahoo.com +Street Add= +ress: 3236 Brayton Mist Dr +City: North Las Vegas +State: NV +Zip: 89081= + +Are You A Homeowner: Yes +Best Time To Contact:=20 +Vendor: +Vendor Nam= +e: Inside Response +Order Information: +Name: Custom Order 1 +Policy Det= +ails: +Self Credit Rating: Good (620 - 719) +Currently Insured: Yes +Cur= +rent Insurance Company: State Farm County +Insured Since: 03/28/2020 +Pol= +icy Start: 03/28/2020 +Policy Expiration: 05/28/2022 +Desired Coverage Ty= +pe: standard +Desired Collision Deductible: 1000 +Desired Comprehensive D= +eductible: 1000 +Driver 1: +Gender: female +Marital Status: married +Ed= +ucation Level: ged +Occupation: other +Date of Birth: 01/29/1981 +Age Li= +censed: 19 +Has Valid License: Yes +Has DUI: No +Requires SR-22: No +Re= +lation to applicant: self +Years Employed: 2 +Years at Residence: 2 +Has= + Tickets / Accidents: No +Vehicle 1: +Type: 2006 LEXUS SC 430 2WD CONVERT= +IBLE - 4.3L V8 FI DOHC 32V F +Vin: JTHFN48Y060000000 +Leased: No +Pri= +mary Use: Pleasure Use Only +Commute Days: 5 +Daily Mileage: 5 +Annual M= +ileage: 15000 +Has Alarm: Yes +Garage: nocover +` + +var rfc2045exampleAtext string = ` +You just received a lead! Please check your lead management system, or use the contact information +below. Please do not respond to this email address, as it is not active. You may also view your leads +in Allstate LeadVantage. Please call Allstate LeadVantage Support at 855-317-4233 or sign up here: +https://allstateleadvantage.com/#/orders/list + +Lead Information: +Unique ID: 138296007 +Vertical: Auto Insurance +Alliance URL: https://agencygateway.allstate.com/ALLIANCE/launch?AgentNumber=A0c3858&ST=NV&FunctionType=AF&SourceOfLaunchPoint=01&ControlNumber=198220870336180 +Contact Information: +First Name: Brenda +Last Name: Qualls +Phone Number: (702) 485-1038 +Email Address: brendaqualls29@yahoo.com +Street Address: 3236 Brayton Mist Dr +City: North Las Vegas +State: NV +Zip: 89081 +Are You A Homeowner: Yes +Best Time To Contact: +Vendor: +Vendor Name: Inside Response +Order Information: +Name: Custom Order 1 +Policy Details: +Self Credit Rating: Good (620 - 719) +Currently Insured: Yes +Current Insurance Company: State Farm County +Insured Since: 03/28/2020 +Policy Start: 03/28/2020 +Policy Expiration: 05/28/2022 +Desired Coverage Type: standard +Desired Collision Deductible: 1000 +Desired Comprehensive Deductible: 1000 +Driver 1: +Gender: female +Marital Status: married +Education Level: ged +Occupation: other +Date of Birth: 01/29/1981 +Age Licensed: 19 +Has Valid License: Yes +Has DUI: No +Requires SR-22: No +Relation to applicant: self +Years Employed: 2 +Years at Residence: 2 +Has Tickets / Accidents: No +Vehicle 1: +Type: 2006 LEXUS SC 430 2WD CONVERTIBLE - 4.3L V8 FI DOHC 32V F +Vin: JTHFN48Y060000000 +Leased: No +Primary Use: Pleasure Use Only +Commute Days: 5 +Daily Mileage: 5 +Annual Mileage: 15000 +Has Alarm: Yes +Garage: nocover` + +var rfc2045exampleB string = `From v-biheobc_begnlldjf_icanamoe_icanamoe_a-1@bounce.allweb.mkt3103.com Tue Mar 29 22:05:46 2022 +Return-Path: +X-Original-To: sample@example.com +Delivered-To: leads@reciever.com +Received: by mail2792.allweb.mkt3188.com id h8e1bk2r7ao5 for ; Tue, 29 Mar 2022 22:05:46 +0000 (envelope-from ) +Date: Tue, 29 Mar 2022 22:05:46 +0000 (GMT) +From: AllWebLeads +Reply-To: no-reply@allwebleads.com +To: sample@example.com +Message-ID: <1187856165.40703531648591546580.JavaMail.app@rapp51.atlis1> +Subject: New Business Property/Casualty Lead Received (#245200111) +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: quoted-printable + +
+=09
Time for the egg.
+=09

+=09


+=09
Should we hardboil the egg or fry it. We can scramble it or poach i= +t.
+
` + +var rfc2045exampleBhtml string = `
+
Time for the egg.
+

+


+
Should we hardboil the egg or fry it. We can scramble it or poach it.
+
` +var multipartRelatedExampleQuoted = `MIME-Version: 1.0 +From: John Doe +Sender: Michael Jones +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> +Subject: ooops +To: test@example.rocks +Content-Type: multipart/related; boundary="000000000000ab2e2205a26de587" + +--000000000000ab2e2205a26de587 +Content-Type: multipart/alternative; boundary="000000000000ab2e1f05a26de586" + +--000000000000ab2e1f05a26de586 +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +Time for the egg. Should we hardboil the egg or fry it. We can scramble it = +or poach it. + +--000000000000ab2e1f05a26de586 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +
+=09
Time for the egg.
+=09

+=09


+=09
Should we hardboil the egg or fry it. We can scramble it or poach i= +t.
+
+ +--000000000000ab2e1f05a26de586-- + + +--000000000000ab2e2205a26de587-- +` From ea3da3ace5b1cc9050f89de6a801240d0864987e Mon Sep 17 00:00:00 2001 From: Mario Hros Date: Sat, 26 Aug 2023 17:15:47 +0200 Subject: [PATCH 09/10] add test cases for base64 (from ryotosaito:master #24) --- parsemail_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/parsemail_test.go b/parsemail_test.go index 281437f..cf4483c 100644 --- a/parsemail_test.go +++ b/parsemail_test.go @@ -503,6 +503,31 @@ So, "Hello".`, htmlBody: rfc2045exampleBhtml, textBody: "Time for the egg. Should we hardboil the egg or fry it. We can scramble it or poach it.", }, + 18: { + contentType: "multipart/alternative; boundary=\"000000000000ab2e1f05a26de586\"", + mailData: base64Content, + subject: "Saying Hello", + from: []mail.Address{ + { + Name: "John Doe", + Address: "jdoe@machine.example", + }, + }, + sender: mail.Address{ + Name: "Michael Jones", + Address: "mjones@machine.example", + }, + to: []mail.Address{ + { + Name: "Mary Smith", + Address: "mary@example.net", + }, + }, + messageID: "1234@local.machine.example", + date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"), + htmlBody: "
👍
", + textBody: "👍", + }, } for index, td := range testData { @@ -1310,3 +1335,26 @@ t. --000000000000ab2e2205a26de587-- ` +var base64Content = `MIME-Version: 1.0 +From: John Doe +Sender: Michael Jones +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> +Content-Type: multipart/alternative; boundary="000000000000ab2e1f05a26de586" + +--000000000000ab2e1f05a26de586 +Content-Type: text/plain; charset="UTF-8" +Content-Transfer-Encoding: base64 + +8J+RjQo= + +--000000000000ab2e1f05a26de586 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9Imx0ciI+8J+RjTwvZGl2Pgo= + +--000000000000ab2e1f05a26de586-- +` From dc7f947e911a6ae44204b0bc3a5c88621cd16754 Mon Sep 17 00:00:00 2001 From: Mario Hros Date: Sat, 2 Sep 2023 20:58:20 +0200 Subject: [PATCH 10/10] new fork repo setup --- .circleci/config.yml | 8 +++++--- CONTRIBUTING.md | 2 +- README.md | 6 ++++-- go.mod | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e8b54c0..82f8b1a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ -# Golang CircleCI 2.0 configuration file +# Golang CircleCI 2.1 configuration file # # Check https://circleci.com/docs/2.0/language-go/ for more details -version: 2 +version: 2.1 jobs: build: docker: @@ -9,4 +9,6 @@ jobs: working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} steps: - checkout - - run: go test -v ./... \ No newline at end of file + - run: GO111MODULE=off go get github.com/mattn/goveralls + - run: go test -v -cover -race -coverprofile=coverage.out ./... + - run: $GOPATH/bin/goveralls -coverprofile=coverage.out -service=circle-ci -repotoken=$COVERALLS_TOKEN diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 046d839..38faea1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,4 +16,4 @@ Every change merges to master. No development is done in other branches. - Wait for CI to verify you didn't break anything - If you did, rewrite it - If CI passes, wait for manual review by repo's owner -- Your pull request will be merged into master \ No newline at end of file +- Your pull request will be merged into master diff --git a/README.md b/README.md index 382b921..8d0f786 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Parsemail - simple email parsing Go library -[![Build Status](https://circleci.com/gh/DusanKasan/parsemail.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/DusanKasan/parsemail) [![Coverage Status](https://coveralls.io/repos/github/DusanKasan/Parsemail/badge.svg?branch=master)](https://coveralls.io/github/DusanKasan/Parsemail?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/DusanKasan/parsemail)](https://goreportcard.com/report/github.com/DusanKasan/parsemail) +[![Build Status](https://circleci.com/gh/k3a/parsemail.svg?style=shield)](https://circleci.com/gh/k3a/parsemail) [![Coverage Status](https://coveralls.io/repos/github/k3a/parsemail/badge.svg?branch=master)](https://coveralls.io/github/k3a/parsemail?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/k3a/parsemail)](https://goreportcard.com/report/github.com/k3a/parsemail) + +> The [original repo](https://github.com/DusanKasan/parsemail) is unmaintained. This is an active fork replacing the original repo. Contributions are welcome. This library allows for parsing an email message into a more convenient form than the `net/mail` provides. Where the `net/mail` just gives you a map of header fields and a `io.Reader` of its body, Parsemail allows access to all the standard header fields set in [RFC5322](https://tools.ietf.org/html/rfc5322), html/text body as well as attachements/embedded content as binary streams with metadata. @@ -55,4 +57,4 @@ for _, a := range(email.EmbeddedFiles) { fmt.Println(a.ContentType) //and read a.Data } -``` \ No newline at end of file +``` diff --git a/go.mod b/go.mod index 5c9602d..f52e400 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/get-net/parsemail +module github.com/k3a/parsemail -go 1.12 \ No newline at end of file +go 1.12