...

Source file src/github.com/go-mail/mail/writeto.go

Documentation: github.com/go-mail/mail

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

View as plain text