-
Notifications
You must be signed in to change notification settings - Fork 0
/
message.go
244 lines (200 loc) · 6.26 KB
/
message.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package MIMEMail
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/smtp"
"net/textproto"
"github.com/tike/MIMEMail/templated"
)
// Mail represents a MIME email message and handles encoding,
// MIME headers and so on.
type Mail struct {
// Address Lists for the Mailheader,
// Fields that are nil, will be ignored.
// Use the Add_Recipient or AddAddress for convienice.
Addresses
// The subject Line
Subject string
parts []*MIMEPart
// for testing purposes only
boundary string
}
// NewMail returns a new mail object ready to use.
func NewMail() *Mail {
return &Mail{
Addresses: NewAddresses(),
parts: make([]*MIMEPart, 0, 1),
}
}
// NewTemplated renders the template specified by config and name using data as the
// rendering context. The results will be put into the Subject and HTMLBody of the
// returned Mail struct.
func NewTemplated(cnf *templated.Config, name string, data interface{}) (*Mail, error) {
subj, body, err := templated.Render(cnf, name, data)
if err != nil {
return nil, err
}
m := NewMail()
m.Subject = subj
bodyPart := NewHTML()
bodyPart.Buffer = bytes.NewBuffer(body)
m.parts = append(m.parts, bodyPart)
return m, nil
}
// SendMail sends the mail via smtp.SendMail (which uses StartTLS if available).
// If you have the Sender field set, it's first entry is used and
// should match the Address in auth, else the first "From" entry
// is used (with the same restrictions). If both are nil,
// a NoSender error is returned.
// These values are then passed on to smtp.SendMail, returning any errors it throws.
func (m *Mail) SendMail(adr string, auth smtp.Auth) error {
msg, err := m.Bytes()
if err != nil {
return err
}
from, err := m.EffectiveSender()
if err != nil {
return err
}
return smtp.SendMail(adr, auth, from, m.Recipients(), msg)
}
// AddFile adds the file given by filename as an attachment to the mail.
// If you provide the optional attachmentname argument, the file will be
// attached with this name.
func (m *Mail) AddFile(filename string, attachmentname ...string) error {
p, err := NewFile(filename, attachmentname...)
if err != nil {
return err
}
m.parts = append(m.parts, p)
return nil
}
// AddReader adds the given reader as an attachment, using name as the filename.
func (m *Mail) AddReader(name string, r io.Reader) error {
p, err := NewAttachment(name, r)
if err != nil {
return err
}
m.parts = append(m.parts, p)
return nil
}
func (m *Mail) getHeader() textproto.MIMEHeader {
part := make(textproto.MIMEHeader)
part = m.ToMimeHeader(part)
part.Set("Subject", m.Subject)
part.Set("MIME-Version", "1.0")
return part
}
// HTMLBody adds a HTML body part and returns a buffer that you can render your Template to.
func (m *Mail) HTMLBody() io.Writer {
p := NewHTML()
m.parts = append(m.parts, p)
return p
}
// PlainTextBody adds a Plaintext body part and returns a buffer that you can render your Template to.
func (m *Mail) PlainTextBody() io.Writer {
p := NewPlainText()
m.parts = append(m.parts, p)
return p
}
// Bytes returns the fully formatted complete message as a slice of bytes.
// This is for plain MIME mails, if you want a PGP/MIME encrypted mail, use the Encrypt method instead.
func (m *Mail) Bytes() ([]byte, error) {
msg := bytes.NewBuffer(nil)
if err := m.write(msg); err != nil {
return nil, err
}
return msg.Bytes(), nil
}
var headerOrder = []string{"Sender", "From", "To", "Cc", "Bcc", "ReplyTo", "FollowupTo", "Subject", "MIME-Version"}
func (m *Mail) writeHeader(w io.Writer) error {
header := m.getHeader()
for _, field := range headerOrder {
for _, value := range header.Values(field) {
if _, err := w.Write([]byte(fmt.Sprintf("%s: %s\r\n", field, value))); err != nil {
return err
}
}
}
return nil
}
func (m *Mail) writeBody(w io.Writer) error {
mpw := multipart.NewWriter(w)
m.boundary = mpw.Boundary()
w.Write([]byte(fmt.Sprintf("%s: %s; boundary=%s\r\n\r\n", content_type, mime_multipart, mpw.Boundary())))
for _, part := range m.parts {
pw, err := mpw.CreatePart(part.MIMEHeader)
if err != nil {
return err
}
if _, err := pw.Write(part.Bytes()); err != nil {
return err
}
}
return mpw.Close()
}
func (m *Mail) write(w io.Writer) error {
if err := m.writeHeader(w); err != nil {
return err
}
if err := m.writeBody(w); err != nil {
return err
}
return nil
}
// WriteTo writes the fully formatted complete message to the given writer.
// This is for plain MIME mails, if you want a PGP/MIME encrypted mail, use the WriteEncrypted method instead.
func (m *Mail) WriteTo(w io.Writer) error {
return m.write(w)
}
// Encrypt encrypts the mail with PGP/MIME using CreateEntity to obtain recpient and CreateSigningEntity to obtain the signing entity.
// If signer is nil, the mail will simply not be signed. The Key Fields of both to and signer must be non-nil.
func (m *Mail) Encrypt(to *Account, signer *Account) ([]byte, error) {
var b bytes.Buffer
if err := m.WriteEncrypted(&b, to, signer); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// WriteEncrypted encrypts the mail with PGP/MIME using CreateEntity to obtain the recpient and CreateSigningEntity to obtain the signing entity.
// If signer is nil, the mail will simply not be signed. The Key Fields of both to and signer must be non-nil.
func (m *Mail) WriteEncrypted(w io.Writer, to *Account, signer *Account) error {
if err := m.writeHeader(w); err != nil {
return err
}
mpw := multipart.NewWriter(w)
pgpMIMEheader := fmt.Sprintf("%s: %s; protocol=%q; boundary=%q\r\n\r\n",
content_type, "multipart/encrypted", "application/pgp-encrypted", mpw.Boundary())
if _, err := w.Write([]byte(pgpMIMEheader)); err != nil {
return err
}
pgpVersion := NewPGPVersion()
pgpVersionBody, err := mpw.CreatePart(pgpVersion.MIMEHeader)
if err != nil {
return err
}
if _, err := pgpVersionBody.Write(pgpVersion.Bytes()); err != nil {
return err
}
pgpBody := NewPGPBody()
pgpBodyPart, err := mpw.CreatePart(pgpBody.MIMEHeader)
if err != nil {
return err
}
plainTextWriter, err := Encrypt(pgpBodyPart, to, signer)
if err != nil {
return err
}
if err := m.writeBody(plainTextWriter); err != nil {
return err
}
if err := plainTextWriter.Close(); err != nil {
return err
}
if _, err := pgpBodyPart.Write([]byte("\r\n")); err != nil {
return err
}
return mpw.Close()
}