62b5598596
* init * fix typos and remove console log * fix linter and typos * move mail to internal * naming enhancements and remove gmailauth * add message * fix linter * fix comment tabulation * fix comment * change flow * escape subject * add q encoding to msg fields * fix typo * fix comment tabulation * lil comment fix
116 lines
3.0 KiB
Go
116 lines
3.0 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information
|
|
|
|
package mail
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"mime/multipart"
|
|
"mime/quotedprintable"
|
|
"net/mail"
|
|
"net/textproto"
|
|
"time"
|
|
)
|
|
|
|
// Message is RFC compliant email message
|
|
type Message struct {
|
|
From mail.Address
|
|
To []mail.Address
|
|
Subject string
|
|
ID string
|
|
Date time.Time
|
|
ReceiptTo []string
|
|
|
|
PlainText string
|
|
Parts []Part
|
|
}
|
|
|
|
// Part represent one part of multipart message
|
|
type Part struct {
|
|
Type string
|
|
Encoding string
|
|
Disposition string
|
|
Content string
|
|
}
|
|
|
|
// Bytes builds message and returns result as bytes
|
|
func (msg *Message) Bytes() []byte {
|
|
// always returns nil error on read and write, so most of the errors can be ignored
|
|
var body bytes.Buffer
|
|
|
|
// write headers
|
|
fmt.Fprintf(&body, "MIME-Version: 1.0\r\n")
|
|
fmt.Fprintf(&body, "Subject: %v\r\n", mime.QEncoding.Encode("utf-8", msg.Subject))
|
|
fmt.Fprintf(&body, "From: %s\r\n", &msg.From)
|
|
for _, to := range msg.To {
|
|
fmt.Fprintf(&body, "To: %s\r\n", &to)
|
|
}
|
|
for _, recipient := range msg.ReceiptTo {
|
|
fmt.Fprintf(&body, "Disposition-Notification-To: <%v>\r\n", mime.QEncoding.Encode("utf-8", recipient))
|
|
}
|
|
// date and id are optional as they can be set by server itself
|
|
if !msg.Date.IsZero() {
|
|
fmt.Fprintf(&body, "Date: %v\r\n", msg.Date)
|
|
}
|
|
if msg.ID != "" {
|
|
fmt.Fprintf(&body, "Message-ID: <%v>\r\n", mime.QEncoding.Encode("utf-8", msg.ID))
|
|
}
|
|
|
|
switch {
|
|
// multipart upload
|
|
case len(msg.Parts) > 0:
|
|
wr := multipart.NewWriter(&body)
|
|
|
|
fmt.Fprintf(&body, "Content-Type: multipart/alternative;")
|
|
fmt.Fprintf(&body, "\tboundary=\"%v\"\r\n", wr.Boundary())
|
|
fmt.Fprintf(&body, "\r\n")
|
|
|
|
var sub io.Writer
|
|
|
|
if len(msg.PlainText) > 0 {
|
|
sub, _ = wr.CreatePart(textproto.MIMEHeader{
|
|
"Content-Type": []string{"text/plain; charset=UTF-8; format=flowed"},
|
|
"Content-Transfer-Encoding": []string{"quoted-printable"},
|
|
})
|
|
|
|
enc := quotedprintable.NewWriter(sub)
|
|
_, _ = enc.Write([]byte(msg.PlainText))
|
|
_ = enc.Close()
|
|
}
|
|
|
|
for _, part := range msg.Parts {
|
|
header := textproto.MIMEHeader{"Content-Type": []string{mime.QEncoding.Encode("utf-8", part.Type)}}
|
|
if part.Encoding != "" {
|
|
header["Content-Transfer-Encoding"] = []string{mime.QEncoding.Encode("utf-8", part.Encoding)}
|
|
}
|
|
if part.Disposition != "" {
|
|
header["Content-Disposition"] = []string{mime.QEncoding.Encode("utf-8", part.Disposition)}
|
|
}
|
|
|
|
sub, _ = wr.CreatePart(header)
|
|
fmt.Fprint(sub, part.Content)
|
|
}
|
|
|
|
_ = wr.Close()
|
|
// fallback if there are no parts, write PlainText with appropriate Content-Type
|
|
default:
|
|
fmt.Fprintf(&body, "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n")
|
|
fmt.Fprintf(&body, "Content-Transfer-Encoding: quoted-printable\r\n\r\n")
|
|
|
|
enc := quotedprintable.NewWriter(&body)
|
|
_, _ = enc.Write([]byte(msg.PlainText))
|
|
_ = enc.Close()
|
|
}
|
|
|
|
return tocrlf(body.Bytes())
|
|
}
|
|
|
|
func tocrlf(data []byte) []byte {
|
|
lf := bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
|
crlf := bytes.Replace(lf, []byte("\n"), []byte("\r\n"), -1)
|
|
return crlf
|
|
}
|