Skip to content

Commit

Permalink
Separate the email and message methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Grasse committed Jan 11, 2016
1 parent 32e75f9 commit 7fdf6ea
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 244 deletions.
246 changes: 2 additions & 244 deletions mail.go → email.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,15 @@ package mail
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"
)

Expand Down Expand Up @@ -647,238 +639,4 @@ func send(host string, port string, from string, to []string, msg string, auth s
}

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)
}
}
}
Loading

0 comments on commit 7fdf6ea

Please sign in to comment.