go-mail/msg.go

319 lines
8.6 KiB
Go
Raw Normal View History

2022-03-09 16:52:23 +01:00
package mail
import (
"errors"
2022-03-09 16:52:23 +01:00
"fmt"
"io"
2022-03-09 16:52:23 +01:00
"math/rand"
"mime"
2022-03-10 16:19:51 +01:00
"net/mail"
2022-03-09 16:52:23 +01:00
"os"
"time"
)
var (
// ErrNoFromAddress should be used when a FROM address is requrested but not set
ErrNoFromAddress = errors.New("no FROM address set")
// ErrNoRcptAddresses should be used when the list of RCPTs is empty
ErrNoRcptAddresses = errors.New("no recipient addresses set")
)
2022-03-09 16:52:23 +01:00
// Msg is the mail message struct
type Msg struct {
// charset represents the charset of the mail (defaults to UTF-8)
charset Charset
// encoding represents the message encoding (the encoder will be a corresponding WordEncoder)
encoding Encoding
2022-03-09 16:52:23 +01:00
// encoder represents a mime.WordEncoder from the std lib
encoder mime.WordEncoder
2022-03-09 16:52:23 +01:00
2022-03-10 16:19:51 +01:00
// genHeader is a slice of strings that the different generic mail Header fields
genHeader map[Header][]string
// addrHeader is a slice of strings that the different mail AddrHeader fields
addrHeader map[AddrHeader][]*mail.Address
2022-03-09 16:52:23 +01:00
}
// MsgOption returns a function that can be used for grouping Msg options
type MsgOption func(*Msg)
2022-03-09 16:52:23 +01:00
// NewMsg returns a new Msg pointer
func NewMsg(o ...MsgOption) *Msg {
2022-03-09 16:52:23 +01:00
m := &Msg{
encoding: EncodingQP,
charset: CharsetUTF8,
2022-03-10 16:19:51 +01:00
genHeader: make(map[Header][]string),
addrHeader: make(map[AddrHeader][]*mail.Address),
2022-03-09 16:52:23 +01:00
}
// Override defaults with optionally provided MsgOption functions
for _, co := range o {
if co == nil {
continue
}
co(m)
}
// Set the matcing mime.WordEncoder for the Msg
m.setEncoder()
2022-03-09 16:52:23 +01:00
return m
}
// WithCharset overrides the default message charset
func WithCharset(c Charset) MsgOption {
return func(m *Msg) {
m.charset = c
}
}
// WithEncoding overrides the default message encoding
func WithEncoding(e Encoding) MsgOption {
return func(m *Msg) {
m.encoding = e
}
}
// SetCharset sets the encoding charset of the Msg
func (m *Msg) SetCharset(c Charset) {
m.charset = c
}
// SetEncoding sets the encoding of the Msg
func (m *Msg) SetEncoding(e Encoding) {
m.encoding = e
}
2022-03-13 10:49:07 +01:00
// Encoding returns the currently set encoding of the Msg
func (m *Msg) Encoding() string {
return m.encoding.String()
}
// Charset returns the currently set charset of the Msg
func (m *Msg) Charset() string {
return m.charset.String()
}
2022-03-10 16:19:51 +01:00
// SetHeader sets a generic header field of the Msg
2022-03-09 16:52:23 +01:00
func (m *Msg) SetHeader(h Header, v ...string) {
for i, hv := range v {
v[i] = m.encodeString(hv)
}
2022-03-10 16:19:51 +01:00
m.genHeader[h] = v
}
// SetAddrHeader sets an address related header field of the Msg
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
var al []*mail.Address
for _, av := range v {
a, err := mail.ParseAddress(m.encodeString(av))
2022-03-10 16:19:51 +01:00
if err != nil {
return fmt.Errorf("failed to parse mail address header %q: %w", av, err)
}
al = append(al, a)
}
2022-03-09 16:52:23 +01:00
switch h {
case HeaderFrom:
2022-03-10 16:19:51 +01:00
m.addrHeader[h] = []*mail.Address{al[0]}
2022-03-09 16:52:23 +01:00
default:
2022-03-10 16:19:51 +01:00
m.addrHeader[h] = al
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
return nil
}
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
// in the validation process
func (m *Msg) SetAddrHeaderIgnoreInvalid(h AddrHeader, v ...string) {
var al []*mail.Address
for _, av := range v {
a, err := mail.ParseAddress(m.encodeString(av))
2022-03-10 16:19:51 +01:00
if err != nil {
continue
}
al = append(al, a)
}
m.addrHeader[h] = al
}
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
func (m *Msg) From(f string) error {
return m.SetAddrHeader(HeaderFrom, f)
2022-03-09 16:52:23 +01:00
}
2022-03-12 20:05:43 +01:00
// FromFormat takes a name and address, formats them RFC5322 compliant and stores them as
// the From address header field
func (m *Msg) FromFormat(n, a string) error {
return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, n, a))
}
2022-03-10 16:19:51 +01:00
// To takes and validates a given mail address list sets the To: addresses of the Msg
func (m *Msg) To(t ...string) error {
return m.SetAddrHeader(HeaderTo, t...)
2022-03-09 16:52:23 +01:00
}
2022-03-12 20:05:43 +01:00
// AddTo adds an additional address to the To address header field
func (m *Msg) AddTo(t string) error {
2022-03-13 10:49:07 +01:00
return m.addAddr(HeaderTo, t)
2022-03-12 20:05:43 +01:00
}
2022-03-13 10:49:07 +01:00
// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional To address header field
func (m *Msg) AddToFormat(n, a string) error {
return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, n, a))
}
2022-03-10 16:19:51 +01:00
// ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) ToIgnoreInvalid(t ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderTo, t...)
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
func (m *Msg) Cc(c ...string) error {
return m.SetAddrHeader(HeaderCc, c...)
}
2022-03-13 10:49:07 +01:00
// AddCc adds an additional address to the Cc address header field
func (m *Msg) AddCc(t string) error {
return m.addAddr(HeaderCc, t)
}
// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Cc address header field
func (m *Msg) AddCcFormat(n, a string) error {
return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, n, a))
}
2022-03-10 16:19:51 +01:00
// CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) CcIgnoreInvalid(c ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderCc, c...)
}
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
func (m *Msg) Bcc(b ...string) error {
return m.SetAddrHeader(HeaderBcc, b...)
}
2022-03-13 10:49:07 +01:00
// AddBcc adds an additional address to the Bcc address header field
func (m *Msg) AddBcc(t string) error {
return m.addAddr(HeaderBcc, t)
}
// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as
// as additional Bcc address header field
func (m *Msg) AddBccFormat(n, a string) error {
return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, n, a))
}
2022-03-10 16:19:51 +01:00
// BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg
// Any provided address that is not RFC5322 compliant, will be ignored
func (m *Msg) BccIgnoreInvalid(b ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderBcc, b...)
2022-03-09 16:52:23 +01:00
}
2022-03-13 10:49:07 +01:00
// addAddr adds an additional address to the given addrHeader of the Msg
func (m *Msg) addAddr(h AddrHeader, a string) error {
var al []string
for _, ca := range m.addrHeader[h] {
al = append(al, ca.String())
}
al = append(al, a)
return m.SetAddrHeader(h, al...)
}
// Subject sets the "Subject" header field of the Msg
func (m *Msg) Subject(s string) {
m.SetHeader(HeaderSubject, s)
}
2022-03-09 16:52:23 +01:00
// SetMessageID generates a random message id for the mail
func (m *Msg) SetMessageID() {
hn, err := os.Hostname()
if err != nil {
hn = "localhost.localdomain"
}
ct := time.Now().Unix()
2022-03-09 16:52:23 +01:00
r := rand.New(rand.NewSource(ct))
rn := r.Int()
pid := os.Getpid()
mid := fmt.Sprintf("%d.%d.%d@%s", pid, rn, ct, hn)
m.SetMessageIDWithValue(mid)
}
// SetMessageIDWithValue sets the message id for the mail
func (m *Msg) SetMessageIDWithValue(v string) {
m.SetHeader(HeaderMessageID, fmt.Sprintf("<%s>", v))
2022-03-09 16:52:23 +01:00
}
2022-03-10 16:19:51 +01:00
// SetBulk sets the "Precedence: bulk" genHeader which is recommended for
2022-03-09 16:52:23 +01:00
// automated mails like OOO replies
// See: https://www.rfc-editor.org/rfc/rfc2076#section-3.9
func (m *Msg) SetBulk() {
m.SetHeader(HeaderPrecedence, "bulk")
}
2022-03-10 16:19:51 +01:00
// SetDate sets the Date genHeader field to the current time in a valid format
func (m *Msg) SetDate() {
ts := time.Now().Format(time.RFC1123Z)
m.SetHeader(HeaderDate, ts)
}
// GetSender returns the currently set FROM address. If f is true, it will return the full
// address string including the address name, if set
func (m *Msg) GetSender(ff bool) (string, error) {
f, ok := m.addrHeader[HeaderFrom]
if !ok || len(f) == 0 {
return "", ErrNoFromAddress
2022-03-11 16:57:14 +01:00
}
if ff {
return f[0].String(), nil
2022-03-11 16:57:14 +01:00
}
return f[0].Address, nil
}
// GetRecipients returns a list of the currently set TO/CC/BCC addresses.
func (m *Msg) GetRecipients() ([]string, error) {
var rl []string
for _, t := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} {
al, ok := m.addrHeader[t]
if !ok || len(al) == 0 {
continue
}
for _, r := range al {
rl = append(rl, r.Address)
}
}
if len(rl) <= 0 {
return rl, ErrNoRcptAddresses
}
return rl, nil
}
func (m *Msg) Write(w io.Writer) (int64, error) {
mw := &msgWriter{w: w}
mw.writeMsg(m)
return mw.n, mw.err
2022-03-09 16:52:23 +01:00
}
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
func (m *Msg) setEncoder() {
switch m.encoding {
case EncodingQP:
m.encoder = mime.QEncoding
case EncodingB64:
m.encoder = mime.BEncoding
default:
m.encoder = mime.QEncoding
}
}
// encodeString encodes a string based on the configured message encoder and the corresponding
// charset for the Msg
func (m *Msg) encodeString(s string) string {
return m.encoder.Encode(string(m.charset), s)
}