| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package mail
- import (
- "bytes"
- "encoding/base64"
- "io"
- "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 := 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)
- }
- }
|