gomail/writeto.go

247 lines
5.2 KiB
Go
Raw Normal View History

2014-10-15 23:47:07 +08:00
package gomail
import (
"encoding/base64"
"errors"
2014-10-15 23:47:07 +08:00
"io"
"mime"
2014-10-15 23:47:07 +08:00
"mime/multipart"
"path/filepath"
2014-10-15 23:47:07 +08:00
"time"
)
2015-07-03 05:15:42 +08:00
// WriteTo implements io.WriterTo. It dumps the whole message into w.
2015-07-15 05:34:21 +08:00
func (m *Message) WriteTo(w io.Writer) (int64, error) {
mw := &messageWriter{w: w}
2015-07-15 05:34:21 +08:00
mw.writeMessage(m)
return mw.n, mw.err
}
2015-07-15 05:34:21 +08:00
func (w *messageWriter) writeMessage(m *Message) {
if _, ok := m.header["Mime-Version"]; !ok {
w.writeString("Mime-Version: 1.0\r\n")
}
2015-07-15 05:34:21 +08:00
if _, ok := m.header["Date"]; !ok {
w.writeHeader("Date", m.FormatDate(now()))
}
2015-07-15 05:34:21 +08:00
w.writeHeaders(m.header)
2014-10-15 23:47:07 +08:00
2015-07-15 05:34:21 +08:00
if m.hasMixedPart() {
2014-10-15 23:47:07 +08:00
w.openMultipart("mixed")
}
2015-07-15 05:34:21 +08:00
if m.hasRelatedPart() {
2014-10-15 23:47:07 +08:00
w.openMultipart("related")
}
2015-07-15 05:34:21 +08:00
if m.hasAlternativePart() {
2014-10-15 23:47:07 +08:00
w.openMultipart("alternative")
}
2015-07-15 05:34:21 +08:00
for _, part := range m.parts {
contentType := part.contentType + "; charset=" + m.charset
w.writeHeaders(map[string][]string{
"Content-Type": []string{contentType},
2015-07-15 05:34:21 +08:00
"Content-Transfer-Encoding": []string{string(m.encoding)},
})
2015-07-15 05:34:21 +08:00
w.writeBody(part.copier, m.encoding)
2014-10-15 23:47:07 +08:00
}
2015-07-15 05:34:21 +08:00
if m.hasAlternativePart() {
2014-10-15 23:47:07 +08:00
w.closeMultipart()
}
2015-07-15 05:34:21 +08:00
w.addFiles(m.embedded, false)
if m.hasRelatedPart() {
2014-10-15 23:47:07 +08:00
w.closeMultipart()
}
2015-07-15 05:34:21 +08:00
w.addFiles(m.attachments, true)
if m.hasMixedPart() {
2014-10-15 23:47:07 +08:00
w.closeMultipart()
}
}
2015-07-15 05:34:21 +08:00
func (m *Message) hasMixedPart() bool {
return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
2014-10-15 23:47:07 +08:00
}
2015-07-15 05:34:21 +08:00
func (m *Message) hasRelatedPart() bool {
return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
2014-10-15 23:47:07 +08:00
}
2015-07-15 05:34:21 +08:00
func (m *Message) hasAlternativePart() bool {
return len(m.parts) > 1
2014-10-15 23:47:07 +08:00
}
type messageWriter struct {
w io.Writer
n int64
2014-10-15 23:47:07 +08:00
writers [3]*multipart.Writer
partWriter io.Writer
depth uint8
err error
2014-10-15 23:47:07 +08:00
}
func (w *messageWriter) openMultipart(mimeType string) {
mw := multipart.NewWriter(w)
contentType := "multipart/" + mimeType + "; boundary=" + mw.Boundary()
w.writers[w.depth] = mw
2014-10-15 23:47:07 +08:00
if w.depth == 0 {
w.writeHeader("Content-Type", contentType)
w.writeString("\r\n")
2014-10-15 23:47:07 +08:00
} else {
w.createPart(map[string][]string{
"Content-Type": []string{contentType},
})
2014-10-15 23:47:07 +08:00
}
w.depth++
}
func (w *messageWriter) createPart(h map[string][]string) {
w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
2014-10-15 23:47:07 +08:00
}
func (w *messageWriter) closeMultipart() {
if w.depth > 0 {
w.writers[w.depth-1].Close()
w.depth--
}
}
func (w *messageWriter) addFiles(files []*File, isAttachment bool) {
for _, f := range files {
if _, ok := f.Header["Content-Type"]; !ok {
mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
if mediaType == "" {
mediaType = "application/octet-stream"
}
f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
}
if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
f.setHeader("Content-Transfer-Encoding", string(Base64))
}
if _, ok := f.Header["Content-Disposition"]; !ok {
var disp string
if isAttachment {
disp = "attachment"
} else {
disp = "inline"
}
f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
}
if !isAttachment {
if _, ok := f.Header["Content-ID"]; !ok {
f.setHeader("Content-ID", "<"+f.Name+">")
}
2014-10-15 23:47:07 +08:00
}
w.writeHeaders(f.Header)
w.writeBody(f.Copier, Base64)
}
}
2014-10-15 23:47:07 +08:00
func (w *messageWriter) Write(p []byte) (int, error) {
if w.err != nil {
return 0, errors.New("gomail: cannot write as writer is in error")
2014-10-15 23:47:07 +08:00
}
var n int
n, w.err = w.w.Write(p)
w.n += int64(n)
return n, w.err
2014-10-15 23:47:07 +08:00
}
func (w *messageWriter) writeString(s string) {
n, _ := io.WriteString(w.w, s)
w.n += int64(n)
}
func (w *messageWriter) writeStrings(a []string, sep string) {
if len(a) > 0 {
w.writeString(a[0])
if len(a) == 1 {
return
}
}
for _, s := range a[1:] {
w.writeString(sep)
w.writeString(s)
}
2014-10-15 23:47:07 +08:00
}
func (w *messageWriter) writeHeader(k string, v ...string) {
w.writeString(k)
w.writeString(": ")
w.writeStrings(v, ", ")
w.writeString("\r\n")
}
func (w *messageWriter) writeHeaders(h map[string][]string) {
2014-10-15 23:47:07 +08:00
if w.depth == 0 {
for k, v := range h {
if k != "Bcc" {
w.writeHeader(k, v...)
}
2014-10-15 23:47:07 +08:00
}
} else {
w.createPart(h)
}
}
func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
2014-10-15 23:47:07 +08:00
var subWriter io.Writer
if w.depth == 0 {
w.writeString("\r\n")
subWriter = w.w
2014-10-15 23:47:07 +08:00
} else {
subWriter = w.partWriter
}
if enc == Base64 {
wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
w.err = f(wc)
wc.Close()
2014-10-28 15:35:50 +08:00
} else if enc == Unencoded {
w.err = f(subWriter)
2014-10-15 23:47:07 +08:00
} else {
wc := newQPWriter(subWriter)
w.err = f(wc)
wc.Close()
2014-10-15 23:47:07 +08:00
}
}
// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
// RFC 2045, 6.8. (page 25) for base64.
const maxLineLen = 76
2014-10-15 23:47:07 +08:00
// base64LineWriter limits text encoded in base64 to 76 characters per line
2014-10-15 23:47:07 +08:00
type base64LineWriter struct {
w io.Writer
lineLen int
}
func newBase64LineWriter(w io.Writer) *base64LineWriter {
return &base64LineWriter{w: w}
}
func (w *base64LineWriter) Write(p []byte) (int, error) {
n := 0
for len(p)+w.lineLen > maxLineLen {
w.w.Write(p[:maxLineLen-w.lineLen])
w.w.Write([]byte("\r\n"))
p = p[maxLineLen-w.lineLen:]
n += maxLineLen - w.lineLen
w.lineLen = 0
}
w.w.Write(p)
w.lineLen += len(p)
return n + len(p), nil
}
// Stubbed out for testing.
var now = time.Now