Skip to content

Commit a714d99

Browse files
ArkaGPLMario Hros
authored and
Mario Hros
committed
use decodeContent on every part, NextRawPart for mime types (manual merge of arkagpl:master DusanKasan#28 and matoubidou:master DusanKasan#26)
1 parent daeeb1b commit a714d99

File tree

2 files changed

+105
-30
lines changed

2 files changed

+105
-30
lines changed

parsemail.go

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/ioutil"
99
"mime"
1010
"mime/multipart"
11+
"mime/quotedprintable"
1112
"net/mail"
1213
"strings"
1314
"time"
@@ -38,6 +39,8 @@ func Parse(r io.Reader) (email Email, err error) {
3839
return
3940
}
4041

42+
cte := msg.Header.Get("Content-Transfer-Encoding")
43+
4144
switch contentType {
4245
case contentTypeMultipartMixed:
4346
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) {
4750
email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartRelated(msg.Body, params["boundary"])
4851
case contentTypeTextPlain:
4952
message, _ := ioutil.ReadAll(msg.Body)
53+
var reader io.Reader
54+
reader, err = decodeContent(strings.NewReader(string(message[:])), cte)
55+
if err != nil {
56+
return
57+
}
58+
59+
message, err = ioutil.ReadAll(reader)
60+
if err != nil {
61+
return
62+
}
63+
5064
email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
5165
case contentTypeTextHtml:
5266
message, _ := ioutil.ReadAll(msg.Body)
67+
var reader io.Reader
68+
reader, err = decodeContent(strings.NewReader(string(message[:])), cte)
69+
if err != nil {
70+
return
71+
}
72+
73+
message, err = ioutil.ReadAll(reader)
74+
if err != nil {
75+
return
76+
}
77+
5378
email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
5479
case contentTypeOctetStream:
5580
email.Attachments, err = parseAttachmentOnlyEmail(msg.Body, msg.Header)
5681
default:
57-
email.Content, err = decodeContent(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
82+
email.Content, err = decodeContent(msg.Body, cte)
5883
}
5984

6085
return
@@ -134,29 +159,39 @@ func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments [
134159
func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
135160
pmr := multipart.NewReader(msg, boundary)
136161
for {
137-
part, err := pmr.NextPart()
162+
part, err := pmr.NextRawPart()
138163

139164
if err == io.EOF {
140165
break
141166
} else if err != nil {
142167
return textBody, htmlBody, embeddedFiles, err
143168
}
144169

170+
cte := part.Header.Get("Content-Transfer-Encoding")
171+
145172
contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
146173
if err != nil {
147174
return textBody, htmlBody, embeddedFiles, err
148175
}
149176

150177
switch contentType {
151178
case contentTypeTextPlain:
152-
ppContent, err := ioutil.ReadAll(part)
179+
decoded, err := decodeContent(part, cte)
180+
if err != nil {
181+
return textBody, htmlBody, embeddedFiles, err
182+
}
183+
ppContent, err := ioutil.ReadAll(decoded)
153184
if err != nil {
154185
return textBody, htmlBody, embeddedFiles, err
155186
}
156187

157188
textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
158189
case contentTypeTextHtml:
159-
ppContent, err := ioutil.ReadAll(part)
190+
decoded, err := decodeContent(part, cte)
191+
if err != nil {
192+
return textBody, htmlBody, embeddedFiles, err
193+
}
194+
ppContent, err := ioutil.ReadAll(decoded)
160195
if err != nil {
161196
return textBody, htmlBody, embeddedFiles, err
162197
}
@@ -191,29 +226,39 @@ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody s
191226
func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
192227
pmr := multipart.NewReader(msg, boundary)
193228
for {
194-
part, err := pmr.NextPart()
229+
part, err := pmr.NextRawPart()
195230

196231
if err == io.EOF {
197232
break
198233
} else if err != nil {
199234
return textBody, htmlBody, embeddedFiles, err
200235
}
201236

237+
cte := part.Header.Get("Content-Transfer-Encoding")
238+
202239
contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
203240
if err != nil {
204241
return textBody, htmlBody, embeddedFiles, err
205242
}
206243

207244
switch contentType {
208245
case contentTypeTextPlain:
209-
ppContent, err := ioutil.ReadAll(part)
246+
decoded, err := decodeContent(part, cte)
247+
if err != nil {
248+
return textBody, htmlBody, embeddedFiles, err
249+
}
250+
ppContent, err := ioutil.ReadAll(decoded)
210251
if err != nil {
211252
return textBody, htmlBody, embeddedFiles, err
212253
}
213254

214255
textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
215256
case contentTypeTextHtml:
216-
ppContent, err := ioutil.ReadAll(part)
257+
decoded, err := decodeContent(part, cte)
258+
if err != nil {
259+
return textBody, htmlBody, embeddedFiles, err
260+
}
261+
ppContent, err := ioutil.ReadAll(decoded)
217262
if err != nil {
218263
return textBody, htmlBody, embeddedFiles, err
219264
}
@@ -248,7 +293,7 @@ func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBo
248293
func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
249294
mr := multipart.NewReader(msg, boundary)
250295
for {
251-
part, err := mr.NextPart()
296+
part, err := mr.NextRawPart()
252297
if err == io.EOF {
253298
break
254299
} else if err != nil {
@@ -265,11 +310,21 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str
265310
continue
266311
}
267312

313+
cte := part.Header.Get("Content-Transfer-Encoding")
314+
268315
contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
269316
if err != nil {
270317
return textBody, htmlBody, attachments, embeddedFiles, err
271318
}
272319

320+
if isAttachment(part) {
321+
at, err := decodeAttachment(part)
322+
if err != nil {
323+
return textBody, htmlBody, attachments, embeddedFiles, err
324+
}
325+
attachments = append(attachments, at)
326+
}
327+
273328
if contentType == contentTypeMultipartAlternative {
274329
textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
275330
if err != nil {
@@ -281,14 +336,22 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str
281336
return textBody, htmlBody, attachments, embeddedFiles, err
282337
}
283338
} else if contentType == contentTypeTextPlain {
284-
ppContent, err := ioutil.ReadAll(part)
339+
decoded, err := decodeContent(part, cte)
340+
if err != nil {
341+
return textBody, htmlBody, attachments, embeddedFiles, err
342+
}
343+
ppContent, err := ioutil.ReadAll(decoded)
285344
if err != nil {
286345
return textBody, htmlBody, attachments, embeddedFiles, err
287346
}
288347

289348
textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
290349
} else if contentType == contentTypeTextHtml {
291-
ppContent, err := ioutil.ReadAll(part)
350+
decoded, err := decodeContent(part, cte)
351+
if err != nil {
352+
return textBody, htmlBody, attachments, embeddedFiles, err
353+
}
354+
ppContent, err := ioutil.ReadAll(decoded)
292355
if err != nil {
293356
return textBody, htmlBody, attachments, embeddedFiles, err
294357
}
@@ -383,17 +446,25 @@ func decodeContent(content io.Reader, encoding string) (io.Reader, error) {
383446
if err != nil {
384447
return nil, err
385448
}
386-
387449
return bytes.NewReader(b), nil
388-
case "7bit", "8bit", "binary:
389-
dd, err := ioutil.ReadAll(content)
450+
case "quoted-printable":
451+
decoded := quotedprintable.NewReader(content)
452+
b, err := ioutil.ReadAll(decoded)
390453
if err != nil {
391454
return nil, err
392455
}
393-
394-
return bytes.NewReader(dd), nil
395-
case "":
396-
return content, nil
456+
return bytes.NewReader(b), nil
457+
// The values "8bit", "7bit", and "binary" all imply that NO encoding has been performed and data need to be read as bytes.
458+
// "7bit" means that the data is all represented as short lines of US-ASCII data.
459+
// "8bit" means that the lines are short, but there may be non-ASCII characters (octets with the high-order bit set).
460+
// "Binary" means that not only may non-ASCII characters be present, but also that the lines are not necessarily short enough for SMTP transport.
461+
case "", "7bit", "8bit", "binary":
462+
decoded := quotedprintable.NewReader(content)
463+
b, err := ioutil.ReadAll(decoded)
464+
if err != nil {
465+
return nil, err
466+
}
467+
return bytes.NewReader(b), nil
397468
default:
398469
return nil, fmt.Errorf("unknown encoding: %s", encoding)
399470
}

parsemail_test.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,15 +372,15 @@ So, "Hello".`,
372372
htmlBody: "<div dir=\"ltr\"><br></div>",
373373
attachments: []attachmentData{
374374
{
375-
filename: "unencoded.csv",
376-
contentType: "application/csv",
377-
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"),
375+
filename: "unencoded.csv",
376+
contentType: "application/csv",
377+
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"),
378378
},
379379
},
380380
},
381381
13: {
382382
contentType: "multipart/related; boundary=\"000000000000ab2e2205a26de587\"",
383-
mailData: multipartRelatedExample,
383+
mailData: multipartRelatedExample,
384384
subject: "Saying Hello",
385385
from: []mail.Address{
386386
{
@@ -389,7 +389,7 @@ So, "Hello".`,
389389
},
390390
},
391391
sender: mail.Address{
392-
Name: "Michael Jones",
392+
Name: "Michael Jones",
393393
Address: "[email protected]",
394394
},
395395
to: []mail.Address{
@@ -401,7 +401,7 @@ So, "Hello".`,
401401
messageID: "[email protected]",
402402
date: parseDate("Fri, 21 Nov 1997 09:55:06 -0600"),
403403
htmlBody: "<div dir=\"ltr\"><div>Time for the egg.</div><div><br></div><div><br><br></div></div>",
404-
textBody: "Time for the egg.",
404+
textBody: "Time for the egg.",
405405
},
406406
14: {
407407
mailData: data3,
@@ -563,10 +563,14 @@ So, "Hello".`,
563563
t.Error(err)
564564
}
565565

566-
if ra.Filename == ad.filename && string(b) == ad.data && ra.ContentType == ad.contentType {
566+
if ra.Filename == ad.filename && ra.ContentType == ad.contentType {
567567
found = true
568568
attachs = append(attachs[:i], attachs[i+1:]...)
569569
}
570+
571+
if string(b) != ad.data {
572+
t.Errorf("[Test Case %v] Bad data for attachment: \nEXPECTED:\n%s\nHAVE:\n%s", index, ad.data, string(b))
573+
}
570574
}
571575

572576
if !found {
@@ -623,9 +627,9 @@ func parseDate(in string) time.Time {
623627
}
624628

625629
type attachmentData struct {
626-
filename string
627-
contentType string
628-
data string
630+
filename string
631+
contentType string
632+
data string
629633
}
630634

631635
type embeddedFileData struct {
@@ -869,8 +873,8 @@ Message-ID: <[email protected]>
869873
Hi everyone.
870874
`
871875

872-
//todo: not yet implemented in net/mail
873-
//once there is support for this, add it
876+
// todo: not yet implemented in net/mail
877+
// once there is support for this, add it
874878
var rfc5322exampleA13 = `From: Pete <[email protected]>
875879
876880
Cc: Undisclosed recipients:;
@@ -880,7 +884,7 @@ Message-ID: <[email protected]>
880884
Testing.
881885
`
882886

883-
//we skipped the first message bcause it's the same as A 1.1
887+
// we skipped the first message bcause it's the same as A 1.1
884888
var rfc5322exampleA2a = `From: Mary Smith <[email protected]>
885889
To: John Doe <[email protected]>
886890
Reply-To: "Mary Smith: Personal Account" <[email protected]>

0 commit comments

Comments
 (0)