/
reader.go
145 lines (116 loc) · 3.71 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package mimestream
import (
"bufio"
"encoding/base64"
"io"
"mime"
"mime/multipart"
"net/textproto"
"strings"
"github.com/pkg/errors"
)
// Most emails will never contain more than 3 levels of nested multipart bodies
var MaximumMultipartDepth = 10
var ErrMaximumMultipartDepth = errors.New("Mimestream: Maximum multipart/mime nesting level reached")
// Most emails will never have more than a dozen attachments / text parts total
var MaximumPartsPerMultipart = 50
var ErrMaximumPartsPerMultipart = errors.New("Mimestream: Maximum number of multipart/mime parts reached")
// ErrMissingBoundary for multipart bodies without a boundary header
var ErrMissingBoundary = errors.New("Missing boundary")
// The format of the callback for handling MIME emails
type partHandler func(textproto.MIMEHeader, io.Reader) error
// NewEmailFromReader reads a stream of bytes from an io.Reader, r,
// and returns an email struct containing the parsed data.
// This function expects the data in RFC 5322 format.
func HandleEmailFromReader(r io.Reader, h partHandler) (err error) {
tp := textproto.NewReader(bufioReader(r))
var header textproto.MIMEHeader
header, err = tp.ReadMIMEHeader()
if err != nil {
return
}
// FYI: http.Header and textproto.MIMEHeader are both map[string][]string
// http.Header(header)
// header.(map[string][]string)
// (*map[string][]string).(header)
// Recursively parse the MIME parts
err = parseMIMEParts(header, tp.R, h, 0)
return
}
// parseMIMEParts will recursively walk a MIME entity calling the handler
func parseMIMEParts(hs textproto.MIMEHeader, body io.Reader, handler partHandler, level int) (err error) {
// Protect against bad actors
if level > MaximumMultipartDepth {
return ErrMaximumMultipartDepth
}
ct, params, err := mime.ParseMediaType(hs.Get("Content-Type"))
if err != nil {
return
}
// Either a leaf node, or not a multipart email
if !strings.HasPrefix(ct, "multipart/") {
err = handler(hs, contentDecoderReader(hs, body))
return
}
// Should we allow this?
if _, ok := params["boundary"]; !ok {
return ErrMissingBoundary
}
// Readers are buffered https://golang.org/src/mime/multipart/multipart.go#L99
mr := multipart.NewReader(body, params["boundary"])
var partsCounter int
var p *multipart.Part
for {
// Decodes quotedprintable: https://golang.org/src/mime/multipart/multipart.go#L128
// Closes last part reader: https://golang.org/src/mime/multipart/multipart.go#L302
p, err = mr.NextPart()
if err == io.EOF {
err = nil
break
}
if err != nil {
return
}
// Protect against bad actors
partsCounter++
if partsCounter > MaximumPartsPerMultipart {
return ErrMaximumPartsPerMultipart
}
// Correctly decode the body bytes
body := contentDecoderReader(p.Header, p)
var subct string
subct, _, err = mime.ParseMediaType(p.Header.Get("Content-Type"))
// Nested multipart
if strings.HasPrefix(subct, "multipart/") {
err = parseMIMEParts(p.Header, body, handler, level+1)
if err != nil {
return
}
} else {
// Leaf node
err = handler(p.Header, body)
if err != nil {
return
}
}
}
return
}
// contentDecoderReader
func contentDecoderReader(headers textproto.MIMEHeader, bodyReader io.Reader) *bufio.Reader {
// Already handled by textproto
// if headers.Get("Content-Transfer-Encoding") == "quoted-printable" {
// return bufioReader(quotedprintable.NewReader(bodyReader))
// }
if headers.Get("Content-Transfer-Encoding") == "base64" {
return bufioReader(base64.NewDecoder(base64.StdEncoding, bodyReader))
}
return bufioReader(bodyReader)
}
// bufioReader ...
func bufioReader(r io.Reader) *bufio.Reader {
if bufferedReader, ok := r.(*bufio.Reader); ok {
return bufferedReader
}
return bufio.NewReader(r)
}