forked from emersion/go-message
-
Notifications
You must be signed in to change notification settings - Fork 0
/
encoding.go
117 lines (98 loc) · 2.78 KB
/
encoding.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
package message
import (
"encoding/base64"
"errors"
"fmt"
"io"
mimeqp "mime/quotedprintable"
"strings"
"sync"
"github.com/emersion/go-textwrapper"
)
var (
decoders sync.Map // map[string]decoderProviderFn
)
type UnknownEncodingError struct {
e error
}
func (u UnknownEncodingError) Unwrap() error { return u.e }
func (u UnknownEncodingError) Error() string {
return "encoding error: " + u.e.Error()
}
// IsUnknownEncoding returns a boolean indicating whether the error is known to
// report that the encoding advertised by the entity is unknown.
func IsUnknownEncoding(err error) bool {
return errors.As(err, new(UnknownEncodingError))
}
// DecoderProviderFn should return an implementation of io.Reader capable of
// decoding the transport encoding it was registered with, or nil to use the
// module defaults.
type DecoderProviderFn func(r io.Reader) io.Reader
// RegisterTransportDecoder allows custom decoders for a specified transport
// encoding, which can override the module defaults. If there is existing
// custom decoder for a transportEncoding, it is replaced.
func RegisterTransportDecoder(transportEncoding string, f DecoderProviderFn) {
if transportEncoding == "" || f == nil {
return
}
decoders.Store(strings.ToLower(transportEncoding), f)
}
func encodingReader(enc string, r io.Reader) (io.Reader, error) {
var dec io.Reader
enc = strings.ToLower(enc)
if f, ok := decoders.Load(enc); ok {
dec = f.(DecoderProviderFn)(r)
if dec != nil {
return dec, nil
}
}
switch enc {
case "quoted-printable":
dec = mimeqp.NewReader(r)
case "base64":
wrapped := &whitespaceReplacingReader{wrapped: r}
dec = base64.NewDecoder(base64.StdEncoding, wrapped)
case "7bit", "8bit", "binary", "":
dec = r
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return dec, nil
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error {
return nil
}
func encodingWriter(enc string, w io.Writer) (io.WriteCloser, error) {
var wc io.WriteCloser
switch strings.ToLower(enc) {
case "quoted-printable":
wc = mimeqp.NewWriter(w)
case "base64":
wc = base64.NewEncoder(base64.StdEncoding, textwrapper.NewRFC822(w))
case "7bit", "8bit":
wc = nopCloser{textwrapper.New(w, "\r\n", 1000)}
case "binary", "":
wc = nopCloser{w}
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return wc, nil
}
// whitespaceReplacingReader replaces space and tab characters with a LF so
// base64 bodies with a continuation indent can be decoded by the base64 decoder
// even though it is against the spec.
type whitespaceReplacingReader struct {
wrapped io.Reader
}
func (r *whitespaceReplacingReader) Read(p []byte) (int, error) {
n, err := r.wrapped.Read(p)
for i := 0; i < n; i++ {
if p[i] == ' ' || p[i] == '\t' {
p[i] = '\n'
}
}
return n, err
}