mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
8.0 KiB
322 lines
8.0 KiB
package gomail |
|
|
|
import ( |
|
"bytes" |
|
"io" |
|
"os" |
|
"path/filepath" |
|
"time" |
|
) |
|
|
|
// Message represents an email. |
|
type Message struct { |
|
header header |
|
parts []*part |
|
attachments []*file |
|
embedded []*file |
|
charset string |
|
encoding Encoding |
|
hEncoder mimeEncoder |
|
buf bytes.Buffer |
|
} |
|
|
|
type header map[string][]string |
|
|
|
type part struct { |
|
contentType string |
|
copier func(io.Writer) error |
|
encoding Encoding |
|
} |
|
|
|
// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding |
|
// by default. |
|
func NewMessage(settings ...MessageSetting) *Message { |
|
m := &Message{ |
|
header: make(header), |
|
charset: "UTF-8", |
|
encoding: QuotedPrintable, |
|
} |
|
|
|
m.applySettings(settings) |
|
|
|
if m.encoding == Base64 { |
|
m.hEncoder = bEncoding |
|
} else { |
|
m.hEncoder = qEncoding |
|
} |
|
|
|
return m |
|
} |
|
|
|
// Reset resets the message so it can be reused. The message keeps its previous |
|
// settings so it is in the same state that after a call to NewMessage. |
|
func (m *Message) Reset() { |
|
for k := range m.header { |
|
delete(m.header, k) |
|
} |
|
m.parts = nil |
|
m.attachments = nil |
|
m.embedded = nil |
|
} |
|
|
|
func (m *Message) applySettings(settings []MessageSetting) { |
|
for _, s := range settings { |
|
s(m) |
|
} |
|
} |
|
|
|
// A MessageSetting can be used as an argument in NewMessage to configure an |
|
// email. |
|
type MessageSetting func(m *Message) |
|
|
|
// SetCharset is a message setting to set the charset of the email. |
|
func SetCharset(charset string) MessageSetting { |
|
return func(m *Message) { |
|
m.charset = charset |
|
} |
|
} |
|
|
|
// SetEncoding is a message setting to set the encoding of the email. |
|
func SetEncoding(enc Encoding) MessageSetting { |
|
return func(m *Message) { |
|
m.encoding = enc |
|
} |
|
} |
|
|
|
// Encoding represents a MIME encoding scheme like quoted-printable or base64. |
|
type Encoding string |
|
|
|
const ( |
|
// QuotedPrintable represents the quoted-printable encoding as defined in |
|
// RFC 2045. |
|
QuotedPrintable Encoding = "quoted-printable" |
|
// Base64 represents the base64 encoding as defined in RFC 2045. |
|
Base64 Encoding = "base64" |
|
// Unencoded can be used to avoid encoding the body of an email. The headers |
|
// will still be encoded using quoted-printable encoding. |
|
Unencoded Encoding = "8bit" |
|
) |
|
|
|
// SetHeader sets a value to the given header field. |
|
func (m *Message) SetHeader(field string, value ...string) { |
|
m.encodeHeader(value) |
|
m.header[field] = value |
|
} |
|
|
|
func (m *Message) encodeHeader(values []string) { |
|
for i := range values { |
|
values[i] = m.encodeString(values[i]) |
|
} |
|
} |
|
|
|
func (m *Message) encodeString(value string) string { |
|
return m.hEncoder.Encode(m.charset, value) |
|
} |
|
|
|
// SetHeaders sets the message headers. |
|
func (m *Message) SetHeaders(h map[string][]string) { |
|
for k, v := range h { |
|
m.SetHeader(k, v...) |
|
} |
|
} |
|
|
|
// SetAddressHeader sets an address to the given header field. |
|
func (m *Message) SetAddressHeader(field, address, name string) { |
|
m.header[field] = []string{m.FormatAddress(address, name)} |
|
} |
|
|
|
// FormatAddress formats an address and a name as a valid RFC 5322 address. |
|
func (m *Message) FormatAddress(address, name string) string { |
|
if name == "" { |
|
return address |
|
} |
|
|
|
enc := m.encodeString(name) |
|
if enc == name { |
|
m.buf.WriteByte('"') |
|
for i := 0; i < len(name); i++ { |
|
b := name[i] |
|
if b == '\\' || b == '"' { |
|
m.buf.WriteByte('\\') |
|
} |
|
m.buf.WriteByte(b) |
|
} |
|
m.buf.WriteByte('"') |
|
} else if hasSpecials(name) { |
|
m.buf.WriteString(bEncoding.Encode(m.charset, name)) |
|
} else { |
|
m.buf.WriteString(enc) |
|
} |
|
m.buf.WriteString(" <") |
|
m.buf.WriteString(address) |
|
m.buf.WriteByte('>') |
|
|
|
addr := m.buf.String() |
|
m.buf.Reset() |
|
return addr |
|
} |
|
|
|
func hasSpecials(text string) bool { |
|
for i := 0; i < len(text); i++ { |
|
switch c := text[i]; c { |
|
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"': |
|
return true |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
// SetDateHeader sets a date to the given header field. |
|
func (m *Message) SetDateHeader(field string, date time.Time) { |
|
m.header[field] = []string{m.FormatDate(date)} |
|
} |
|
|
|
// FormatDate formats a date as a valid RFC 5322 date. |
|
func (m *Message) FormatDate(date time.Time) string { |
|
return date.Format(time.RFC1123Z) |
|
} |
|
|
|
// GetHeader gets a header field. |
|
func (m *Message) GetHeader(field string) []string { |
|
return m.header[field] |
|
} |
|
|
|
// SetBody sets the body of the message. It replaces any content previously set |
|
// by SetBody, AddAlternative or AddAlternativeWriter. |
|
func (m *Message) SetBody(contentType, body string, settings ...PartSetting) { |
|
m.parts = []*part{m.newPart(contentType, newCopier(body), settings)} |
|
} |
|
|
|
// AddAlternative adds an alternative part to the message. |
|
// |
|
// It is commonly used to send HTML emails that default to the plain text |
|
// version for backward compatibility. AddAlternative appends the new part to |
|
// the end of the message. So the plain text part should be added before the |
|
// HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative |
|
func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) { |
|
m.AddAlternativeWriter(contentType, newCopier(body), settings...) |
|
} |
|
|
|
func newCopier(s string) func(io.Writer) error { |
|
return func(w io.Writer) error { |
|
_, err := io.WriteString(w, s) |
|
return err |
|
} |
|
} |
|
|
|
// AddAlternativeWriter adds an alternative part to the message. It can be |
|
// useful with the text/template or html/template packages. |
|
func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) { |
|
m.parts = append(m.parts, m.newPart(contentType, f, settings)) |
|
} |
|
|
|
func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part { |
|
p := &part{ |
|
contentType: contentType, |
|
copier: f, |
|
encoding: m.encoding, |
|
} |
|
|
|
for _, s := range settings { |
|
s(p) |
|
} |
|
|
|
return p |
|
} |
|
|
|
// A PartSetting can be used as an argument in Message.SetBody, |
|
// Message.AddAlternative or Message.AddAlternativeWriter to configure the part |
|
// added to a message. |
|
type PartSetting func(*part) |
|
|
|
// SetPartEncoding sets the encoding of the part added to the message. By |
|
// default, parts use the same encoding than the message. |
|
func SetPartEncoding(e Encoding) PartSetting { |
|
return PartSetting(func(p *part) { |
|
p.encoding = e |
|
}) |
|
} |
|
|
|
type file struct { |
|
Name string |
|
Header map[string][]string |
|
CopyFunc func(w io.Writer) error |
|
} |
|
|
|
func (f *file) setHeader(field, value string) { |
|
f.Header[field] = []string{value} |
|
} |
|
|
|
// A FileSetting can be used as an argument in Message.Attach or Message.Embed. |
|
type FileSetting func(*file) |
|
|
|
// SetHeader is a file setting to set the MIME header of the message part that |
|
// contains the file content. |
|
// |
|
// Mandatory headers are automatically added if they are not set when sending |
|
// the email. |
|
func SetHeader(h map[string][]string) FileSetting { |
|
return func(f *file) { |
|
for k, v := range h { |
|
f.Header[k] = v |
|
} |
|
} |
|
} |
|
|
|
// Rename is a file setting to set the name of the attachment if the name is |
|
// different than the filename on disk. |
|
func Rename(name string) FileSetting { |
|
return func(f *file) { |
|
f.Name = name |
|
} |
|
} |
|
|
|
// SetCopyFunc is a file setting to replace the function that runs when the |
|
// message is sent. It should copy the content of the file to the io.Writer. |
|
// |
|
// The default copy function opens the file with the given filename, and copy |
|
// its content to the io.Writer. |
|
func SetCopyFunc(f func(io.Writer) error) FileSetting { |
|
return func(fi *file) { |
|
fi.CopyFunc = f |
|
} |
|
} |
|
|
|
func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file { |
|
f := &file{ |
|
Name: filepath.Base(name), |
|
Header: make(map[string][]string), |
|
CopyFunc: func(w io.Writer) error { |
|
h, err := os.Open(name) |
|
if err != nil { |
|
return err |
|
} |
|
if _, err := io.Copy(w, h); err != nil { |
|
h.Close() |
|
return err |
|
} |
|
return h.Close() |
|
}, |
|
} |
|
|
|
for _, s := range settings { |
|
s(f) |
|
} |
|
|
|
if list == nil { |
|
return []*file{f} |
|
} |
|
|
|
return append(list, f) |
|
} |
|
|
|
// Attach attaches the files to the email. |
|
func (m *Message) Attach(filename string, settings ...FileSetting) { |
|
m.attachments = m.appendFile(m.attachments, filename, settings) |
|
} |
|
|
|
// Embed embeds the images to the email. |
|
func (m *Message) Embed(filename string, settings ...FileSetting) { |
|
m.embedded = m.appendFile(m.embedded, filename, settings) |
|
}
|
|
|