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.

306 lines
6.6 KiB

  1. package gomail
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "io"
  6. "mime"
  7. "mime/multipart"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. )
  12. // WriteTo implements io.WriterTo. It dumps the whole message into w.
  13. func (m *Message) WriteTo(w io.Writer) (int64, error) {
  14. mw := &messageWriter{w: w}
  15. mw.writeMessage(m)
  16. return mw.n, mw.err
  17. }
  18. func (w *messageWriter) writeMessage(m *Message) {
  19. if _, ok := m.header["Mime-Version"]; !ok {
  20. w.writeString("Mime-Version: 1.0\r\n")
  21. }
  22. if _, ok := m.header["Date"]; !ok {
  23. w.writeHeader("Date", m.FormatDate(now()))
  24. }
  25. w.writeHeaders(m.header)
  26. if m.hasMixedPart() {
  27. w.openMultipart("mixed")
  28. }
  29. if m.hasRelatedPart() {
  30. w.openMultipart("related")
  31. }
  32. if m.hasAlternativePart() {
  33. w.openMultipart("alternative")
  34. }
  35. for _, part := range m.parts {
  36. w.writePart(part, m.charset)
  37. }
  38. if m.hasAlternativePart() {
  39. w.closeMultipart()
  40. }
  41. w.addFiles(m.embedded, false)
  42. if m.hasRelatedPart() {
  43. w.closeMultipart()
  44. }
  45. w.addFiles(m.attachments, true)
  46. if m.hasMixedPart() {
  47. w.closeMultipart()
  48. }
  49. }
  50. func (m *Message) hasMixedPart() bool {
  51. return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
  52. }
  53. func (m *Message) hasRelatedPart() bool {
  54. return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
  55. }
  56. func (m *Message) hasAlternativePart() bool {
  57. return len(m.parts) > 1
  58. }
  59. type messageWriter struct {
  60. w io.Writer
  61. n int64
  62. writers [3]*multipart.Writer
  63. partWriter io.Writer
  64. depth uint8
  65. err error
  66. }
  67. func (w *messageWriter) openMultipart(mimeType string) {
  68. mw := multipart.NewWriter(w)
  69. contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
  70. w.writers[w.depth] = mw
  71. if w.depth == 0 {
  72. w.writeHeader("Content-Type", contentType)
  73. w.writeString("\r\n")
  74. } else {
  75. w.createPart(map[string][]string{
  76. "Content-Type": {contentType},
  77. })
  78. }
  79. w.depth++
  80. }
  81. func (w *messageWriter) createPart(h map[string][]string) {
  82. w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
  83. }
  84. func (w *messageWriter) closeMultipart() {
  85. if w.depth > 0 {
  86. w.writers[w.depth-1].Close()
  87. w.depth--
  88. }
  89. }
  90. func (w *messageWriter) writePart(p *part, charset string) {
  91. w.writeHeaders(map[string][]string{
  92. "Content-Type": {p.contentType + "; charset=" + charset},
  93. "Content-Transfer-Encoding": {string(p.encoding)},
  94. })
  95. w.writeBody(p.copier, p.encoding)
  96. }
  97. func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
  98. for _, f := range files {
  99. if _, ok := f.Header["Content-Type"]; !ok {
  100. mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
  101. if mediaType == "" {
  102. mediaType = "application/octet-stream"
  103. }
  104. f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
  105. }
  106. if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
  107. f.setHeader("Content-Transfer-Encoding", string(Base64))
  108. }
  109. if _, ok := f.Header["Content-Disposition"]; !ok {
  110. var disp string
  111. if isAttachment {
  112. disp = "attachment"
  113. } else {
  114. disp = "inline"
  115. }
  116. f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
  117. }
  118. if !isAttachment {
  119. if _, ok := f.Header["Content-ID"]; !ok {
  120. f.setHeader("Content-ID", "<"+f.Name+">")
  121. }
  122. }
  123. w.writeHeaders(f.Header)
  124. w.writeBody(f.CopyFunc, Base64)
  125. }
  126. }
  127. func (w *messageWriter) Write(p []byte) (int, error) {
  128. if w.err != nil {
  129. return 0, errors.New("gomail: cannot write as writer is in error")
  130. }
  131. var n int
  132. n, w.err = w.w.Write(p)
  133. w.n += int64(n)
  134. return n, w.err
  135. }
  136. func (w *messageWriter) writeString(s string) {
  137. n, _ := io.WriteString(w.w, s)
  138. w.n += int64(n)
  139. }
  140. func (w *messageWriter) writeHeader(k string, v ...string) {
  141. w.writeString(k)
  142. if len(v) == 0 {
  143. w.writeString(":\r\n")
  144. return
  145. }
  146. w.writeString(": ")
  147. // Max header line length is 78 characters in RFC 5322 and 76 characters
  148. // in RFC 2047. So for the sake of simplicity we use the 76 characters
  149. // limit.
  150. charsLeft := 76 - len(k) - len(": ")
  151. for i, s := range v {
  152. // If the line is already too long, insert a newline right away.
  153. if charsLeft < 1 {
  154. if i == 0 {
  155. w.writeString("\r\n ")
  156. } else {
  157. w.writeString(",\r\n ")
  158. }
  159. charsLeft = 75
  160. } else if i != 0 {
  161. w.writeString(", ")
  162. charsLeft -= 2
  163. }
  164. // While the header content is too long, fold it by inserting a newline.
  165. for len(s) > charsLeft {
  166. s = w.writeLine(s, charsLeft)
  167. charsLeft = 75
  168. }
  169. w.writeString(s)
  170. if i := lastIndexByte(s, '\n'); i != -1 {
  171. charsLeft = 75 - (len(s) - i - 1)
  172. } else {
  173. charsLeft -= len(s)
  174. }
  175. }
  176. w.writeString("\r\n")
  177. }
  178. func (w *messageWriter) writeLine(s string, charsLeft int) string {
  179. // If there is already a newline before the limit. Write the line.
  180. if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
  181. w.writeString(s[:i+1])
  182. return s[i+1:]
  183. }
  184. for i := charsLeft - 1; i >= 0; i-- {
  185. if s[i] == ' ' {
  186. w.writeString(s[:i])
  187. w.writeString("\r\n ")
  188. return s[i+1:]
  189. }
  190. }
  191. // We could not insert a newline cleanly so look for a space or a newline
  192. // even if it is after the limit.
  193. for i := 75; i < len(s); i++ {
  194. if s[i] == ' ' {
  195. w.writeString(s[:i])
  196. w.writeString("\r\n ")
  197. return s[i+1:]
  198. }
  199. if s[i] == '\n' {
  200. w.writeString(s[:i+1])
  201. return s[i+1:]
  202. }
  203. }
  204. // Too bad, no space or newline in the whole string. Just write everything.
  205. w.writeString(s)
  206. return ""
  207. }
  208. func (w *messageWriter) writeHeaders(h map[string][]string) {
  209. if w.depth == 0 {
  210. for k, v := range h {
  211. if k != "Bcc" {
  212. w.writeHeader(k, v...)
  213. }
  214. }
  215. } else {
  216. w.createPart(h)
  217. }
  218. }
  219. func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
  220. var subWriter io.Writer
  221. if w.depth == 0 {
  222. w.writeString("\r\n")
  223. subWriter = w.w
  224. } else {
  225. subWriter = w.partWriter
  226. }
  227. if enc == Base64 {
  228. wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
  229. w.err = f(wc)
  230. wc.Close()
  231. } else if enc == Unencoded {
  232. w.err = f(subWriter)
  233. } else {
  234. wc := newQPWriter(subWriter)
  235. w.err = f(wc)
  236. wc.Close()
  237. }
  238. }
  239. // As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
  240. // RFC 2045, 6.8. (page 25) for base64.
  241. const maxLineLen = 76
  242. // base64LineWriter limits text encoded in base64 to 76 characters per line
  243. type base64LineWriter struct {
  244. w io.Writer
  245. lineLen int
  246. }
  247. func newBase64LineWriter(w io.Writer) *base64LineWriter {
  248. return &base64LineWriter{w: w}
  249. }
  250. func (w *base64LineWriter) Write(p []byte) (int, error) {
  251. n := 0
  252. for len(p)+w.lineLen > maxLineLen {
  253. w.w.Write(p[:maxLineLen-w.lineLen])
  254. w.w.Write([]byte("\r\n"))
  255. p = p[maxLineLen-w.lineLen:]
  256. n += maxLineLen - w.lineLen
  257. w.lineLen = 0
  258. }
  259. w.w.Write(p)
  260. w.lineLen += len(p)
  261. return n + len(p), nil
  262. }
  263. // Stubbed out for testing.
  264. var now = time.Now