encoding.go raw
1 package message
2
3 import (
4 "bytes"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "mime/quotedprintable"
10 "strings"
11 )
12
13 type UnknownEncodingError struct {
14 e error
15 }
16
17 func (u UnknownEncodingError) Unwrap() error { return u.e }
18
19 func (u UnknownEncodingError) Error() string {
20 return "encoding error: " + u.e.Error()
21 }
22
23 // IsUnknownEncoding returns a boolean indicating whether the error is known to
24 // report that the encoding advertised by the entity is unknown.
25 func IsUnknownEncoding(err error) bool {
26 return errors.As(err, new(UnknownEncodingError))
27 }
28
29 func encodingReader(enc string, r io.Reader) (io.Reader, error) {
30 var dec io.Reader
31 switch strings.ToLower(enc) {
32 case "quoted-printable":
33 dec = quotedprintable.NewReader(r)
34 case "base64":
35 wrapped := &whitespaceReplacingReader{wrapped: r}
36 dec = base64.NewDecoder(base64.StdEncoding, wrapped)
37 case "7bit", "8bit", "binary", "":
38 dec = r
39 default:
40 return nil, fmt.Errorf("unhandled encoding %q", enc)
41 }
42 return dec, nil
43 }
44
45 type nopCloser struct {
46 io.Writer
47 }
48
49 func (nopCloser) Close() error {
50 return nil
51 }
52
53 func encodingWriter(enc string, w io.Writer) (io.WriteCloser, error) {
54 var wc io.WriteCloser
55 switch strings.ToLower(enc) {
56 case "quoted-printable":
57 wc = quotedprintable.NewWriter(w)
58 case "base64":
59 wc = base64.NewEncoder(base64.StdEncoding, &lineWrapper{w: w, maxLineLen: 76})
60 case "7bit", "8bit":
61 wc = nopCloser{&lineWrapper{w: w, maxLineLen: 998}}
62 case "binary", "":
63 wc = nopCloser{w}
64 default:
65 return nil, fmt.Errorf("unhandled encoding %q", enc)
66 }
67 return wc, nil
68 }
69
70 // whitespaceReplacingReader replaces space and tab characters with a LF so
71 // base64 bodies with a continuation indent can be decoded by the base64 decoder
72 // even though it is against the spec.
73 type whitespaceReplacingReader struct {
74 wrapped io.Reader
75 }
76
77 func (r *whitespaceReplacingReader) Read(p []byte) (int, error) {
78 n, err := r.wrapped.Read(p)
79
80 for i := 0; i < n; i++ {
81 if p[i] == ' ' || p[i] == '\t' {
82 p[i] = '\n'
83 }
84 }
85
86 return n, err
87 }
88
89 type lineWrapper struct {
90 w io.Writer
91 maxLineLen int
92
93 curLineLen int
94 cr bool
95 }
96
97 func (w *lineWrapper) Write(b []byte) (int, error) {
98 var written int
99 for len(b) > 0 {
100 var l []byte
101 l, b = cutLine(b, w.maxLineLen-w.curLineLen)
102
103 lf := bytes.HasSuffix(l, []byte("\n"))
104 l = bytes.TrimSuffix(l, []byte("\n"))
105
106 n, err := w.w.Write(l)
107 if err != nil {
108 return written, err
109 }
110 written += n
111
112 cr := bytes.HasSuffix(l, []byte("\r"))
113 if len(l) == 0 {
114 cr = w.cr
115 }
116
117 if !lf && len(b) == 0 {
118 w.curLineLen += len(l)
119 w.cr = cr
120 break
121 }
122 w.curLineLen = 0
123
124 ending := []byte("\r\n")
125 if cr {
126 ending = []byte("\n")
127 }
128 _, err = w.w.Write(ending)
129 if err != nil {
130 return written, err
131 }
132 // If the written `\n` was part of the input bytes slice, then account for it.
133 if lf {
134 written++
135 }
136 w.cr = false
137 }
138
139 return written, nil
140 }
141
142 func cutLine(b []byte, max int) ([]byte, []byte) {
143 for i := 0; i < len(b); i++ {
144 if b[i] == '\r' && i == max {
145 continue
146 }
147 if b[i] == '\n' {
148 return b[:i+1], b[i+1:]
149 }
150 if i >= max {
151 return b[:i], b[i:]
152 }
153 }
154 return b, nil
155 }
156