| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 | package gomailimport (	"encoding/base64"	"errors"	"io"	"mime"	"mime/multipart"	"path/filepath"	"strings"	"time")// WriteTo implements io.WriterTo. It dumps the whole message into w.func (m *Message) WriteTo(w io.Writer) (int64, error) {	mw := &messageWriter{w: w}	mw.writeMessage(m)	return mw.n, mw.err}func (w *messageWriter) writeMessage(m *Message) {	if _, ok := m.header["Mime-Version"]; !ok {		w.writeString("Mime-Version: 1.0\r\n")	}	if _, ok := m.header["Date"]; !ok {		w.writeHeader("Date", m.FormatDate(now()))	}	w.writeHeaders(m.header)	if m.hasMixedPart() {		w.openMultipart("mixed")	}	if m.hasRelatedPart() {		w.openMultipart("related")	}	if m.hasAlternativePart() {		w.openMultipart("alternative")	}	for _, part := range m.parts {		w.writePart(part, m.charset)	}	if m.hasAlternativePart() {		w.closeMultipart()	}	w.addFiles(m.embedded, false)	if m.hasRelatedPart() {		w.closeMultipart()	}	w.addFiles(m.attachments, true)	if m.hasMixedPart() {		w.closeMultipart()	}}func (m *Message) hasMixedPart() bool {	return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1}func (m *Message) hasRelatedPart() bool {	return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1}func (m *Message) hasAlternativePart() bool {	return len(m.parts) > 1}type messageWriter struct {	w          io.Writer	n          int64	writers    [3]*multipart.Writer	partWriter io.Writer	depth      uint8	err        error}func (w *messageWriter) openMultipart(mimeType string) {	mw := multipart.NewWriter(w)	contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()	w.writers[w.depth] = mw	if w.depth == 0 {		w.writeHeader("Content-Type", contentType)		w.writeString("\r\n")	} else {		w.createPart(map[string][]string{			"Content-Type": {contentType},		})	}	w.depth++}func (w *messageWriter) createPart(h map[string][]string) {	w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)}func (w *messageWriter) closeMultipart() {	if w.depth > 0 {		w.writers[w.depth-1].Close()		w.depth--	}}func (w *messageWriter) writePart(p *part, charset string) {	w.writeHeaders(map[string][]string{		"Content-Type":              {p.contentType + "; charset=" + charset},		"Content-Transfer-Encoding": {string(p.encoding)},	})	w.writeBody(p.copier, p.encoding)}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+">")			}		}		w.writeHeaders(f.Header)		w.writeBody(f.CopyFunc, Base64)	}}func (w *messageWriter) Write(p []byte) (int, error) {	if w.err != nil {		return 0, errors.New("gomail: cannot write as writer is in error")	}	var n int	n, w.err = w.w.Write(p)	w.n += int64(n)	return n, w.err}func (w *messageWriter) writeString(s string) {	n, _ := io.WriteString(w.w, s)	w.n += int64(n)}func (w *messageWriter) writeHeader(k string, v ...string) {	w.writeString(k)	if len(v) == 0 {		w.writeString(":\r\n")		return	}	w.writeString(": ")	// Max header line length is 78 characters in RFC 5322 and 76 characters	// in RFC 2047. So for the sake of simplicity we use the 76 characters	// limit.	charsLeft := 76 - len(k) - len(": ")	for i, s := range v {		// If the line is already too long, insert a newline right away.		if charsLeft < 1 {			if i == 0 {				w.writeString("\r\n ")			} else {				w.writeString(",\r\n ")			}			charsLeft = 75		} else if i != 0 {			w.writeString(", ")			charsLeft -= 2		}		// While the header content is too long, fold it by inserting a newline.		for len(s) > charsLeft {			s = w.writeLine(s, charsLeft)			charsLeft = 75		}		w.writeString(s)		if i := lastIndexByte(s, '\n'); i != -1 {			charsLeft = 75 - (len(s) - i - 1)		} else {			charsLeft -= len(s)		}	}	w.writeString("\r\n")}func (w *messageWriter) writeLine(s string, charsLeft int) string {	// If there is already a newline before the limit. Write the line.	if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {		w.writeString(s[:i+1])		return s[i+1:]	}	for i := charsLeft - 1; i >= 0; i-- {		if s[i] == ' ' {			w.writeString(s[:i])			w.writeString("\r\n ")			return s[i+1:]		}	}	// We could not insert a newline cleanly so look for a space or a newline	// even if it is after the limit.	for i := 75; i < len(s); i++ {		if s[i] == ' ' {			w.writeString(s[:i])			w.writeString("\r\n ")			return s[i+1:]		}		if s[i] == '\n' {			w.writeString(s[:i+1])			return s[i+1:]		}	}	// Too bad, no space or newline in the whole string. Just write everything.	w.writeString(s)	return ""}func (w *messageWriter) writeHeaders(h map[string][]string) {	if w.depth == 0 {		for k, v := range h {			if k != "Bcc" {				w.writeHeader(k, v...)			}		}	} else {		w.createPart(h)	}}func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {	var subWriter io.Writer	if w.depth == 0 {		w.writeString("\r\n")		subWriter = w.w	} else {		subWriter = w.partWriter	}	if enc == Base64 {		wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))		w.err = f(wc)		wc.Close()	} else if enc == Unencoded {		w.err = f(subWriter)	} else {		wc := newQPWriter(subWriter)		w.err = f(wc)		wc.Close()	}}// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and// RFC 2045, 6.8. (page 25) for base64.const maxLineLen = 76// base64LineWriter limits text encoded in base64 to 76 characters per linetype 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
 |