Separate the email and message methods
Joe Grasse hide@my.email
Mon, 11 Jan 2016 14:15:54 -0600
2 files changed,
251 insertions(+),
244 deletions(-)
M
mail.go
→
email.go
@@ -3,23 +3,15 @@
import ( "bytes" "crypto/tls" - "encoding/base64" "errors" "fmt" - "github.com/joegrasse/mime/header" - "io" "io/ioutil" "mime" - "mime/multipart" - "mime/quotedprintable" "net" + "net/textproto" "net/mail" "net/smtp" - "net/textproto" "path/filepath" - "regexp" - "strconv" - "strings" "time" )@@ -647,238 +639,4 @@ return err
} return c.Quit() -} - -type message struct { - headers textproto.MIMEHeader - body *bytes.Buffer - writers []*multipart.Writer - parts uint8 - cids map[string]string - charset string - encoding encoding -} - -func newMessage(email *email) *message { - return &message{ - headers: email.headers, - body: new(bytes.Buffer), - cids: make(map[string]string), - charset: email.Charset, - encoding: email.Encoding} -} - -func encodeHeader(text string, charset string, usedChars int) string { - // create buffer - buf := new(bytes.Buffer) - - // encode - encoder := header.NewEncoder(buf, charset, usedChars) - encoder.Encode([]byte(text)) - - return buf.String() - - /* - switch encoding { - case EncodingBase64: - return mime.BEncoding.Encode(charset, text) - default: - return mime.QEncoding.Encode(charset, text) - } - */ -} - -// getHeaders returns the message headers -func (msg *message) getHeaders() (headers string) { - // if the date header isn't set, set it - if date := msg.headers.Get("Date"); date == "" { - msg.headers.Set("Date", time.Now().Format(time.RFC1123Z)) - } - - // encode and combine the headers - for header, values := range msg.headers { - headers += header + ": " + encodeHeader(strings.Join(values, ", "), msg.charset, len(header) + 2) + "\r\n" - } - - headers = headers + "\r\n" - - return -} - -// getCID gets the generated CID for the provided text -func (msg *message) getCID(text string) (cid string) { - // set the date format to use - const dateFormat = "20060102.150405" - - // get the cid if we have one - cid, exists := msg.cids[text] - if !exists { - // generate a new cid - cid = time.Now().Format(dateFormat) + "." + strconv.Itoa(len(msg.cids)+1) + "@mail.0" - // save it - msg.cids[text] = cid - } - - return -} - -// replaceCIDs replaces the CIDs found in a text string -// with generated ones -func (msg *message) replaceCIDs(text string) string { - // regular expression to find cids - re := regexp.MustCompile(`(src|href)="cid:(.*?)"`) - // replace all of the found cids with generated ones - for _, matches := range re.FindAllStringSubmatch(text, -1) { - cid := msg.getCID(matches[2]) - text = strings.Replace(text, "cid:"+matches[2], "cid:"+cid, -1) - } - - return text -} - -// openMultipart creates a new part of a multipart message -func (msg *message) openMultipart(multipartType string) { - // create a new multipart writer - msg.writers = append(msg.writers, multipart.NewWriter(msg.body)) - // create the boundary - contentType := "multipart/" + multipartType + ";\n \tboundary=" + msg.writers[msg.parts].Boundary() - - // if no existing parts, add header to main header group - if msg.parts == 0 { - msg.headers.Set("Content-Type", contentType) - } else { // add header to multipart section - header := make(textproto.MIMEHeader) - header.Set("Content-Type", contentType) - msg.writers[msg.parts-1].CreatePart(header) - } - - msg.parts++ -} - -// closeMultipart closes a part of a multipart message -func (msg *message) closeMultipart() { - if msg.parts > 0 { - msg.writers[msg.parts-1].Close() - msg.parts-- - } -} - -// base64Encode base64 encodes the provided text with line wrapping -func base64Encode(text []byte) []byte { - // create buffer - buf := new(bytes.Buffer) - - // create base64 encoder that linewraps - encoder := base64.NewEncoder(base64.StdEncoding, &base64LineWrap{writer: buf}) - - // write the encoded text to buf - encoder.Write(text) - encoder.Close() - - return buf.Bytes() -} - -// qpEncode uses the quoted-printable encoding to encode the provided text -func qpEncode(text []byte) []byte { - // create buffer - buf := new(bytes.Buffer) - - encoder := quotedprintable.NewWriter(buf) - - encoder.Write(text) - encoder.Close() - - return buf.Bytes() -} - -const maxLineChars = 76 - -type base64LineWrap struct { - writer io.Writer - numLineChars int -} - -func (e *base64LineWrap) Write(p []byte) (n int, err error) { - n = 0 - // while we have more chars than are allowed - for len(p)+e.numLineChars > maxLineChars { - numCharsToWrite := maxLineChars - e.numLineChars - // write the chars we can - e.writer.Write(p[:numCharsToWrite]) - // write a line break - e.writer.Write([]byte("\r\n")) - // reset the line count - e.numLineChars = 0 - // remove the chars that have been written - p = p[numCharsToWrite:] - // set the num of chars written - n += numCharsToWrite - } - - // write what is left - e.writer.Write(p) - e.numLineChars += len(p) - n += len(p) - - return -} - -func (msg *message) write(header textproto.MIMEHeader, body []byte, encoding encoding) { - msg.writeHeader(header) - msg.writeBody(body, encoding) -} - -func (msg *message) writeHeader(headers textproto.MIMEHeader) { - // if there are no parts add header to main headers - if msg.parts == 0 { - for header, value := range headers { - msg.headers[header] = value - } - } else { // add header to multipart section - msg.writers[msg.parts-1].CreatePart(headers) - } -} - -func (msg *message) writeBody(body []byte, encoding encoding) { - // encode and write the body - switch encoding { - case EncodingQuotedPrintable: - msg.body.Write(qpEncode(body)) - case EncodingBase64: - msg.body.Write(base64Encode(body)) - default: - msg.body.Write(body) - } -} - -func (msg *message) addBody(contentType string, body []byte) { - body = []byte(msg.replaceCIDs(string(body))) - - header := make(textproto.MIMEHeader) - header.Set("Content-Type", contentType+"; charset="+msg.charset) - header.Set("Content-Transfer-Encoding", msg.encoding.String()) - msg.write(header, body, msg.encoding) -} - -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} - -func (msg *message) addFiles(files []*file, inline bool) { - encoding := EncodingBase64 - for _, file := range files { - header := make(textproto.MIMEHeader) - header.Set("Content-Type", file.mimeType+";\n \tname=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 6)+`"`) - header.Set("Content-Transfer-Encoding", encoding.String()) - if inline { - header.Set("Content-Disposition", "inline;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`) - header.Set("Content-ID", "<"+msg.getCID(file.filename)+">") - } else { - header.Set("Content-Disposition", "attachment;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`) - } - - msg.write(header, file.data, encoding) - } -} +}
A
message.go
@@ -0,0 +1,249 @@
+package mail + +import ( + "bytes" + "encoding/base64" + "io" + "github.com/joegrasse/mime/header" + "mime/multipart" + "mime/quotedprintable" + "net/textproto" + "regexp" + "strconv" + "strings" + "time" +) + +type message struct { + headers textproto.MIMEHeader + body *bytes.Buffer + writers []*multipart.Writer + parts uint8 + cids map[string]string + charset string + encoding encoding +} + +func newMessage(email *email) *message { + return &message{ + headers: email.headers, + body: new(bytes.Buffer), + cids: make(map[string]string), + charset: email.Charset, + encoding: email.Encoding} +} + +func encodeHeader(text string, charset string, usedChars int) string { + // create buffer + buf := new(bytes.Buffer) + + // encode + encoder := header.NewEncoder(buf, charset, usedChars) + encoder.Encode([]byte(text)) + + return buf.String() + + /* + switch encoding { + case EncodingBase64: + return mime.BEncoding.Encode(charset, text) + default: + return mime.QEncoding.Encode(charset, text) + } + */ +} + +// getHeaders returns the message headers +func (msg *message) getHeaders() (headers string) { + // if the date header isn't set, set it + if date := msg.headers.Get("Date"); date == "" { + msg.headers.Set("Date", time.Now().Format(time.RFC1123Z)) + } + + // encode and combine the headers + for header, values := range msg.headers { + headers += header + ": " + encodeHeader(strings.Join(values, ", "), msg.charset, len(header) + 2) + "\r\n" + } + + headers = headers + "\r\n" + + return +} + +// getCID gets the generated CID for the provided text +func (msg *message) getCID(text string) (cid string) { + // set the date format to use + const dateFormat = "20060102.150405" + + // get the cid if we have one + cid, exists := msg.cids[text] + if !exists { + // generate a new cid + cid = time.Now().Format(dateFormat) + "." + strconv.Itoa(len(msg.cids)+1) + "@mail.0" + // save it + msg.cids[text] = cid + } + + return +} + +// replaceCIDs replaces the CIDs found in a text string +// with generated ones +func (msg *message) replaceCIDs(text string) string { + // regular expression to find cids + re := regexp.MustCompile(`(src|href)="cid:(.*?)"`) + // replace all of the found cids with generated ones + for _, matches := range re.FindAllStringSubmatch(text, -1) { + cid := msg.getCID(matches[2]) + text = strings.Replace(text, "cid:"+matches[2], "cid:"+cid, -1) + } + + return text +} + +// openMultipart creates a new part of a multipart message +func (msg *message) openMultipart(multipartType string) { + // create a new multipart writer + msg.writers = append(msg.writers, multipart.NewWriter(msg.body)) + // create the boundary + contentType := "multipart/" + multipartType + ";\n \tboundary=" + msg.writers[msg.parts].Boundary() + + // if no existing parts, add header to main header group + if msg.parts == 0 { + msg.headers.Set("Content-Type", contentType) + } else { // add header to multipart section + header := make(textproto.MIMEHeader) + header.Set("Content-Type", contentType) + msg.writers[msg.parts-1].CreatePart(header) + } + + msg.parts++ +} + +// closeMultipart closes a part of a multipart message +func (msg *message) closeMultipart() { + if msg.parts > 0 { + msg.writers[msg.parts-1].Close() + msg.parts-- + } +} + +// base64Encode base64 encodes the provided text with line wrapping +func base64Encode(text []byte) []byte { + // create buffer + buf := new(bytes.Buffer) + + // create base64 encoder that linewraps + encoder := base64.NewEncoder(base64.StdEncoding, &base64LineWrap{writer: buf}) + + // write the encoded text to buf + encoder.Write(text) + encoder.Close() + + return buf.Bytes() +} + +// qpEncode uses the quoted-printable encoding to encode the provided text +func qpEncode(text []byte) []byte { + // create buffer + buf := new(bytes.Buffer) + + encoder := quotedprintable.NewWriter(buf) + + encoder.Write(text) + encoder.Close() + + return buf.Bytes() +} + +const maxLineChars = 76 + +type base64LineWrap struct { + writer io.Writer + numLineChars int +} + +func (e *base64LineWrap) Write(p []byte) (n int, err error) { + n = 0 + // while we have more chars than are allowed + for len(p)+e.numLineChars > maxLineChars { + numCharsToWrite := maxLineChars - e.numLineChars + // write the chars we can + e.writer.Write(p[:numCharsToWrite]) + // write a line break + e.writer.Write([]byte("\r\n")) + // reset the line count + e.numLineChars = 0 + // remove the chars that have been written + p = p[numCharsToWrite:] + // set the num of chars written + n += numCharsToWrite + } + + // write what is left + e.writer.Write(p) + e.numLineChars += len(p) + n += len(p) + + return +} + +func (msg *message) write(header textproto.MIMEHeader, body []byte, encoding encoding) { + msg.writeHeader(header) + msg.writeBody(body, encoding) +} + +func (msg *message) writeHeader(headers textproto.MIMEHeader) { + // if there are no parts add header to main headers + if msg.parts == 0 { + for header, value := range headers { + msg.headers[header] = value + } + } else { // add header to multipart section + msg.writers[msg.parts-1].CreatePart(headers) + } +} + +func (msg *message) writeBody(body []byte, encoding encoding) { + // encode and write the body + switch encoding { + case EncodingQuotedPrintable: + msg.body.Write(qpEncode(body)) + case EncodingBase64: + msg.body.Write(base64Encode(body)) + default: + msg.body.Write(body) + } +} + +func (msg *message) addBody(contentType string, body []byte) { + body = []byte(msg.replaceCIDs(string(body))) + + header := make(textproto.MIMEHeader) + header.Set("Content-Type", contentType+"; charset="+msg.charset) + header.Set("Content-Transfer-Encoding", msg.encoding.String()) + msg.write(header, body, msg.encoding) +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + +func (msg *message) addFiles(files []*file, inline bool) { + encoding := EncodingBase64 + for _, file := range files { + header := make(textproto.MIMEHeader) + header.Set("Content-Type", file.mimeType+";\n \tname=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 6)+`"`) + header.Set("Content-Transfer-Encoding", encoding.String()) + if inline { + header.Set("Content-Disposition", "inline;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`) + header.Set("Content-ID", "<"+msg.getCID(file.filename)+">") + } else { + header.Set("Content-Disposition", "attachment;\n \tfilename=\""+encodeHeader(escapeQuotes(file.filename), msg.charset, 10)+`"`) + } + + msg.write(header, file.data, encoding) + } +}