1 package mail
2
3 import (
4 "encoding/base64"
5 "errors"
6 "io"
7 "mime"
8 "mime/multipart"
9 "path/filepath"
10 "strings"
11 "time"
12 )
13
14
15 func (m *Message) WriteTo(w io.Writer) (int64, error) {
16 mw := &messageWriter{w: w}
17 mw.writeMessage(m)
18 return mw.n, mw.err
19 }
20
21 func (w *messageWriter) writeMessage(m *Message) {
22 if _, ok := m.header["MIME-Version"]; !ok {
23 w.writeString("MIME-Version: 1.0\r\n")
24 }
25 if _, ok := m.header["Date"]; !ok {
26 w.writeHeader("Date", m.FormatDate(now()))
27 }
28 w.writeHeaders(m.header)
29
30 if m.hasMixedPart() {
31 w.openMultipart("mixed", m.boundary)
32 }
33
34 if m.hasRelatedPart() {
35 w.openMultipart("related", m.boundary)
36 }
37
38 if m.hasAlternativePart() {
39 w.openMultipart("alternative", m.boundary)
40 }
41 for _, part := range m.parts {
42 w.writePart(part, m.charset)
43 }
44 if m.hasAlternativePart() {
45 w.closeMultipart()
46 }
47
48 w.addFiles(m.embedded, false)
49 if m.hasRelatedPart() {
50 w.closeMultipart()
51 }
52
53 w.addFiles(m.attachments, true)
54 if m.hasMixedPart() {
55 w.closeMultipart()
56 }
57 }
58
59 func (m *Message) hasMixedPart() bool {
60 return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
61 }
62
63 func (m *Message) hasRelatedPart() bool {
64 return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
65 }
66
67 func (m *Message) hasAlternativePart() bool {
68 return len(m.parts) > 1
69 }
70
71 type messageWriter struct {
72 w io.Writer
73 n int64
74 writers [3]*multipart.Writer
75 partWriter io.Writer
76 depth uint8
77 err error
78 }
79
80 func (w *messageWriter) openMultipart(mimeType, boundary string) {
81 mw := multipart.NewWriter(w)
82 if boundary != "" {
83 mw.SetBoundary(boundary)
84 }
85 contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
86 w.writers[w.depth] = mw
87
88 if w.depth == 0 {
89 w.writeHeader("Content-Type", contentType)
90 w.writeString("\r\n")
91 } else {
92 w.createPart(map[string][]string{
93 "Content-Type": {contentType},
94 })
95 }
96 w.depth++
97 }
98
99 func (w *messageWriter) createPart(h map[string][]string) {
100 w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
101 }
102
103 func (w *messageWriter) closeMultipart() {
104 if w.depth > 0 {
105 w.writers[w.depth-1].Close()
106 w.depth--
107 }
108 }
109
110 func (w *messageWriter) writePart(p *part, charset string) {
111 w.writeHeaders(map[string][]string{
112 "Content-Type": {p.contentType + "; charset=" + charset},
113 "Content-Transfer-Encoding": {string(p.encoding)},
114 })
115 w.writeBody(p.copier, p.encoding)
116 }
117
118 func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
119 for _, f := range files {
120 if _, ok := f.Header["Content-Type"]; !ok {
121 mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
122 if mediaType == "" {
123 mediaType = "application/octet-stream"
124 }
125 f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
126 }
127
128 if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
129 f.setHeader("Content-Transfer-Encoding", string(Base64))
130 }
131
132 if _, ok := f.Header["Content-Disposition"]; !ok {
133 var disp string
134 if isAttachment {
135 disp = "attachment"
136 } else {
137 disp = "inline"
138 }
139 f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
140 }
141
142 if !isAttachment {
143 if _, ok := f.Header["Content-ID"]; !ok {
144 f.setHeader("Content-ID", "<"+f.Name+">")
145 }
146 }
147 w.writeHeaders(f.Header)
148 w.writeBody(f.CopyFunc, Base64)
149 }
150 }
151
152 func (w *messageWriter) Write(p []byte) (int, error) {
153 if w.err != nil {
154 return 0, errors.New("gomail: cannot write as writer is in error")
155 }
156
157 var n int
158 n, w.err = w.w.Write(p)
159 w.n += int64(n)
160 return n, w.err
161 }
162
163 func (w *messageWriter) writeString(s string) {
164 if w.err != nil {
165 return
166 }
167 var n int
168 n, w.err = io.WriteString(w.w, s)
169 w.n += int64(n)
170 }
171
172 func (w *messageWriter) writeHeader(k string, v ...string) {
173 w.writeString(k)
174 if len(v) == 0 {
175 w.writeString(":\r\n")
176 return
177 }
178 w.writeString(": ")
179
180
181
182
183 charsLeft := 76 - len(k) - len(": ")
184
185 for i, s := range v {
186
187 if charsLeft < 1 {
188 if i == 0 {
189 w.writeString("\r\n ")
190 } else {
191 w.writeString(",\r\n ")
192 }
193 charsLeft = 75
194 } else if i != 0 {
195 w.writeString(", ")
196 charsLeft -= 2
197 }
198
199
200 for len(s) > charsLeft {
201 s = w.writeLine(s, charsLeft)
202 charsLeft = 75
203 }
204 w.writeString(s)
205 if i := lastIndexByte(s, '\n'); i != -1 {
206 charsLeft = 75 - (len(s) - i - 1)
207 } else {
208 charsLeft -= len(s)
209 }
210 }
211 w.writeString("\r\n")
212 }
213
214 func (w *messageWriter) writeLine(s string, charsLeft int) string {
215
216 if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
217 w.writeString(s[:i+1])
218 return s[i+1:]
219 }
220
221 for i := charsLeft - 1; i >= 0; i-- {
222 if s[i] == ' ' {
223 w.writeString(s[:i])
224 w.writeString("\r\n ")
225 return s[i+1:]
226 }
227 }
228
229
230
231 for i := 75; i < len(s); i++ {
232 if s[i] == ' ' {
233 w.writeString(s[:i])
234 w.writeString("\r\n ")
235 return s[i+1:]
236 }
237 if s[i] == '\n' {
238 w.writeString(s[:i+1])
239 return s[i+1:]
240 }
241 }
242
243
244 w.writeString(s)
245 return ""
246 }
247
248 func (w *messageWriter) writeHeaders(h map[string][]string) {
249 if w.depth == 0 {
250 for k, v := range h {
251 if k != "Bcc" {
252 w.writeHeader(k, v...)
253 }
254 }
255 } else {
256 w.createPart(h)
257 }
258 }
259
260 func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
261 var subWriter io.Writer
262 if w.depth == 0 {
263 w.writeString("\r\n")
264 subWriter = w.w
265 } else {
266 subWriter = w.partWriter
267 }
268
269 if enc == Base64 {
270 wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
271 w.err = f(wc)
272 wc.Close()
273 } else if enc == Unencoded {
274 w.err = f(subWriter)
275 } else {
276 wc := newQPWriter(subWriter)
277 w.err = f(wc)
278 wc.Close()
279 }
280 }
281
282
283
284 const maxLineLen = 76
285
286
287 type base64LineWriter struct {
288 w io.Writer
289 lineLen int
290 }
291
292 func newBase64LineWriter(w io.Writer) *base64LineWriter {
293 return &base64LineWriter{w: w}
294 }
295
296 func (w *base64LineWriter) Write(p []byte) (int, error) {
297 n := 0
298 for len(p)+w.lineLen > maxLineLen {
299 w.w.Write(p[:maxLineLen-w.lineLen])
300 w.w.Write([]byte("\r\n"))
301 p = p[maxLineLen-w.lineLen:]
302 n += maxLineLen - w.lineLen
303 w.lineLen = 0
304 }
305
306 w.w.Write(p)
307 w.lineLen += len(p)
308
309 return n + len(p), nil
310 }
311
312
313 var now = time.Now
314
View as plain text