|
|
- 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)
- }
|