message.go 9.2 KB

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