forked from emersion/go-message
-
Notifications
You must be signed in to change notification settings - Fork 0
/
writer.go
117 lines (100 loc) · 2.83 KB
/
writer.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 (
"errors"
"fmt"
"io"
"mime/multipart"
"net/textproto"
"sort"
"strings"
)
// From https://golang.org/src/mime/multipart/writer.go?s=2140:2215#L76
func writeHeader(w io.Writer, header Header) error {
keys := make([]string, 0, len(header))
for k := range header {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
for _, v := range header[k] {
if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
return err
}
}
}
_, err := fmt.Fprintf(w, "\r\n")
return err
}
// A Writer formats entities.
type Writer struct {
w io.Writer
c io.Closer
mw *multipart.Writer
}
// newWriter creates a new Writer writing to w with the provided header. Nothing
// is written to w when it is called. header is modified in-place.
func newWriter(w io.Writer, header Header) *Writer {
ww := &Writer{w: w}
mediaType, mediaParams, _ := header.ContentType()
if strings.HasPrefix(mediaType, "multipart/") {
ww.mw = multipart.NewWriter(ww.w)
// Do not set ww's io.Closer for now: if this is a multipart entity but
// CreatePart is not used (only Write is used), then the final boundary
// is expected to be written by the user too. In this case, ww.Close
// shouldn't write the final boundary.
if mediaParams["boundary"] != "" {
ww.mw.SetBoundary(mediaParams["boundary"])
} else {
mediaParams["boundary"] = ww.mw.Boundary()
header.SetContentType(mediaType, mediaParams)
}
header.Del("Content-Transfer-Encoding")
} else {
wc := encodingWriter(header.Get("Content-Transfer-Encoding"), ww.w)
ww.w = wc
ww.c = wc
}
return ww
}
// CreateWriter creates a new Writer writing to w. If header contains an
// encoding, data written to the Writer will automatically be encoded with it.
func CreateWriter(w io.Writer, header Header) (*Writer, error) {
ww := newWriter(w, header)
if err := writeHeader(w, header); err != nil {
return nil, err
}
return ww, nil
}
// Write implements io.Writer.
func (w *Writer) Write(b []byte) (int, error) {
return w.w.Write(b)
}
// Close implements io.Closer.
func (w *Writer) Close() error {
if w.c != nil {
return w.c.Close()
}
return nil
}
// CreatePart returns a Writer to a new part in this multipart entity. If this
// entity is not multipart, it fails. The body of the part should be written to
// the returned io.WriteCloser.
func (w *Writer) CreatePart(header Header) (*Writer, error) {
if w.mw == nil {
return nil, errors.New("messages: cannot create a part in a non-multipart message")
}
if w.c == nil {
// We know that the user calls CreatePart so Close should write the final
// boundary
w.c = w.mw
}
// cw -> ww -> pw -> w.mw -> w.w
ww := &struct{ io.Writer }{nil}
cw := newWriter(ww, header)
pw, err := w.mw.CreatePart(textproto.MIMEHeader(header))
if err != nil {
return nil, err
}
ww.Writer = pw
return cw, nil
}