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 [
134159func 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
191226func 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
248293func 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 }
0 commit comments