-
Notifications
You must be signed in to change notification settings - Fork 75
/
attachments.go
166 lines (144 loc) · 4.51 KB
/
attachments.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package mailyak
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"net/textproto"
)
// DetectContentType needs at most 512 bytes
const sniffLen = 512
type partCreator interface {
CreatePart(header textproto.MIMEHeader) (io.Writer, error)
}
type writeWrapper interface {
new(w io.Writer) io.Writer
}
type attachment struct {
filename string
content io.Reader
inline bool
mimeType string
}
// Attach adds the contents of r to the email as an attachment with name as the
// filename.
//
// r is not read until Send is called and the MIME type will be detected
// using https://golang.org/pkg/net/http/#DetectContentType
func (m *MailYak) Attach(name string, r io.Reader) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: false,
})
}
// AttachWithMimeType adds the contents of r to the email as an attachment with
// name as the filename and mimeType as the specified MIME type of the content.
// It is up to the user to ensure the mimeType is correct.
//
// r is not read until Send is called.
func (m *MailYak) AttachWithMimeType(name string, r io.Reader, mimeType string) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: false,
mimeType: mimeType,
})
}
// AttachInline adds the contents of r to the email as an inline attachment.
// Inline attachments are typically used within the email body, such as a logo
// or header image. It is up to the user to ensure name is unique.
//
// Files can be referenced by their name within the email using the cid URL
// protocol:
//
// <img src="cid:myFileName"/>
//
// r is not read until Send is called and the MIME type will be detected
// using https://golang.org/pkg/net/http/#DetectContentType
func (m *MailYak) AttachInline(name string, r io.Reader) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: true,
})
}
// AttachInlineWithMimeType adds the contents of r to the email as an inline attachment
// with mimeType as the specified MIME type of the content. Inline attachments are
// typically used within the email body, such as a logo or header image. It is up to the
// user to ensure name is unique and the specified mimeType is correct.
//
// Files can be referenced by their name within the email using the cid URL
// protocol:
//
// <img src="cid:myFileName"/>
//
// r is not read until Send is called.
func (m *MailYak) AttachInlineWithMimeType(name string, r io.Reader, mimeType string) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: true,
mimeType: mimeType,
})
}
// ClearAttachments removes all current attachments.
func (m *MailYak) ClearAttachments() {
m.attachments = []attachment{}
}
// writeAttachments loops over the attachments, guesses their content-type and
// writes the data as a line-broken base64 string (using the splitter mutator).
func (m *MailYak) writeAttachments(mixed partCreator, splitter writeWrapper) error {
h := make([]byte, sniffLen)
for _, item := range m.attachments {
hLen, err := io.ReadFull(item.content, h)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return err
}
if item.mimeType == "" {
item.mimeType = http.DetectContentType(h[:hLen])
}
ctype := fmt.Sprintf("%s;\n\tfilename=%q; name=%q", item.mimeType, item.filename, item.filename)
part, err := mixed.CreatePart(getMIMEHeader(item, ctype))
if err != nil {
return err
}
encoder := base64.NewEncoder(base64.StdEncoding, splitter.new(part))
if _, err := encoder.Write(h[:hLen]); err != nil {
return err
}
// More to write?
if hLen == sniffLen {
if _, err := io.Copy(encoder, item.content); err != nil {
return err
}
}
if err := encoder.Close(); err != nil {
return err
}
}
return nil
}
func getMIMEHeader(a attachment, ctype string) textproto.MIMEHeader {
var disp string
var header textproto.MIMEHeader
cid := fmt.Sprintf("<%s>", a.filename)
if a.inline {
disp = fmt.Sprintf("inline;\n\tfilename=%q; name=%q", a.filename, a.filename)
header = textproto.MIMEHeader{
"Content-Type": {ctype},
"Content-Disposition": {disp},
"Content-Transfer-Encoding": {"base64"},
"Content-ID": {cid},
}
} else {
disp = fmt.Sprintf("attachment;\n\tfilename=%q; name=%q", a.filename, a.filename)
header = textproto.MIMEHeader{
"Content-Type": {ctype},
"Content-Disposition": {disp},
"Content-Transfer-Encoding": {"base64"},
"Content-ID": {cid},
}
}
return header
}