Skip to content

Commit

Permalink
use decodeContent on every part, NextRawPart for mime types (manual m…
Browse files Browse the repository at this point in the history
…erge of arkagpl:master DusanKasan#28 and matoubidou:master DusanKasan#26)
  • Loading branch information
ArkaGPL authored and Mario Hros committed Sep 2, 2023
1 parent e0baed8 commit 1130a3a
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 30 deletions.
105 changes: 88 additions & 17 deletions parsemail.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"mime"
"mime/multipart"
"mime/quotedprintable"
"net/mail"
"strings"
"time"
Expand Down Expand Up @@ -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"])
Expand All @@ -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
Expand Down Expand Up @@ -134,29 +159,39 @@ 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
} else if err != nil {
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
}

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
}
Expand Down Expand Up @@ -191,29 +226,39 @@ 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
} else if err != nil {
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
}

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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down
30 changes: 17 additions & 13 deletions parsemail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,15 @@ So, "Hello".`,
htmlBody: "<div dir=\"ltr\"><br></div>",
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{
{
Expand All @@ -389,7 +389,7 @@ So, "Hello".`,
},
},
sender: mail.Address{
Name: "Michael Jones",
Name: "Michael Jones",
Address: "mjones@machine.example",
},
to: []mail.Address{
Expand All @@ -401,7 +401,7 @@ So, "Hello".`,
messageID: "1234@local.machine.example",
date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
htmlBody: "<div dir=\"ltr\"><div>Time for the egg.</div><div><br></div><div><br><br></div></div>",
textBody: "Time for the egg.",
textBody: "Time for the egg.",
},
14: {
mailData: data3,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 <pete@silly.example>
To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;
Cc: Undisclosed recipients:;
Expand All @@ -880,7 +884,7 @@ Message-ID: <testabcd.1234@silly.example>
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 <mary@example.net>
To: John Doe <jdoe@machine.example>
Reply-To: "Mary Smith: Personal Account" <smith@home.example>
Expand Down

0 comments on commit 1130a3a

Please sign in to comment.