-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.go
227 lines (198 loc) · 5.06 KB
/
helpers.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package rfc2822
import (
"bufio"
"encoding/base64"
"fmt"
"io"
"mime"
"mime/quotedprintable"
"net/mail"
"strings"
)
func ParseContentType(s string) (ct ContentType, err error) {
mdType, params, err := mime.ParseMediaType(s)
if err != nil {
return ContentType{}, err
}
types := strings.Split(mdType, "/")
ct.Type = types[0]
ct.SubType = strings.Join(types[1:], "/")
ct.Params = params
return
}
// check if list of string contain item
func Contains(val string, items []string) bool {
for _, item := range items {
if val == item {
return true
}
}
return false
}
/*
Note:
The MIME specifications specify that the proper method for encoding Content-Type and Content-Disposition parameter values is the method described in rfc2231.
However, it is common for some older email clients to improperly encode using the method described in rfc2047 instead.
mime.ParseMediaType takes care of rfc2231 specs of encoded parameter types and decode it to utf-8
but it will not handle rfc2047 type encoding and will return an error
eg, this works
name*0*=utf-8''%D0%AD%D1%82%D0%BE%20%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE;\n\tname*1*=%D0%B5%20%D0%B8%D0%BC%D1%8F%20%D1%84%D0%B0%D0%B9%D0%BB%D0%B0.txt"
but this does not
name=\"=?utf-8?b?0K3RgtC+INGA0YPRgdGB0LrQvtC1INC40LzRjyDRhNCw0LnQu9CwLnR4?=\n\t=?utf-8?q?t?=\"
// TODO: Try to catch exact parsing errors, like in this case say something like
"badly encoded content disposition param" instead of a genereic "bad mime"
*/
func ParseContentDisposition(s string) (ct ContentDisposition, err error) {
ct.MediaType, ct.Params, err = mime.ParseMediaType(s)
if err != nil {
return ContentDisposition{}, err
}
return
}
// This is the exact clone of bufio.ReadBytes method but with an upper limit
// bufio.ReadBytes keeps reading till it finds the delim
// but it could be used to feed the parser very long bad data
func readBytesWithLimit(r *bufio.Reader, delim byte, limit int) ([]byte, error) {
var frag []byte
var full [][]byte
var err error
n := 0
for {
var e error
if n >= limit {
// Limit reached, don't read anymore
err = errMaxLineLength
break
}
frag, e = r.ReadSlice(delim)
if e == nil { // got final fragment
break
}
if e != bufio.ErrBufferFull { // unexpected error
err = e
break
}
// Make a copy of the buffer.
buf := make([]byte, len(frag))
copy(buf, frag)
full = append(full, buf)
n += len(buf)
}
if n >= limit {
buf := make([]byte, n)
n = 0
// Copy full pieces and fragment in.
for i := range full {
n += copy(buf[n:], full[i])
}
return buf, err
}
n += len(frag)
// Allocate new buffer to hold the full pieces and the fragment.
buf := make([]byte, n)
n = 0
// Copy full pieces and fragment in.
for i := range full {
n += copy(buf[n:], full[i])
}
copy(buf[n:], frag)
return buf, err
}
func validHeaderKeyByte(b byte) bool {
c := int(b)
return c >= 33 && c <= 126 && c != ':'
}
// TODO: Refer Enmime, add QP and b64 cleaners
func encodingReader(enc string, r io.Reader) (io.Reader, error) {
var dec io.Reader
switch strings.ToLower(enc) {
case "quoted-printable":
dec = quotedprintable.NewReader(r)
case "base64":
dec = base64.NewDecoder(base64.StdEncoding, r)
case "7bit", "8bit", "binary", "":
dec = r
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return dec, nil
}
func parseAddress(headerVal string) ([]*mail.Address, error) {
decodedAddr := decodeToUTF8Base64Header(headerVal)
ret, err := mail.ParseAddressList(decodedAddr)
if err != nil {
switch err.Error() {
case "mail: expected comma":
return mail.ParseAddressList(ensureCommaDelimitedAddresses(decodedAddr))
case "mail: no address":
return nil, mail.ErrHeaderNotPresent
}
return nil, err
}
return ret, nil
}
func IsInternational(val string) bool {
for i := 0; i < len(val); i++ {
if val[i] > 127 {
return true
}
}
return false
}
// Used by AddressList to ensure that address lists are properly delimited
func ensureCommaDelimitedAddresses(s string) string {
// This normalizes the whitespace, but may interfere with CFWS (comments with folding whitespace)
// RFC-5322 3.4.0:
// because some legacy implementations interpret the comment,
// comments generally SHOULD NOT be used in address fields
// to avoid confusing such implementations.
s = strings.Join(strings.Fields(s), " ")
inQuotes := false
inDomain := false
escapeSequence := false
sb := strings.Builder{}
for _, r := range s {
if escapeSequence {
escapeSequence = false
sb.WriteRune(r)
continue
}
if r == '"' {
inQuotes = !inQuotes
sb.WriteRune(r)
continue
}
if inQuotes {
if r == '\\' {
escapeSequence = true
sb.WriteRune(r)
continue
}
} else {
if r == '@' {
inDomain = true
sb.WriteRune(r)
continue
}
if inDomain {
if r == ';' {
sb.WriteRune(r)
break
}
if r == ',' {
inDomain = false
sb.WriteRune(r)
continue
}
if r == ' ' {
inDomain = false
sb.WriteRune(',')
sb.WriteRune(r)
continue
}
}
}
sb.WriteRune(r)
}
return sb.String()
}