Skip to content

Commit

Permalink
Add support for multipart/signed DusanKasan#33
Browse files Browse the repository at this point in the history
  • Loading branch information
PetriTurunen committed Sep 18, 2021
1 parent ff67f47 commit 37dc7e0
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 23 deletions.
128 changes: 106 additions & 22 deletions parsemail.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import (
const contentTypeMultipartMixed = "multipart/mixed"
const contentTypeMultipartAlternative = "multipart/alternative"
const contentTypeMultipartRelated = "multipart/related"
const contentTypeMultipartSigned = "multipart/signed"
const contentTypeTextHtml = "text/html"
const contentTypeTextPlain = "text/plain"

// this is a "special" content-type
const contentAttachment = "attachment"

// Parse an email message read from io.Reader into parsemail.Email struct
func Parse(r io.Reader) (email Email, err error) {
msg, err := mail.ReadMessage(r)
Expand All @@ -44,6 +48,8 @@ func Parse(r io.Reader) (email Email, err error) {
email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
case contentTypeMultipartRelated:
email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartRelated(msg.Body, params["boundary"])
case contentTypeMultipartSigned:
email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartSigned(msg.Body, params["boundary"])
case contentTypeTextPlain:
message, _ := ioutil.ReadAll(msg.Body)
var reader io.Reader
Expand Down Expand Up @@ -265,9 +271,11 @@ 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()
if err == io.EOF {
err = nil
break
} else if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
Expand All @@ -278,45 +286,113 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str
return textBody, htmlBody, attachments, embeddedFiles, err
}

if contentType == contentTypeMultipartAlternative {
textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}
} else if contentType == contentTypeMultipartRelated {
textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"])
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
switch contentType {

case contentTypeMultipartAlternative:
textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}

case contentTypeMultipartRelated:
textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"])
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}

case contentTypeTextPlain:
ppContent, err := ioutil.ReadAll(part)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}

textBody += strings.TrimSuffix(string(ppContent[:]), "\n")

case contentTypeTextHtml:
ppContent, err := ioutil.ReadAll(part)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}

htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")

case isAttachmentAsString(contentType, part):
at, err := decodeAttachment(part)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
}

t attachments = append(attachments, at)
default:
return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
}
}

return textBody, htmlBody, attachments, embeddedFiles, err
}

func parseMultipartSigned(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {

// vars
err = nil
var part *multipart.Part
var contentType string
var params map[string]string

// reader
mr := multipart.NewReader(msg, boundary)

mrparts:
for {
part, err = mr.NextPart()
if err == io.EOF {
err = nil
break
} else if err != nil {
return
}

contentType, params, err = mime.ParseMediaType(part.Header.Get("Content-Type"))
if err != nil {
return
}

switch contentType {

case contentTypeMultipartMixed:
if textBody, htmlBody, attachments, embeddedFiles, err = parseMultipartMixed(part, params["boundary"]); err != nil {
break mrparts
}
} else if contentType == contentTypeTextPlain {
ppContent, err := ioutil.ReadAll(part)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err

case contentTypeMultipartAlternative:
if textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"]); err != nil {
break mrparts
}

textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
} else if contentType == contentTypeTextHtml {
ppContent, err := ioutil.ReadAll(part)
if err != nil {
return textBody, htmlBody, attachments, embeddedFiles, err
case contentTypeMultipartRelated:
if textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"]); err != nil {
break mrparts
}

htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
} else if isAttachment(part) {
case isAttachmentAsString(contentType, 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)

default:
err = fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
return textBody, htmlBody, attachments, embeddedFiles, err
}

}

return textBody, htmlBody, attachments, embeddedFiles, err
}


func decodeMimeSentence(s string) string {
result := []string{}
ss := strings.Split(s, " ")
Expand All @@ -338,6 +414,14 @@ func decodeMimeSentence(s string) string {
return strings.Join(result, "")
}

func isAttachmentAsString(contentType string, part *multipart.Part) string {
if isAttachment(part) == true {
return contentType
}

return ""
}

func decodeHeaderMime(header mail.Header) (mail.Header, error) {
parsedHeader := map[string][]string{}

Expand Down
74 changes: 73 additions & 1 deletion parsemail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,44 @@ So, "Hello".`,
htmlBody: "<div dir=\"ltr\">👍</div>",
textBody: "👍",
},
15: {
mailData: multipartSignedExample,
from: []mail.Address{
{
Name: "John Doe",
Address: "[email protected]",
},
},
sender: mail.Address{
Name: "Michael Jones",
Address: "[email protected]",
},
to: []mail.Address{
{
Name: "Mary Smith",
Address: "[email protected]",
},
},
messageID: "[email protected]",
date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
subject: "Multipart/Signed Mail",
contentType: "multipart/signed; micalg=\"sha-256\"; protocol=\"application/pkcs7-signature\"; boundary=\"=-qEPtUjpmMQEwviXs/uAF\"",
htmlBody: "<html><head></head><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;\"><div>A strange mail</div><div><span></span></div></body></html>",
textBody: "A strange mail",
attachments: []attachmentData{
{
filename: "smime.p7s",
contentType: "application/pkcs7-signature",
data: "data\n",
},
},
},
}

for index, td := range testData {
e, err := Parse(strings.NewReader(td.mailData))
if err != nil {
t.Error(err)
t.Errorf("[Test Case %v] Error occured %s", index, err)
}

if td.contentType != e.ContentType {
Expand Down Expand Up @@ -995,3 +1027,43 @@ PGRpdiBkaXI9Imx0ciI+8J+RjTwvZGl2Pgo=
--000000000000ab2e1f05a26de586--
`

var multipartSignedExample = `MIME-Version: 1.0
From: John Doe <[email protected]>
Sender: Michael Jones <[email protected]>
To: Mary Smith <[email protected]>
Message-ID: <[email protected]>
Subject: Multipart/Signed Mail
Content-Type: multipart/signed; micalg="sha-256"; protocol="application/pkcs7-signature";
boundary="=-qEPtUjpmMQEwviXs/uAF"
User-Agent: Evolution 3.38.4
Date: Fri, 21 Nov 1997 09:55:06 -0600
X-Evolution-Source: b3da4551047fb9d8754f4a75ca595ea448b9dcb1
--=-qEPtUjpmMQEwviXs/uAF
Content-Type: multipart/alternative; boundary="=-8pdwW9TQhAvOhHeSXkVG"
--=-8pdwW9TQhAvOhHeSXkVG
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable
A strange mail
--=-8pdwW9TQhAvOhHeSXkVG
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
<html><head></head><body style=3D"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;"><div>A strange mail</div><div><span></span></div></body></html>
--=-8pdwW9TQhAvOhHeSXkVG--
--=-qEPtUjpmMQEwviXs/uAF
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Disposition: attachment; filename="smime.p7s"
Content-Transfer-Encoding: base64
ZGF0YQo=
--=-qEPtUjpmMQEwviXs/uAF--
`

0 comments on commit 37dc7e0

Please sign in to comment.