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 5b91a53..f52e400 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-module github.com/DusanKasan/parsemail
+module github.com/k3a/parsemail
-go 1.12
\ No newline at end of file
+go 1.12
diff --git a/parsemail.go b/parsemail.go
index 6a60192..6289f70 100644
--- a/parsemail.go
+++ b/parsemail.go
@@ -8,6 +8,7 @@ import (
"io/ioutil"
"mime"
"mime/multipart"
+ "mime/quotedprintable"
"net/mail"
"strings"
"time"
@@ -18,6 +19,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) {
@@ -37,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"])
@@ -46,12 +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
@@ -103,10 +131,35 @@ func parseContentType(contentTypeHeader string) (contentType string, params map[
return mime.ParseMediaType(contentTypeHeader)
}
+func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments []Attachment, err error) {
+ contentDisposition := header.Get("Content-Disposition")
+
+ 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, "\"")
+
+ 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 {
- part, err := pmr.NextPart()
+ part, err := pmr.NextRawPart()
if err == io.EOF {
break
@@ -114,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
@@ -121,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
}
@@ -163,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
@@ -171,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
@@ -178,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
}
@@ -220,18 +293,38 @@ 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 {
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
+ }
+
+ 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 {
@@ -243,26 +336,27 @@ 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
}
-
- htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
- } else if isAttachment(part) {
- at, err := decodeAttachment(part)
+ ppContent, err := ioutil.ReadAll(decoded)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}
- attachments = append(attachments, at)
+ htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
} else {
return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
}
@@ -345,24 +439,32 @@ 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)
if err != nil {
return nil, err
}
-
return bytes.NewReader(b), nil
- case "7bit":
- 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)
}
@@ -483,11 +585,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
+}
diff --git a/parsemail_test.go b/parsemail_test.go
index 109e734..cf4483c 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,132 @@ So, "Hello".`,
messageID: "1234@local.machine.example",
date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
htmlBody: "",
- textBody: "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]",
+ },
+ },
+ },
+ 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.",
+ },
+ 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: "👍",
},
}
@@ -535,10 +660,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 {
@@ -595,9 +724,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 {
@@ -750,6 +879,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)
@@ -806,8 +970,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:;
@@ -817,7 +981,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"
@@ -946,3 +1110,251 @@ 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--
+`
+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--
+`