1 package quotedprintable
2
3 import (
4 "bytes"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "strings"
10 "unicode"
11 "unicode/utf8"
12 )
13
14
15 type WordEncoder byte
16
17 const (
18
19 BEncoding = WordEncoder('b')
20
21 QEncoding = WordEncoder('q')
22 )
23
24 var (
25 errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
26 )
27
28
29
30
31 func (e WordEncoder) Encode(charset, s string) string {
32 if !needsEncoding(s) {
33 return s
34 }
35 return e.encodeWord(charset, s)
36 }
37
38 func needsEncoding(s string) bool {
39 for _, b := range s {
40 if (b < ' ' || b > '~') && b != '\t' {
41 return true
42 }
43 }
44 return false
45 }
46
47
48 func (e WordEncoder) encodeWord(charset, s string) string {
49 buf := getBuffer()
50 defer putBuffer(buf)
51
52 buf.WriteString("=?")
53 buf.WriteString(charset)
54 buf.WriteByte('?')
55 buf.WriteByte(byte(e))
56 buf.WriteByte('?')
57
58 if e == BEncoding {
59 w := base64.NewEncoder(base64.StdEncoding, buf)
60 io.WriteString(w, s)
61 w.Close()
62 } else {
63 enc := make([]byte, 3)
64 for i := 0; i < len(s); i++ {
65 b := s[i]
66 switch {
67 case b == ' ':
68 buf.WriteByte('_')
69 case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
70 buf.WriteByte(b)
71 default:
72 enc[0] = '='
73 enc[1] = upperhex[b>>4]
74 enc[2] = upperhex[b&0x0f]
75 buf.Write(enc)
76 }
77 }
78 }
79 buf.WriteString("?=")
80 return buf.String()
81 }
82
83 const upperhex = "0123456789ABCDEF"
84
85
86 type WordDecoder struct {
87
88
89
90
91
92
93 CharsetReader func(charset string, input io.Reader) (io.Reader, error)
94 }
95
96
97
98 func (d *WordDecoder) Decode(word string) (string, error) {
99 fields := strings.Split(word, "?")
100 if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
101 return "", errInvalidWord
102 }
103
104 content, err := decode(fields[2][0], fields[3])
105 if err != nil {
106 return "", err
107 }
108
109 buf := getBuffer()
110 defer putBuffer(buf)
111
112 if err := d.convert(buf, fields[1], content); err != nil {
113 return "", err
114 }
115
116 return buf.String(), nil
117 }
118
119
120
121 func (d *WordDecoder) DecodeHeader(header string) (string, error) {
122
123 i := strings.Index(header, "=?")
124 if i == -1 {
125 return header, nil
126 }
127
128 buf := getBuffer()
129 defer putBuffer(buf)
130
131 buf.WriteString(header[:i])
132 header = header[i:]
133
134 betweenWords := false
135 for {
136 start := strings.Index(header, "=?")
137 if start == -1 {
138 break
139 }
140 cur := start + len("=?")
141
142 i := strings.Index(header[cur:], "?")
143 if i == -1 {
144 break
145 }
146 charset := header[cur : cur+i]
147 cur += i + len("?")
148
149 if len(header) < cur+len("Q??=") {
150 break
151 }
152 encoding := header[cur]
153 cur++
154
155 if header[cur] != '?' {
156 break
157 }
158 cur++
159
160 j := strings.Index(header[cur:], "?=")
161 if j == -1 {
162 break
163 }
164 text := header[cur : cur+j]
165 end := cur + j + len("?=")
166
167 content, err := decode(encoding, text)
168 if err != nil {
169 betweenWords = false
170 buf.WriteString(header[:start+2])
171 header = header[start+2:]
172 continue
173 }
174
175
176
177 if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) {
178 buf.WriteString(header[:start])
179 }
180
181 if err := d.convert(buf, charset, content); err != nil {
182 return "", err
183 }
184
185 header = header[end:]
186 betweenWords = true
187 }
188
189 if len(header) > 0 {
190 buf.WriteString(header)
191 }
192
193 return buf.String(), nil
194 }
195
196 func decode(encoding byte, text string) ([]byte, error) {
197 switch encoding {
198 case 'B', 'b':
199 return base64.StdEncoding.DecodeString(text)
200 case 'Q', 'q':
201 return qDecode(text)
202 }
203 return nil, errInvalidWord
204 }
205
206 func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error {
207 switch {
208 case strings.EqualFold("utf-8", charset):
209 buf.Write(content)
210 case strings.EqualFold("iso-8859-1", charset):
211 for _, c := range content {
212 buf.WriteRune(rune(c))
213 }
214 case strings.EqualFold("us-ascii", charset):
215 for _, c := range content {
216 if c >= utf8.RuneSelf {
217 buf.WriteRune(unicode.ReplacementChar)
218 } else {
219 buf.WriteByte(c)
220 }
221 }
222 default:
223 if d.CharsetReader == nil {
224 return fmt.Errorf("mime: unhandled charset %q", charset)
225 }
226 r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content))
227 if err != nil {
228 return err
229 }
230 if _, err = buf.ReadFrom(r); err != nil {
231 return err
232 }
233 }
234 return nil
235 }
236
237
238
239 func hasNonWhitespace(s string) bool {
240 for _, b := range s {
241 switch b {
242
243
244 case ' ', '\t', '\n', '\r':
245 default:
246 return true
247 }
248 }
249 return false
250 }
251
252
253 func qDecode(s string) ([]byte, error) {
254 dec := make([]byte, len(s))
255 n := 0
256 for i := 0; i < len(s); i++ {
257 switch c := s[i]; {
258 case c == '_':
259 dec[n] = ' '
260 case c == '=':
261 if i+2 >= len(s) {
262 return nil, errInvalidWord
263 }
264 b, err := readHexByte(s[i+1], s[i+2])
265 if err != nil {
266 return nil, err
267 }
268 dec[n] = b
269 i += 2
270 case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t':
271 dec[n] = c
272 default:
273 return nil, errInvalidWord
274 }
275 n++
276 }
277
278 return dec[:n], nil
279 }
280
View as plain text