...

Source file src/github.com/go-mail/mail/message.go

Documentation: github.com/go-mail/mail

     1  package mail
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"time"
     9  )
    10  
    11  // Message represents an email.
    12  type Message struct {
    13  	header      header
    14  	parts       []*part
    15  	attachments []*file
    16  	embedded    []*file
    17  	charset     string
    18  	encoding    Encoding
    19  	hEncoder    mimeEncoder
    20  	buf         bytes.Buffer
    21  	boundary    string
    22  }
    23  
    24  type header map[string][]string
    25  
    26  type part struct {
    27  	contentType string
    28  	copier      func(io.Writer) error
    29  	encoding    Encoding
    30  }
    31  
    32  // NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
    33  // by default.
    34  func NewMessage(settings ...MessageSetting) *Message {
    35  	m := &Message{
    36  		header:   make(header),
    37  		charset:  "UTF-8",
    38  		encoding: QuotedPrintable,
    39  	}
    40  
    41  	m.applySettings(settings)
    42  
    43  	if m.encoding == Base64 {
    44  		m.hEncoder = bEncoding
    45  	} else {
    46  		m.hEncoder = qEncoding
    47  	}
    48  
    49  	return m
    50  }
    51  
    52  // Reset resets the message so it can be reused. The message keeps its previous
    53  // settings so it is in the same state that after a call to NewMessage.
    54  func (m *Message) Reset() {
    55  	for k := range m.header {
    56  		delete(m.header, k)
    57  	}
    58  	m.parts = nil
    59  	m.attachments = nil
    60  	m.embedded = nil
    61  }
    62  
    63  func (m *Message) applySettings(settings []MessageSetting) {
    64  	for _, s := range settings {
    65  		s(m)
    66  	}
    67  }
    68  
    69  // A MessageSetting can be used as an argument in NewMessage to configure an
    70  // email.
    71  type MessageSetting func(m *Message)
    72  
    73  // SetCharset is a message setting to set the charset of the email.
    74  func SetCharset(charset string) MessageSetting {
    75  	return func(m *Message) {
    76  		m.charset = charset
    77  	}
    78  }
    79  
    80  // SetEncoding is a message setting to set the encoding of the email.
    81  func SetEncoding(enc Encoding) MessageSetting {
    82  	return func(m *Message) {
    83  		m.encoding = enc
    84  	}
    85  }
    86  
    87  // Encoding represents a MIME encoding scheme like quoted-printable or base64.
    88  type Encoding string
    89  
    90  const (
    91  	// QuotedPrintable represents the quoted-printable encoding as defined in
    92  	// RFC 2045.
    93  	QuotedPrintable Encoding = "quoted-printable"
    94  	// Base64 represents the base64 encoding as defined in RFC 2045.
    95  	Base64 Encoding = "base64"
    96  	// Unencoded can be used to avoid encoding the body of an email. The headers
    97  	// will still be encoded using quoted-printable encoding.
    98  	Unencoded Encoding = "8bit"
    99  )
   100  
   101  // SetBoundary sets a custom multipart boundary.
   102  func (m *Message) SetBoundary(boundary string) {
   103  	m.boundary = boundary
   104  }
   105  
   106  // SetHeader sets a value to the given header field.
   107  func (m *Message) SetHeader(field string, value ...string) {
   108  	m.encodeHeader(value)
   109  	m.header[field] = value
   110  }
   111  
   112  func (m *Message) encodeHeader(values []string) {
   113  	for i := range values {
   114  		values[i] = m.encodeString(values[i])
   115  	}
   116  }
   117  
   118  func (m *Message) encodeString(value string) string {
   119  	return m.hEncoder.Encode(m.charset, value)
   120  }
   121  
   122  // SetHeaders sets the message headers.
   123  func (m *Message) SetHeaders(h map[string][]string) {
   124  	for k, v := range h {
   125  		m.SetHeader(k, v...)
   126  	}
   127  }
   128  
   129  // SetAddressHeader sets an address to the given header field.
   130  func (m *Message) SetAddressHeader(field, address, name string) {
   131  	m.header[field] = []string{m.FormatAddress(address, name)}
   132  }
   133  
   134  // FormatAddress formats an address and a name as a valid RFC 5322 address.
   135  func (m *Message) FormatAddress(address, name string) string {
   136  	if name == "" {
   137  		return address
   138  	}
   139  
   140  	enc := m.encodeString(name)
   141  	if enc == name {
   142  		m.buf.WriteByte('"')
   143  		for i := 0; i < len(name); i++ {
   144  			b := name[i]
   145  			if b == '\\' || b == '"' {
   146  				m.buf.WriteByte('\\')
   147  			}
   148  			m.buf.WriteByte(b)
   149  		}
   150  		m.buf.WriteByte('"')
   151  	} else if hasSpecials(name) {
   152  		m.buf.WriteString(bEncoding.Encode(m.charset, name))
   153  	} else {
   154  		m.buf.WriteString(enc)
   155  	}
   156  	m.buf.WriteString(" <")
   157  	m.buf.WriteString(address)
   158  	m.buf.WriteByte('>')
   159  
   160  	addr := m.buf.String()
   161  	m.buf.Reset()
   162  	return addr
   163  }
   164  
   165  func hasSpecials(text string) bool {
   166  	for i := 0; i < len(text); i++ {
   167  		switch c := text[i]; c {
   168  		case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
   169  			return true
   170  		}
   171  	}
   172  
   173  	return false
   174  }
   175  
   176  // SetDateHeader sets a date to the given header field.
   177  func (m *Message) SetDateHeader(field string, date time.Time) {
   178  	m.header[field] = []string{m.FormatDate(date)}
   179  }
   180  
   181  // FormatDate formats a date as a valid RFC 5322 date.
   182  func (m *Message) FormatDate(date time.Time) string {
   183  	return date.Format(time.RFC1123Z)
   184  }
   185  
   186  // GetHeader gets a header field.
   187  func (m *Message) GetHeader(field string) []string {
   188  	return m.header[field]
   189  }
   190  
   191  // SetBody sets the body of the message. It replaces any content previously set
   192  // by SetBody, SetBodyWriter, AddAlternative or AddAlternativeWriter.
   193  func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
   194  	m.SetBodyWriter(contentType, newCopier(body), settings...)
   195  }
   196  
   197  // SetBodyWriter sets the body of the message. It can be useful with the
   198  // text/template or html/template packages.
   199  func (m *Message) SetBodyWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
   200  	m.parts = []*part{m.newPart(contentType, f, settings)}
   201  }
   202  
   203  // AddAlternative adds an alternative part to the message.
   204  //
   205  // It is commonly used to send HTML emails that default to the plain text
   206  // version for backward compatibility. AddAlternative appends the new part to
   207  // the end of the message. So the plain text part should be added before the
   208  // HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
   209  func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
   210  	m.AddAlternativeWriter(contentType, newCopier(body), settings...)
   211  }
   212  
   213  func newCopier(s string) func(io.Writer) error {
   214  	return func(w io.Writer) error {
   215  		_, err := io.WriteString(w, s)
   216  		return err
   217  	}
   218  }
   219  
   220  // AddAlternativeWriter adds an alternative part to the message. It can be
   221  // useful with the text/template or html/template packages.
   222  func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
   223  	m.parts = append(m.parts, m.newPart(contentType, f, settings))
   224  }
   225  
   226  func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
   227  	p := &part{
   228  		contentType: contentType,
   229  		copier:      f,
   230  		encoding:    m.encoding,
   231  	}
   232  
   233  	for _, s := range settings {
   234  		s(p)
   235  	}
   236  
   237  	return p
   238  }
   239  
   240  // A PartSetting can be used as an argument in Message.SetBody,
   241  // Message.SetBodyWriter, Message.AddAlternative or Message.AddAlternativeWriter
   242  // to configure the part added to a message.
   243  type PartSetting func(*part)
   244  
   245  // SetPartEncoding sets the encoding of the part added to the message. By
   246  // default, parts use the same encoding than the message.
   247  func SetPartEncoding(e Encoding) PartSetting {
   248  	return PartSetting(func(p *part) {
   249  		p.encoding = e
   250  	})
   251  }
   252  
   253  type file struct {
   254  	Name     string
   255  	Header   map[string][]string
   256  	CopyFunc func(w io.Writer) error
   257  }
   258  
   259  func (f *file) setHeader(field, value string) {
   260  	f.Header[field] = []string{value}
   261  }
   262  
   263  // A FileSetting can be used as an argument in Message.Attach or Message.Embed.
   264  type FileSetting func(*file)
   265  
   266  // SetHeader is a file setting to set the MIME header of the message part that
   267  // contains the file content.
   268  //
   269  // Mandatory headers are automatically added if they are not set when sending
   270  // the email.
   271  func SetHeader(h map[string][]string) FileSetting {
   272  	return func(f *file) {
   273  		for k, v := range h {
   274  			f.Header[k] = v
   275  		}
   276  	}
   277  }
   278  
   279  // Rename is a file setting to set the name of the attachment if the name is
   280  // different than the filename on disk.
   281  func Rename(name string) FileSetting {
   282  	return func(f *file) {
   283  		f.Name = name
   284  	}
   285  }
   286  
   287  // SetCopyFunc is a file setting to replace the function that runs when the
   288  // message is sent. It should copy the content of the file to the io.Writer.
   289  //
   290  // The default copy function opens the file with the given filename, and copy
   291  // its content to the io.Writer.
   292  func SetCopyFunc(f func(io.Writer) error) FileSetting {
   293  	return func(fi *file) {
   294  		fi.CopyFunc = f
   295  	}
   296  }
   297  
   298  // AttachReader attaches a file using an io.Reader
   299  func (m *Message) AttachReader(name string, r io.Reader, settings ...FileSetting) {
   300  	m.attachments = m.appendFile(m.attachments, fileFromReader(name, r), settings)
   301  }
   302  
   303  // Attach attaches the files to the email.
   304  func (m *Message) Attach(filename string, settings ...FileSetting) {
   305  	m.attachments = m.appendFile(m.attachments, fileFromFilename(filename), settings)
   306  }
   307  
   308  // EmbedReader embeds the images to the email.
   309  func (m *Message) EmbedReader(name string, r io.Reader, settings ...FileSetting) {
   310  	m.embedded = m.appendFile(m.embedded, fileFromReader(name, r), settings)
   311  }
   312  
   313  // Embed embeds the images to the email.
   314  func (m *Message) Embed(filename string, settings ...FileSetting) {
   315  	m.embedded = m.appendFile(m.embedded, fileFromFilename(filename), settings)
   316  }
   317  
   318  func fileFromFilename(name string) *file {
   319  	return &file{
   320  		Name:   filepath.Base(name),
   321  		Header: make(map[string][]string),
   322  		CopyFunc: func(w io.Writer) error {
   323  			h, err := os.Open(name)
   324  			if err != nil {
   325  				return err
   326  			}
   327  			if _, err := io.Copy(w, h); err != nil {
   328  				h.Close()
   329  				return err
   330  			}
   331  			return h.Close()
   332  		},
   333  	}
   334  }
   335  
   336  func fileFromReader(name string, r io.Reader) *file {
   337  	return &file{
   338  		Name:   filepath.Base(name),
   339  		Header: make(map[string][]string),
   340  		CopyFunc: func(w io.Writer) error {
   341  			if _, err := io.Copy(w, r); err != nil {
   342  				return err
   343  			}
   344  			return nil
   345  		},
   346  	}
   347  }
   348  
   349  func (m *Message) appendFile(list []*file, f *file, settings []FileSetting) []*file {
   350  	for _, s := range settings {
   351  		s(f)
   352  	}
   353  
   354  	if list == nil {
   355  		return []*file{f}
   356  	}
   357  
   358  	return append(list, f)
   359  }
   360  

View as plain text