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

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