forked from domodwyer/mailyak
-
Notifications
You must be signed in to change notification settings - Fork 0
/
attachments.go
124 lines (105 loc) · 2.94 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
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
}
// 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.
func (m *MailYak) Attach(name string, r io.Reader) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: false,
})
}
// 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.
func (m *MailYak) AttachInline(name string, r io.Reader) {
m.attachments = append(m.attachments, attachment{
filename: name,
content: r,
inline: true,
})
}
// 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 := item.content.Read(h)
if err != nil && err != io.EOF {
return err
}
ctype := fmt.Sprintf("%s;\n\tfilename=%q", http.DetectContentType(h[:hLen]), 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
if a.inline {
disp = fmt.Sprintf("inline;\n\tfilename=%q", a.filename)
header = textproto.MIMEHeader{
"Content-Type": {ctype},
"Content-Disposition": {disp},
"Content-Transfer-Encoding": {"base64"},
}
} else {
disp = fmt.Sprintf("attachment;\n\tfilename=%q", a.filename)
cid := fmt.Sprintf("<%s>", a.filename)
header = textproto.MIMEHeader{
"Content-Type": {ctype},
"Content-Disposition": {disp},
"Content-Transfer-Encoding": {"base64"},
"Content-ID": {cid},
}
}
return header
}