all repos — mail2 @ eb280f3a8a630d11b3b82a1b7507e12f911ba082

fork of github.com/joegrasse/mail with some changes

message.go (view raw)

  1package mail
  2
  3import (
  4	"bytes"
  5	"encoding/base64"
  6	"io"
  7	"mime/multipart"
  8	"mime/quotedprintable"
  9	"net/textproto"
 10	"regexp"
 11	"strconv"
 12	"strings"
 13	"time"
 14
 15	"github.com/joegrasse/mime/header"
 16)
 17
 18type message struct {
 19	headers  textproto.MIMEHeader
 20	body     *bytes.Buffer
 21	writers  []*multipart.Writer
 22	parts    uint8
 23	cids     map[string]string
 24	charset  string
 25	encoding encoding
 26}
 27
 28func newMessage(email *Email) *message {
 29	return &message{
 30		headers:  email.headers,
 31		body:     new(bytes.Buffer),
 32		cids:     make(map[string]string),
 33		charset:  email.Charset,
 34		encoding: email.Encoding}
 35}
 36
 37func encodeHeader(text string, charset string, usedChars int) string {
 38	// create buffer
 39	buf := new(bytes.Buffer)
 40
 41	// encode
 42	encoder := header.NewEncoder(buf, charset, usedChars)
 43	encoder.Encode([]byte(text))
 44
 45	return buf.String()
 46
 47	/*
 48			switch encoding {
 49			case EncodingBase64:
 50				return mime.BEncoding.Encode(charset, text)
 51			default:
 52				return mime.QEncoding.Encode(charset, text)
 53		}
 54	*/
 55}
 56
 57// getHeaders returns the message headers
 58func (msg *message) getHeaders() (headers string) {
 59	// if the date header isn't set, set it
 60	if date := msg.headers.Get("Date"); date == "" {
 61		msg.headers.Set("Date", time.Now().Format(time.RFC1123Z))
 62	}
 63
 64	// encode and combine the headers
 65	for header, values := range msg.headers {
 66		headers += header + ": " + encodeHeader(strings.Join(values, ", "), msg.charset, len(header)+2) + "\r\n"
 67	}
 68
 69	headers = headers + "\r\n"
 70
 71	return
 72}
 73
 74// getCID gets the generated CID for the provided text
 75func (msg *message) getCID(text string) (cid string) {
 76	// set the date format to use
 77	const dateFormat = "20060102.150405"
 78
 79	// get the cid if we have one
 80	cid, exists := msg.cids[text]
 81	if !exists {
 82		// generate a new cid
 83		cid = time.Now().Format(dateFormat) + "." + strconv.Itoa(len(msg.cids)+1) + "@mail.0"
 84		// save it
 85		msg.cids[text] = cid
 86	}
 87
 88	return
 89}
 90
 91// replaceCIDs replaces the CIDs found in a text string
 92// with generated ones
 93func (msg *message) replaceCIDs(text string) string {
 94	// regular expression to find cids
 95	re := regexp.MustCompile(`(src|href)="cid:(.*?)"`)
 96	// replace all of the found cids with generated ones
 97	for _, matches := range re.FindAllStringSubmatch(text, -1) {
 98		cid := msg.getCID(matches[2])
 99		text = strings.Replace(text, "cid:"+matches[2], "cid:"+cid, -1)
100	}
101
102	return text
103}
104
105// openMultipart creates a new part of a multipart message
106func (msg *message) openMultipart(multipartType string) {
107	// create a new multipart writer
108	msg.writers = append(msg.writers, multipart.NewWriter(msg.body))
109	// create the boundary
110	contentType := "multipart/" + multipartType + ";\n \tboundary=" + msg.writers[msg.parts].Boundary()
111
112	// if no existing parts, add header to main header group
113	if msg.parts == 0 {
114		msg.headers.Set("Content-Type", contentType)
115	} else { // add header to multipart section
116		header := make(textproto.MIMEHeader)
117		header.Set("Content-Type", contentType)
118		msg.writers[msg.parts-1].CreatePart(header)
119	}
120
121	msg.parts++
122}
123
124// closeMultipart closes a part of a multipart message
125func (msg *message) closeMultipart() {
126	if msg.parts > 0 {
127		msg.writers[msg.parts-1].Close()
128		msg.parts--
129	}
130}
131
132// base64Encode base64 encodes the provided text with line wrapping
133func base64Encode(text []byte) []byte {
134	// create buffer
135	buf := new(bytes.Buffer)
136
137	// create base64 encoder that linewraps
138	encoder := base64.NewEncoder(base64.StdEncoding, &base64LineWrap{writer: buf})
139
140	// write the encoded text to buf
141	encoder.Write(text)
142	encoder.Close()
143
144	return buf.Bytes()
145}
146
147// qpEncode uses the quoted-printable encoding to encode the provided text
148func qpEncode(text []byte) []byte {
149	// create buffer
150	buf := new(bytes.Buffer)
151
152	encoder := quotedprintable.NewWriter(buf)
153
154	encoder.Write(text)
155	encoder.Close()
156
157	return buf.Bytes()
158}
159
160const maxLineChars = 76
161
162type base64LineWrap struct {
163	writer       io.Writer
164	numLineChars int
165}
166
167func (e *base64LineWrap) Write(p []byte) (n int, err error) {
168	n = 0
169	// while we have more chars than are allowed
170	for len(p)+e.numLineChars > maxLineChars {
171		numCharsToWrite := maxLineChars - e.numLineChars
172		// write the chars we can
173		e.writer.Write(p[:numCharsToWrite])
174		// write a line break
175		e.writer.Write([]byte("\r\n"))
176		// reset the line count
177		e.numLineChars = 0
178		// remove the chars that have been written
179		p = p[numCharsToWrite:]
180		// set the num of chars written
181		n += numCharsToWrite
182	}
183
184	// write what is left
185	e.writer.Write(p)
186	e.numLineChars += len(p)
187	n += len(p)
188
189	return
190}
191
192func (msg *message) write(header textproto.MIMEHeader, body []byte, encoding encoding) {
193	msg.writeHeader(header)
194	msg.writeBody(body, encoding)
195}
196
197func (msg *message) writeHeader(headers textproto.MIMEHeader) {
198	// if there are no parts add header to main headers
199	if msg.parts == 0 {
200		for header, value := range headers {
201			msg.headers[header] = value
202		}
203	} else { // add header to multipart section
204		msg.writers[msg.parts-1].CreatePart(headers)
205	}
206}
207
208func (msg *message) writeBody(body []byte, encoding encoding) {
209	// encode and write the body
210	switch encoding {
211	case EncodingQuotedPrintable:
212		msg.body.Write(qpEncode(body))
213	case EncodingBase64:
214		msg.body.Write(base64Encode(body))
215	default:
216		msg.body.Write(body)
217	}
218}
219
220func (msg *message) addBody(contentType string, body []byte) {
221	body = []byte(msg.replaceCIDs(string(body)))
222
223	header := make(textproto.MIMEHeader)
224	header.Set("Content-Type", contentType+"; charset="+msg.charset)
225	header.Set("Content-Transfer-Encoding", msg.encoding.String())
226	msg.write(header, body, msg.encoding)
227}
228
229var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
230
231func escapeQuotes(s string) string {
232	return quoteEscaper.Replace(s)
233}
234
235func (msg *message) addFiles(files []*file, inline bool) {
236	encoding := EncodingBase64
237	for _, file := range files {
238		header := make(textproto.MIMEHeader)
239		header.Set("Content-Type", file.mimeType+";\n \tname=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 6)+`"`)
240		header.Set("Content-Transfer-Encoding", encoding.String())
241		if inline {
242			header.Set("Content-Disposition", "inline;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`)
243			header.Set("Content-ID", "<"+msg.getCID(file.filename)+">")
244		} else {
245			header.Set("Content-Disposition", "attachment;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`)
246		}
247
248		msg.write(header, file.data, encoding)
249	}
250}