8
8
"io/ioutil"
9
9
"mime"
10
10
"mime/multipart"
11
+ "mime/quotedprintable"
11
12
"net/mail"
12
13
"strings"
13
14
"time"
@@ -38,6 +39,8 @@ func Parse(r io.Reader) (email Email, err error) {
38
39
return
39
40
}
40
41
42
+ cte := msg .Header .Get ("Content-Transfer-Encoding" )
43
+
41
44
switch contentType {
42
45
case contentTypeMultipartMixed :
43
46
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) {
47
50
email .TextBody , email .HTMLBody , email .EmbeddedFiles , err = parseMultipartRelated (msg .Body , params ["boundary" ])
48
51
case contentTypeTextPlain :
49
52
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
+
50
64
email .TextBody = strings .TrimSuffix (string (message [:]), "\n " )
51
65
case contentTypeTextHtml :
52
66
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
+
53
78
email .HTMLBody = strings .TrimSuffix (string (message [:]), "\n " )
54
79
case contentTypeOctetStream :
55
80
email .Attachments , err = parseAttachmentOnlyEmail (msg .Body , msg .Header )
56
81
default :
57
- email .Content , err = decodeContent (msg .Body , msg . Header . Get ( "Content-Transfer-Encoding" ) )
82
+ email .Content , err = decodeContent (msg .Body , cte )
58
83
}
59
84
60
85
return
@@ -134,29 +159,39 @@ func parseAttachmentOnlyEmail(body io.Reader, header mail.Header) (attachments [
134
159
func parseMultipartRelated (msg io.Reader , boundary string ) (textBody , htmlBody string , embeddedFiles []EmbeddedFile , err error ) {
135
160
pmr := multipart .NewReader (msg , boundary )
136
161
for {
137
- part , err := pmr .NextPart ()
162
+ part , err := pmr .NextRawPart ()
138
163
139
164
if err == io .EOF {
140
165
break
141
166
} else if err != nil {
142
167
return textBody , htmlBody , embeddedFiles , err
143
168
}
144
169
170
+ cte := part .Header .Get ("Content-Transfer-Encoding" )
171
+
145
172
contentType , params , err := mime .ParseMediaType (part .Header .Get ("Content-Type" ))
146
173
if err != nil {
147
174
return textBody , htmlBody , embeddedFiles , err
148
175
}
149
176
150
177
switch contentType {
151
178
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 )
153
184
if err != nil {
154
185
return textBody , htmlBody , embeddedFiles , err
155
186
}
156
187
157
188
textBody += strings .TrimSuffix (string (ppContent [:]), "\n " )
158
189
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 )
160
195
if err != nil {
161
196
return textBody , htmlBody , embeddedFiles , err
162
197
}
@@ -191,29 +226,39 @@ func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody s
191
226
func parseMultipartAlternative (msg io.Reader , boundary string ) (textBody , htmlBody string , embeddedFiles []EmbeddedFile , err error ) {
192
227
pmr := multipart .NewReader (msg , boundary )
193
228
for {
194
- part , err := pmr .NextPart ()
229
+ part , err := pmr .NextRawPart ()
195
230
196
231
if err == io .EOF {
197
232
break
198
233
} else if err != nil {
199
234
return textBody , htmlBody , embeddedFiles , err
200
235
}
201
236
237
+ cte := part .Header .Get ("Content-Transfer-Encoding" )
238
+
202
239
contentType , params , err := mime .ParseMediaType (part .Header .Get ("Content-Type" ))
203
240
if err != nil {
204
241
return textBody , htmlBody , embeddedFiles , err
205
242
}
206
243
207
244
switch contentType {
208
245
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 )
210
251
if err != nil {
211
252
return textBody , htmlBody , embeddedFiles , err
212
253
}
213
254
214
255
textBody += strings .TrimSuffix (string (ppContent [:]), "\n " )
215
256
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 )
217
262
if err != nil {
218
263
return textBody , htmlBody , embeddedFiles , err
219
264
}
@@ -248,7 +293,7 @@ func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBo
248
293
func parseMultipartMixed (msg io.Reader , boundary string ) (textBody , htmlBody string , attachments []Attachment , embeddedFiles []EmbeddedFile , err error ) {
249
294
mr := multipart .NewReader (msg , boundary )
250
295
for {
251
- part , err := mr .NextPart ()
296
+ part , err := mr .NextRawPart ()
252
297
if err == io .EOF {
253
298
break
254
299
} else if err != nil {
@@ -265,11 +310,21 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str
265
310
continue
266
311
}
267
312
313
+ cte := part .Header .Get ("Content-Transfer-Encoding" )
314
+
268
315
contentType , params , err := mime .ParseMediaType (part .Header .Get ("Content-Type" ))
269
316
if err != nil {
270
317
return textBody , htmlBody , attachments , embeddedFiles , err
271
318
}
272
319
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
+
273
328
if contentType == contentTypeMultipartAlternative {
274
329
textBody , htmlBody , embeddedFiles , err = parseMultipartAlternative (part , params ["boundary" ])
275
330
if err != nil {
@@ -281,14 +336,22 @@ func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody str
281
336
return textBody , htmlBody , attachments , embeddedFiles , err
282
337
}
283
338
} 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 )
285
344
if err != nil {
286
345
return textBody , htmlBody , attachments , embeddedFiles , err
287
346
}
288
347
289
348
textBody += strings .TrimSuffix (string (ppContent [:]), "\n " )
290
349
} 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 )
292
355
if err != nil {
293
356
return textBody , htmlBody , attachments , embeddedFiles , err
294
357
}
@@ -383,17 +446,25 @@ func decodeContent(content io.Reader, encoding string) (io.Reader, error) {
383
446
if err != nil {
384
447
return nil , err
385
448
}
386
-
387
449
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 )
390
453
if err != nil {
391
454
return nil , err
392
455
}
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
397
468
default :
398
469
return nil , fmt .Errorf ("unknown encoding: %s" , encoding )
399
470
}
0 commit comments