writeto.go 6.7 KB

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