mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
Compare commits
No commits in common. "4e880ab31c0895c6d05256af69247fe32f9b8392" and "68e6284e4e23d6b24a2ce267acf655e6cba66906" have entirely different histories.
4e880ab31c
...
68e6284e4e
3 changed files with 349 additions and 355 deletions
345
msg.go
345
msg.go
|
@ -126,8 +126,8 @@ const SendmailPath = "/usr/sbin/sendmail"
|
||||||
type MsgOption func(*Msg)
|
type MsgOption func(*Msg)
|
||||||
|
|
||||||
// NewMsg returns a new Msg pointer
|
// NewMsg returns a new Msg pointer
|
||||||
func NewMsg(opts ...MsgOption) *Msg {
|
func NewMsg(o ...MsgOption) *Msg {
|
||||||
msg := &Msg{
|
m := &Msg{
|
||||||
addrHeader: make(map[AddrHeader][]*mail.Address),
|
addrHeader: make(map[AddrHeader][]*mail.Address),
|
||||||
charset: CharsetUTF8,
|
charset: CharsetUTF8,
|
||||||
encoding: EncodingQP,
|
encoding: EncodingQP,
|
||||||
|
@ -137,17 +137,17 @@ func NewMsg(opts ...MsgOption) *Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided MsgOption functions
|
// Override defaults with optionally provided MsgOption functions
|
||||||
for _, option := range opts {
|
for _, co := range o {
|
||||||
if option == nil {
|
if co == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
option(msg)
|
co(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the matcing mime.WordEncoder for the Msg
|
// Set the matcing mime.WordEncoder for the Msg
|
||||||
msg.setEncoder()
|
m.setEncoder()
|
||||||
|
|
||||||
return msg
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCharset overrides the default message charset
|
// WithCharset overrides the default message charset
|
||||||
|
@ -186,9 +186,9 @@ func WithMiddleware(mw Middleware) MsgOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPGPType overrides the default PGPType of the message
|
// WithPGPType overrides the default PGPType of the message
|
||||||
func WithPGPType(pt PGPType) MsgOption {
|
func WithPGPType(t PGPType) MsgOption {
|
||||||
return func(m *Msg) {
|
return func(m *Msg) {
|
||||||
m.pgptype = pt
|
m.pgptype = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,20 +232,20 @@ func (m *Msg) Charset() string {
|
||||||
// For adding address headers like "To:" or "From", see SetAddrHeader
|
// For adding address headers like "To:" or "From", see SetAddrHeader
|
||||||
//
|
//
|
||||||
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead
|
// Deprecated: This method only exists for compatibility reason. Please use SetGenHeader instead
|
||||||
func (m *Msg) SetHeader(header Header, values ...string) {
|
func (m *Msg) SetHeader(h Header, v ...string) {
|
||||||
m.SetGenHeader(header, values...)
|
m.SetGenHeader(h, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGenHeader sets a generic header field of the Msg
|
// SetGenHeader sets a generic header field of the Msg
|
||||||
// For adding address headers like "To:" or "From", see SetAddrHeader
|
// For adding address headers like "To:" or "From", see SetAddrHeader
|
||||||
func (m *Msg) SetGenHeader(header Header, values ...string) {
|
func (m *Msg) SetGenHeader(h Header, v ...string) {
|
||||||
if m.genHeader == nil {
|
if m.genHeader == nil {
|
||||||
m.genHeader = make(map[Header][]string)
|
m.genHeader = make(map[Header][]string)
|
||||||
}
|
}
|
||||||
for i, val := range values {
|
for i, hv := range v {
|
||||||
values[i] = m.encodeString(val)
|
v[i] = m.encodeString(hv)
|
||||||
}
|
}
|
||||||
m.genHeader[header] = values
|
m.genHeader[h] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeaderPreformatted sets a generic header field of the Msg which content is
|
// SetHeaderPreformatted sets a generic header field of the Msg which content is
|
||||||
|
@ -253,8 +253,8 @@ func (m *Msg) SetGenHeader(header Header, values ...string) {
|
||||||
//
|
//
|
||||||
// Deprecated: This method only exists for compatibility reason. Please use
|
// Deprecated: This method only exists for compatibility reason. Please use
|
||||||
// SetGenHeaderPreformatted instead
|
// SetGenHeaderPreformatted instead
|
||||||
func (m *Msg) SetHeaderPreformatted(header Header, value string) {
|
func (m *Msg) SetHeaderPreformatted(h Header, v string) {
|
||||||
m.SetGenHeaderPreformatted(header, value)
|
m.SetGenHeaderPreformatted(h, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGenHeaderPreformatted sets a generic header field of the Msg which content is
|
// SetGenHeaderPreformatted sets a generic header field of the Msg which content is
|
||||||
|
@ -268,207 +268,206 @@ func (m *Msg) SetHeaderPreformatted(header Header, value string) {
|
||||||
// user is respondible for the formating of the message header, go-mail cannot
|
// user is respondible for the formating of the message header, go-mail cannot
|
||||||
// guarantee the fully compliance with the RFC 2822. It is recommended to use
|
// guarantee the fully compliance with the RFC 2822. It is recommended to use
|
||||||
// SetGenHeader instead.
|
// SetGenHeader instead.
|
||||||
func (m *Msg) SetGenHeaderPreformatted(header Header, value string) {
|
func (m *Msg) SetGenHeaderPreformatted(h Header, v string) {
|
||||||
if m.preformHeader == nil {
|
if m.preformHeader == nil {
|
||||||
m.preformHeader = make(map[Header]string)
|
m.preformHeader = make(map[Header]string)
|
||||||
}
|
}
|
||||||
m.preformHeader[header] = value
|
m.preformHeader[h] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAddrHeader sets an address related header field of the Msg
|
// SetAddrHeader sets an address related header field of the Msg
|
||||||
func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error {
|
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error {
|
||||||
if m.addrHeader == nil {
|
if m.addrHeader == nil {
|
||||||
m.addrHeader = make(map[AddrHeader][]*mail.Address)
|
m.addrHeader = make(map[AddrHeader][]*mail.Address)
|
||||||
}
|
}
|
||||||
var addresses []*mail.Address
|
var al []*mail.Address
|
||||||
for _, addrVal := range values {
|
for _, av := range v {
|
||||||
address, err := mail.ParseAddress(addrVal)
|
a, err := mail.ParseAddress(av)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(errParseMailAddr, addrVal, err)
|
return fmt.Errorf(errParseMailAddr, av, err)
|
||||||
}
|
}
|
||||||
addresses = append(addresses, address)
|
al = append(al, a)
|
||||||
}
|
}
|
||||||
switch header {
|
switch h {
|
||||||
case HeaderFrom:
|
case HeaderFrom:
|
||||||
if len(addresses) > 0 {
|
if len(al) > 0 {
|
||||||
m.addrHeader[header] = []*mail.Address{addresses[0]}
|
m.addrHeader[h] = []*mail.Address{al[0]}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
m.addrHeader[header] = addresses
|
m.addrHeader[h] = al
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
|
// SetAddrHeaderIgnoreInvalid sets an address related header field of the Msg and ignores invalid address
|
||||||
// in the validation process
|
// in the validation process
|
||||||
func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) {
|
func (m *Msg) SetAddrHeaderIgnoreInvalid(h AddrHeader, v ...string) {
|
||||||
var addresses []*mail.Address
|
var al []*mail.Address
|
||||||
for _, addrVal := range values {
|
for _, av := range v {
|
||||||
address, err := mail.ParseAddress(m.encodeString(addrVal))
|
a, err := mail.ParseAddress(m.encodeString(av))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addresses = append(addresses, address)
|
al = append(al, a)
|
||||||
}
|
}
|
||||||
m.addrHeader[header] = addresses
|
m.addrHeader[h] = al
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvelopeFrom takes and validates a given mail address and sets it as envelope "FROM"
|
// EnvelopeFrom takes and validates a given mail address and sets it as envelope "FROM"
|
||||||
// addrHeader of the Msg
|
// addrHeader of the Msg
|
||||||
func (m *Msg) EnvelopeFrom(from string) error {
|
func (m *Msg) EnvelopeFrom(f string) error {
|
||||||
return m.SetAddrHeader(HeaderEnvelopeFrom, from)
|
return m.SetAddrHeader(HeaderEnvelopeFrom, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvelopeFromFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// EnvelopeFromFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// the envelope FROM address header field
|
// the envelope FROM address header field
|
||||||
func (m *Msg) EnvelopeFromFormat(name, addr string) error {
|
func (m *Msg) EnvelopeFromFormat(n, a string) error {
|
||||||
return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
|
// From takes and validates a given mail address and sets it as "From" genHeader of the Msg
|
||||||
func (m *Msg) From(from string) error {
|
func (m *Msg) From(f string) error {
|
||||||
return m.SetAddrHeader(HeaderFrom, from)
|
return m.SetAddrHeader(HeaderFrom, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// FromFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// the From address header field
|
// the From address header field
|
||||||
func (m *Msg) FromFormat(name, addr string) error {
|
func (m *Msg) FromFormat(n, a string) error {
|
||||||
return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// To takes and validates a given mail address list sets the To: addresses of the Msg
|
// To takes and validates a given mail address list sets the To: addresses of the Msg
|
||||||
func (m *Msg) To(rcpts ...string) error {
|
func (m *Msg) To(t ...string) error {
|
||||||
return m.SetAddrHeader(HeaderTo, rcpts...)
|
return m.SetAddrHeader(HeaderTo, t...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTo adds an additional address to the To address header field
|
// AddTo adds an additional address to the To address header field
|
||||||
func (m *Msg) AddTo(rcpt string) error {
|
func (m *Msg) AddTo(t string) error {
|
||||||
return m.addAddr(HeaderTo, rcpt)
|
return m.addAddr(HeaderTo, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// AddToFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// as additional To address header field
|
// as additional To address header field
|
||||||
func (m *Msg) AddToFormat(name, addr string) error {
|
func (m *Msg) AddToFormat(n, a string) error {
|
||||||
return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToIgnoreInvalid takes and validates a given mail address list sets the To: addresses of the Msg
|
// 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
|
// Any provided address that is not RFC5322 compliant, will be ignored
|
||||||
func (m *Msg) ToIgnoreInvalid(rcpts ...string) {
|
func (m *Msg) ToIgnoreInvalid(t ...string) {
|
||||||
m.SetAddrHeaderIgnoreInvalid(HeaderTo, rcpts...)
|
m.SetAddrHeaderIgnoreInvalid(HeaderTo, t...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToFromString takes and validates a given string of comma separted
|
// ToFromString takes and validates a given string of comma separted
|
||||||
// mail address and sets them as To: addresses of the Msg
|
// mail address and sets them as To: addresses of the Msg
|
||||||
func (m *Msg) ToFromString(rcpts string) error {
|
func (m *Msg) ToFromString(v string) error {
|
||||||
return m.To(strings.Split(rcpts, ",")...)
|
return m.To(strings.Split(v, ",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
|
// Cc takes and validates a given mail address list sets the Cc: addresses of the Msg
|
||||||
func (m *Msg) Cc(rcpts ...string) error {
|
func (m *Msg) Cc(c ...string) error {
|
||||||
return m.SetAddrHeader(HeaderCc, rcpts...)
|
return m.SetAddrHeader(HeaderCc, c...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCc adds an additional address to the Cc address header field
|
// AddCc adds an additional address to the Cc address header field
|
||||||
func (m *Msg) AddCc(rcpt string) error {
|
func (m *Msg) AddCc(t string) error {
|
||||||
return m.addAddr(HeaderCc, rcpt)
|
return m.addAddr(HeaderCc, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// AddCcFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// as additional Cc address header field
|
// as additional Cc address header field
|
||||||
func (m *Msg) AddCcFormat(name, addr string) error {
|
func (m *Msg) AddCcFormat(n, a string) error {
|
||||||
return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CcIgnoreInvalid takes and validates a given mail address list sets the Cc: addresses of the Msg
|
// 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
|
// Any provided address that is not RFC5322 compliant, will be ignored
|
||||||
func (m *Msg) CcIgnoreInvalid(rcpts ...string) {
|
func (m *Msg) CcIgnoreInvalid(c ...string) {
|
||||||
m.SetAddrHeaderIgnoreInvalid(HeaderCc, rcpts...)
|
m.SetAddrHeaderIgnoreInvalid(HeaderCc, c...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CcFromString takes and validates a given string of comma separted
|
// CcFromString takes and validates a given string of comma separted
|
||||||
// mail address and sets them as Cc: addresses of the Msg
|
// mail address and sets them as Cc: addresses of the Msg
|
||||||
func (m *Msg) CcFromString(rcpts string) error {
|
func (m *Msg) CcFromString(v string) error {
|
||||||
return m.Cc(strings.Split(rcpts, ",")...)
|
return m.Cc(strings.Split(v, ",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
|
// Bcc takes and validates a given mail address list sets the Bcc: addresses of the Msg
|
||||||
func (m *Msg) Bcc(rcpts ...string) error {
|
func (m *Msg) Bcc(b ...string) error {
|
||||||
return m.SetAddrHeader(HeaderBcc, rcpts...)
|
return m.SetAddrHeader(HeaderBcc, b...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBcc adds an additional address to the Bcc address header field
|
// AddBcc adds an additional address to the Bcc address header field
|
||||||
func (m *Msg) AddBcc(rcpt string) error {
|
func (m *Msg) AddBcc(t string) error {
|
||||||
return m.addAddr(HeaderBcc, rcpt)
|
return m.addAddr(HeaderBcc, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// AddBccFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// as additional Bcc address header field
|
// as additional Bcc address header field
|
||||||
func (m *Msg) AddBccFormat(name, addr string) error {
|
func (m *Msg) AddBccFormat(n, a string) error {
|
||||||
return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BccIgnoreInvalid takes and validates a given mail address list sets the Bcc: addresses of the Msg
|
// 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
|
// Any provided address that is not RFC5322 compliant, will be ignored
|
||||||
func (m *Msg) BccIgnoreInvalid(rcpts ...string) {
|
func (m *Msg) BccIgnoreInvalid(b ...string) {
|
||||||
m.SetAddrHeaderIgnoreInvalid(HeaderBcc, rcpts...)
|
m.SetAddrHeaderIgnoreInvalid(HeaderBcc, b...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BccFromString takes and validates a given string of comma separted
|
// BccFromString takes and validates a given string of comma separted
|
||||||
// mail address and sets them as Bcc: addresses of the Msg
|
// mail address and sets them as Bcc: addresses of the Msg
|
||||||
func (m *Msg) BccFromString(rcpts string) error {
|
func (m *Msg) BccFromString(v string) error {
|
||||||
return m.Bcc(strings.Split(rcpts, ",")...)
|
return m.Bcc(strings.Split(v, ",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyTo takes and validates a given mail address and sets it as "Reply-To" addrHeader of the Msg
|
// ReplyTo takes and validates a given mail address and sets it as "Reply-To" addrHeader of the Msg
|
||||||
func (m *Msg) ReplyTo(addr string) error {
|
func (m *Msg) ReplyTo(r string) error {
|
||||||
replyTo, err := mail.ParseAddress(addr)
|
rt, err := mail.ParseAddress(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse reply-to address: %w", err)
|
return fmt.Errorf("failed to parse reply-to address: %w", err)
|
||||||
}
|
}
|
||||||
m.SetGenHeader(HeaderReplyTo, replyTo.String())
|
m.SetGenHeader(HeaderReplyTo, rt.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyToFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
// ReplyToFormat takes a name and address, formats them RFC5322 compliant and stores them as
|
||||||
// the Reply-To header field
|
// the Reply-To header field
|
||||||
func (m *Msg) ReplyToFormat(name, addr string) error {
|
func (m *Msg) ReplyToFormat(n, a string) error {
|
||||||
return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAddr adds an additional address to the given addrHeader of the Msg
|
// addAddr adds an additional address to the given addrHeader of the Msg
|
||||||
func (m *Msg) addAddr(header AddrHeader, addr string) error {
|
func (m *Msg) addAddr(h AddrHeader, a string) error {
|
||||||
var addresses []string
|
var al []string
|
||||||
for _, address := range m.addrHeader[header] {
|
for _, ca := range m.addrHeader[h] {
|
||||||
addresses = append(addresses, address.String())
|
al = append(al, ca.String())
|
||||||
}
|
}
|
||||||
addresses = append(addresses, addr)
|
al = append(al, a)
|
||||||
return m.SetAddrHeader(header, addresses...)
|
return m.SetAddrHeader(h, al...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subject sets the "Subject" header field of the Msg
|
// Subject sets the "Subject" header field of the Msg
|
||||||
func (m *Msg) Subject(subj string) {
|
func (m *Msg) Subject(s string) {
|
||||||
m.SetGenHeader(HeaderSubject, subj)
|
m.SetGenHeader(HeaderSubject, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMessageID generates a random message id for the mail
|
// SetMessageID generates a random message id for the mail
|
||||||
func (m *Msg) SetMessageID() {
|
func (m *Msg) SetMessageID() {
|
||||||
hostname, err := os.Hostname()
|
hn, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostname = "localhost.localdomain"
|
hn = "localhost.localdomain"
|
||||||
}
|
}
|
||||||
randNumPrimary, _ := randNum(100000000)
|
rn, _ := randNum(100000000)
|
||||||
randNumSecondary, _ := randNum(10000)
|
rm, _ := randNum(10000)
|
||||||
randString, _ := randomStringSecure(17)
|
rs, _ := randomStringSecure(17)
|
||||||
procID := os.Getpid() * randNumSecondary
|
pid := os.Getpid() * rm
|
||||||
messageID := fmt.Sprintf("%d.%d%d.%s@%s", procID, randNumPrimary, randNumSecondary,
|
mid := fmt.Sprintf("%d.%d%d.%s@%s", pid, rn, rm, rs, hn)
|
||||||
randString, hostname)
|
m.SetMessageIDWithValue(mid)
|
||||||
m.SetMessageIDWithValue(messageID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMessageIDWithValue sets the message id for the mail
|
// SetMessageIDWithValue sets the message id for the mail
|
||||||
func (m *Msg) SetMessageIDWithValue(messageID string) {
|
func (m *Msg) SetMessageIDWithValue(v string) {
|
||||||
m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", messageID))
|
m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBulk sets the "Precedence: bulk" and "X-Auto-Response-Suppress: All" genHeaders which are
|
// SetBulk sets the "Precedence: bulk" and "X-Auto-Response-Suppress: All" genHeaders which are
|
||||||
|
@ -482,35 +481,35 @@ func (m *Msg) SetBulk() {
|
||||||
|
|
||||||
// SetDate sets the Date genHeader field to the current time in a valid format
|
// SetDate sets the Date genHeader field to the current time in a valid format
|
||||||
func (m *Msg) SetDate() {
|
func (m *Msg) SetDate() {
|
||||||
now := time.Now().Format(time.RFC1123Z)
|
ts := time.Now().Format(time.RFC1123Z)
|
||||||
m.SetGenHeader(HeaderDate, now)
|
m.SetGenHeader(HeaderDate, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDateWithValue sets the Date genHeader field to the provided time in a valid format
|
// SetDateWithValue sets the Date genHeader field to the provided time in a valid format
|
||||||
func (m *Msg) SetDateWithValue(timeVal time.Time) {
|
func (m *Msg) SetDateWithValue(t time.Time) {
|
||||||
m.SetGenHeader(HeaderDate, timeVal.Format(time.RFC1123Z))
|
m.SetGenHeader(HeaderDate, t.Format(time.RFC1123Z))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetImportance sets the Msg Importance/Priority header to given Importance
|
// SetImportance sets the Msg Importance/Priority header to given Importance
|
||||||
func (m *Msg) SetImportance(importance Importance) {
|
func (m *Msg) SetImportance(i Importance) {
|
||||||
if importance == ImportanceNormal {
|
if i == ImportanceNormal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.SetGenHeader(HeaderImportance, importance.String())
|
m.SetGenHeader(HeaderImportance, i.String())
|
||||||
m.SetGenHeader(HeaderPriority, importance.NumString())
|
m.SetGenHeader(HeaderPriority, i.NumString())
|
||||||
m.SetGenHeader(HeaderXPriority, importance.XPrioString())
|
m.SetGenHeader(HeaderXPriority, i.XPrioString())
|
||||||
m.SetGenHeader(HeaderXMSMailPriority, importance.NumString())
|
m.SetGenHeader(HeaderXMSMailPriority, i.NumString())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOrganization sets the provided string as Organization header for the Msg
|
// SetOrganization sets the provided string as Organization header for the Msg
|
||||||
func (m *Msg) SetOrganization(org string) {
|
func (m *Msg) SetOrganization(o string) {
|
||||||
m.SetGenHeader(HeaderOrganization, org)
|
m.SetGenHeader(HeaderOrganization, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserAgent sets the User-Agent/X-Mailer header for the Msg
|
// SetUserAgent sets the User-Agent/X-Mailer header for the Msg
|
||||||
func (m *Msg) SetUserAgent(userAgent string) {
|
func (m *Msg) SetUserAgent(a string) {
|
||||||
m.SetGenHeader(HeaderUserAgent, userAgent)
|
m.SetGenHeader(HeaderUserAgent, a)
|
||||||
m.SetGenHeader(HeaderXMailer, userAgent)
|
m.SetGenHeader(HeaderXMailer, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDelivered will return true if the Msg has been successfully delivered
|
// IsDelivered will return true if the Msg has been successfully delivered
|
||||||
|
@ -522,17 +521,17 @@ func (m *Msg) IsDelivered() bool {
|
||||||
// as described in RFC8098. It allows to provide a list recipient addresses.
|
// as described in RFC8098. It allows to provide a list recipient addresses.
|
||||||
// Address validation is performed
|
// Address validation is performed
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc8098.html
|
// See: https://www.rfc-editor.org/rfc/rfc8098.html
|
||||||
func (m *Msg) RequestMDNTo(rcpts ...string) error {
|
func (m *Msg) RequestMDNTo(t ...string) error {
|
||||||
var addresses []string
|
var tl []string
|
||||||
for _, addrVal := range rcpts {
|
for _, at := range t {
|
||||||
address, err := mail.ParseAddress(addrVal)
|
a, err := mail.ParseAddress(at)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(errParseMailAddr, addrVal, err)
|
return fmt.Errorf(errParseMailAddr, at, err)
|
||||||
}
|
}
|
||||||
addresses = append(addresses, address.String())
|
tl = append(tl, a.String())
|
||||||
}
|
}
|
||||||
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
|
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
|
||||||
m.genHeader[HeaderDispositionNotificationTo] = addresses
|
m.genHeader[HeaderDispositionNotificationTo] = tl
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -541,77 +540,77 @@ func (m *Msg) RequestMDNTo(rcpts ...string) error {
|
||||||
// as described in RFC8098. It allows to provide a recipient address with name and address and will format
|
// as described in RFC8098. It allows to provide a recipient address with name and address and will format
|
||||||
// accordingly. Address validation is performed
|
// accordingly. Address validation is performed
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc8098.html
|
// See: https://www.rfc-editor.org/rfc/rfc8098.html
|
||||||
func (m *Msg) RequestMDNToFormat(name, addr string) error {
|
func (m *Msg) RequestMDNToFormat(n, a string) error {
|
||||||
return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, name, addr))
|
return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestMDNAddTo adds an additional recipient to the recipient list of the MDN
|
// RequestMDNAddTo adds an additional recipient to the recipient list of the MDN
|
||||||
func (m *Msg) RequestMDNAddTo(rcpt string) error {
|
func (m *Msg) RequestMDNAddTo(t string) error {
|
||||||
address, err := mail.ParseAddress(rcpt)
|
a, err := mail.ParseAddress(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(errParseMailAddr, rcpt, err)
|
return fmt.Errorf(errParseMailAddr, t, err)
|
||||||
}
|
}
|
||||||
var addresses []string
|
var tl []string
|
||||||
addresses = append(addresses, m.genHeader[HeaderDispositionNotificationTo]...)
|
tl = append(tl, m.genHeader[HeaderDispositionNotificationTo]...)
|
||||||
addresses = append(addresses, address.String())
|
tl = append(tl, a.String())
|
||||||
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
|
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
|
||||||
m.genHeader[HeaderDispositionNotificationTo] = addresses
|
m.genHeader[HeaderDispositionNotificationTo] = tl
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestMDNAddToFormat adds an additional formated recipient to the recipient list of the MDN
|
// RequestMDNAddToFormat adds an additional formated recipient to the recipient list of the MDN
|
||||||
func (m *Msg) RequestMDNAddToFormat(name, addr string) error {
|
func (m *Msg) RequestMDNAddToFormat(n, a string) error {
|
||||||
return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, name, addr))
|
return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, n, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSender returns the currently set envelope FROM address. If no envelope FROM is set it will use
|
// GetSender returns the currently set envelope FROM address. If no envelope FROM is set it will use
|
||||||
// the first mail body FROM address. If useFullAddr is true, it will return the full address string
|
// the first mail body FROM address. If ff is true, it will return the full address string including
|
||||||
// including the address name, if set
|
// the address name, if set
|
||||||
func (m *Msg) GetSender(useFullAddr bool) (string, error) {
|
func (m *Msg) GetSender(ff bool) (string, error) {
|
||||||
from, ok := m.addrHeader[HeaderEnvelopeFrom]
|
f, ok := m.addrHeader[HeaderEnvelopeFrom]
|
||||||
if !ok || len(from) == 0 {
|
if !ok || len(f) == 0 {
|
||||||
from, ok = m.addrHeader[HeaderFrom]
|
f, ok = m.addrHeader[HeaderFrom]
|
||||||
if !ok || len(from) == 0 {
|
if !ok || len(f) == 0 {
|
||||||
return "", ErrNoFromAddress
|
return "", ErrNoFromAddress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if useFullAddr {
|
if ff {
|
||||||
return from[0].String(), nil
|
return f[0].String(), nil
|
||||||
}
|
}
|
||||||
return from[0].Address, nil
|
return f[0].Address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecipients returns a list of the currently set TO/CC/BCC addresses.
|
// GetRecipients returns a list of the currently set TO/CC/BCC addresses.
|
||||||
func (m *Msg) GetRecipients() ([]string, error) {
|
func (m *Msg) GetRecipients() ([]string, error) {
|
||||||
var rcpts []string
|
var rl []string
|
||||||
for _, addressType := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} {
|
for _, t := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} {
|
||||||
addresses, ok := m.addrHeader[addressType]
|
al, ok := m.addrHeader[t]
|
||||||
if !ok || len(addresses) == 0 {
|
if !ok || len(al) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, r := range addresses {
|
for _, r := range al {
|
||||||
rcpts = append(rcpts, r.Address)
|
rl = append(rl, r.Address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rcpts) <= 0 {
|
if len(rl) <= 0 {
|
||||||
return rcpts, ErrNoRcptAddresses
|
return rl, ErrNoRcptAddresses
|
||||||
}
|
}
|
||||||
return rcpts, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddrHeader returns the content of the requested address header of the Msg
|
// GetAddrHeader returns the content of the requested address header of the Msg
|
||||||
func (m *Msg) GetAddrHeader(header AddrHeader) []*mail.Address {
|
func (m *Msg) GetAddrHeader(h AddrHeader) []*mail.Address {
|
||||||
return m.addrHeader[header]
|
return m.addrHeader[h]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddrHeaderString returns the address string of the requested address header of the Msg
|
// GetAddrHeaderString returns the address string of the requested address header of the Msg
|
||||||
func (m *Msg) GetAddrHeaderString(header AddrHeader) []string {
|
func (m *Msg) GetAddrHeaderString(h AddrHeader) []string {
|
||||||
var addresses []string
|
var al []string
|
||||||
for _, mh := range m.addrHeader[header] {
|
for _, mh := range m.addrHeader[h] {
|
||||||
addresses = append(addresses, mh.String())
|
al = append(al, mh.String())
|
||||||
}
|
}
|
||||||
return addresses
|
return al
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFrom returns the content of the From address header of the Msg
|
// GetFrom returns the content of the From address header of the Msg
|
||||||
|
@ -655,8 +654,8 @@ func (m *Msg) GetBccString() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGenHeader returns the content of the requested generic header of the Msg
|
// GetGenHeader returns the content of the requested generic header of the Msg
|
||||||
func (m *Msg) GetGenHeader(header Header) []string {
|
func (m *Msg) GetGenHeader(h Header) []string {
|
||||||
return m.genHeader[header]
|
return m.genHeader[h]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetParts returns the message parts of the Msg
|
// GetParts returns the message parts of the Msg
|
||||||
|
@ -670,8 +669,8 @@ func (m *Msg) GetAttachments() []*File {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAttachements sets the attachements of the message.
|
// SetAttachements sets the attachements of the message.
|
||||||
func (m *Msg) SetAttachements(files []*File) {
|
func (m *Msg) SetAttachements(ff []*File) {
|
||||||
m.attachments = files
|
m.attachments = ff
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsetAllAttachments unset the attachments of the message.
|
// UnsetAllAttachments unset the attachments of the message.
|
||||||
|
@ -685,8 +684,8 @@ func (m *Msg) GetEmbeds() []*File {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEmbeds sets the embeds of the message.
|
// SetEmbeds sets the embeds of the message.
|
||||||
func (m *Msg) SetEmbeds(files []*File) {
|
func (m *Msg) SetEmbeds(ff []*File) {
|
||||||
m.embeds = files
|
m.embeds = ff
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsetAllEmbeds unset the embeds of the message.
|
// UnsetAllEmbeds unset the embeds of the message.
|
||||||
|
@ -701,16 +700,16 @@ func (m *Msg) UnsetAllParts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBodyString sets the body of the message.
|
// SetBodyString sets the body of the message.
|
||||||
func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...PartOption) {
|
func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) {
|
||||||
buffer := bytes.NewBufferString(content)
|
buf := bytes.NewBufferString(b)
|
||||||
writeFunc := writeFuncFromBuffer(buffer)
|
w := writeFuncFromBuffer(buf)
|
||||||
m.SetBodyWriter(contentType, writeFunc, opts...)
|
m.SetBodyWriter(ct, w, o...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBodyWriter sets the body of the message.
|
// SetBodyWriter sets the body of the message.
|
||||||
func (m *Msg) SetBodyWriter(contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption) {
|
func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) {
|
||||||
p := m.newPart(contentType, opts...)
|
p := m.newPart(ct, o...)
|
||||||
p.w = writeFunc
|
p.w = w
|
||||||
m.parts = []*Part{p}
|
m.parts = []*Part{p}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,9 +933,9 @@ func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
|
||||||
|
|
||||||
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
|
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
|
||||||
func (m *Msg) WriteTo(w io.Writer) (int64, error) {
|
func (m *Msg) WriteTo(w io.Writer) (int64, error) {
|
||||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
mw := &msgWriter{w: w, c: m.charset, en: m.encoder}
|
||||||
mw.writeMsg(m.applyMiddlewares(m))
|
mw.writeMsg(m.applyMiddlewares(m))
|
||||||
return mw.bytesWritten, mw.err
|
return mw.n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
|
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
|
||||||
|
@ -951,10 +950,10 @@ func (m *Msg) WriteToSkipMiddleware(w io.Writer, mt MiddlewareType) (int64, erro
|
||||||
mwl = append(mwl, m.middlewares[i])
|
mwl = append(mwl, m.middlewares[i])
|
||||||
}
|
}
|
||||||
m.middlewares = mwl
|
m.middlewares = mwl
|
||||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
mw := &msgWriter{w: w, c: m.charset, en: m.encoder}
|
||||||
mw.writeMsg(m.applyMiddlewares(m))
|
mw.writeMsg(m.applyMiddlewares(m))
|
||||||
m.middlewares = omwl
|
m.middlewares = omwl
|
||||||
return mw.bytesWritten, mw.err
|
return mw.n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write is an alias method to WriteTo due to compatibility reasons
|
// Write is an alias method to WriteTo due to compatibility reasons
|
||||||
|
|
351
msgwriter.go
351
msgwriter.go
|
@ -35,241 +35,237 @@ const DoubleNewLine = "\r\n\r\n"
|
||||||
|
|
||||||
// msgWriter handles the I/O to the io.WriteCloser of the SMTP client
|
// msgWriter handles the I/O to the io.WriteCloser of the SMTP client
|
||||||
type msgWriter struct {
|
type msgWriter struct {
|
||||||
bytesWritten int64
|
c Charset
|
||||||
charset Charset
|
d int8
|
||||||
depth int8
|
en mime.WordEncoder
|
||||||
encoder mime.WordEncoder
|
err error
|
||||||
err error
|
mpw [3]*multipart.Writer
|
||||||
multiPartWriter [3]*multipart.Writer
|
n int64
|
||||||
partWriter io.Writer
|
pw io.Writer
|
||||||
writer io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements the io.Writer interface for msgWriter
|
// Write implements the io.Writer interface for msgWriter
|
||||||
func (mw *msgWriter) Write(payload []byte) (int, error) {
|
func (mw *msgWriter) Write(p []byte) (int, error) {
|
||||||
if mw.err != nil {
|
if mw.err != nil {
|
||||||
return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err)
|
return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var n int
|
var n int
|
||||||
n, mw.err = mw.writer.Write(payload)
|
n, mw.err = mw.w.Write(p)
|
||||||
mw.bytesWritten += int64(n)
|
mw.n += int64(n)
|
||||||
return n, mw.err
|
return n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMsg formats the message and sends it to its io.Writer
|
// writeMsg formats the message and sends it to its io.Writer
|
||||||
func (mw *msgWriter) writeMsg(msg *Msg) {
|
func (mw *msgWriter) writeMsg(m *Msg) {
|
||||||
msg.addDefaultHeader()
|
m.addDefaultHeader()
|
||||||
msg.checkUserAgent()
|
m.checkUserAgent()
|
||||||
mw.writeGenHeader(msg)
|
mw.writeGenHeader(m)
|
||||||
mw.writePreformattedGenHeader(msg)
|
mw.writePreformattedGenHeader(m)
|
||||||
|
|
||||||
// Set the FROM header (or envelope FROM if FROM is empty)
|
// Set the FROM header (or envelope FROM if FROM is empty)
|
||||||
hasFrom := true
|
hf := true
|
||||||
from, ok := msg.addrHeader[HeaderFrom]
|
f, ok := m.addrHeader[HeaderFrom]
|
||||||
if !ok || (len(from) == 0 || from == nil) {
|
if !ok || (len(f) == 0 || f == nil) {
|
||||||
from, ok = msg.addrHeader[HeaderEnvelopeFrom]
|
f, ok = m.addrHeader[HeaderEnvelopeFrom]
|
||||||
if !ok || (len(from) == 0 || from == nil) {
|
if !ok || (len(f) == 0 || f == nil) {
|
||||||
hasFrom = false
|
hf = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasFrom && (len(from) > 0 && from[0] != nil) {
|
if hf && (len(f) > 0 && f[0] != nil) {
|
||||||
mw.writeHeader(Header(HeaderFrom), from[0].String())
|
mw.writeHeader(Header(HeaderFrom), f[0].String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the rest of the address headers
|
// Set the rest of the address headers
|
||||||
for _, to := range []AddrHeader{HeaderTo, HeaderCc} {
|
for _, t := range []AddrHeader{HeaderTo, HeaderCc} {
|
||||||
if addresses, ok := msg.addrHeader[to]; ok {
|
if al, ok := m.addrHeader[t]; ok {
|
||||||
var val []string
|
var v []string
|
||||||
for _, addr := range addresses {
|
for _, a := range al {
|
||||||
val = append(val, addr.String())
|
v = append(v, a.String())
|
||||||
}
|
}
|
||||||
mw.writeHeader(Header(to), val...)
|
mw.writeHeader(Header(t), v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.hasMixed() {
|
if m.hasMixed() {
|
||||||
mw.startMP("mixed", msg.boundary)
|
mw.startMP("mixed", m.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
if msg.hasRelated() {
|
if m.hasRelated() {
|
||||||
mw.startMP("related", msg.boundary)
|
mw.startMP("related", m.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
if msg.hasAlt() {
|
if m.hasAlt() {
|
||||||
mw.startMP(MIMEAlternative, msg.boundary)
|
mw.startMP(MIMEAlternative, m.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
if msg.hasPGPType() {
|
if m.hasPGPType() {
|
||||||
switch msg.pgptype {
|
switch m.pgptype {
|
||||||
case PGPEncrypt:
|
case PGPEncrypt:
|
||||||
mw.startMP(`encrypted; protocol="application/pgp-encrypted"`,
|
mw.startMP(`encrypted; protocol="application/pgp-encrypted"`, m.boundary)
|
||||||
msg.boundary)
|
|
||||||
case PGPSignature:
|
case PGPSignature:
|
||||||
mw.startMP(`signed; protocol="application/pgp-signature";`,
|
mw.startMP(`signed; protocol="application/pgp-signature";`, m.boundary)
|
||||||
msg.boundary)
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, part := range msg.parts {
|
for _, p := range m.parts {
|
||||||
if !part.del {
|
if !p.del {
|
||||||
mw.writePart(part, msg.charset)
|
mw.writePart(p, m.charset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.hasAlt() {
|
if m.hasAlt() {
|
||||||
mw.stopMP()
|
mw.stopMP()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add embeds
|
// Add embeds
|
||||||
mw.addFiles(msg.embeds, false)
|
mw.addFiles(m.embeds, false)
|
||||||
if msg.hasRelated() {
|
if m.hasRelated() {
|
||||||
mw.stopMP()
|
mw.stopMP()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add attachments
|
// Add attachments
|
||||||
mw.addFiles(msg.attachments, true)
|
mw.addFiles(m.attachments, true)
|
||||||
if msg.hasMixed() {
|
if m.hasMixed() {
|
||||||
mw.stopMP()
|
mw.stopMP()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeGenHeader writes out all generic headers to the msgWriter
|
// writeGenHeader writes out all generic headers to the msgWriter
|
||||||
func (mw *msgWriter) writeGenHeader(msg *Msg) {
|
func (mw *msgWriter) writeGenHeader(m *Msg) {
|
||||||
keys := make([]string, 0, len(msg.genHeader))
|
gk := make([]string, 0, len(m.genHeader))
|
||||||
for key := range msg.genHeader {
|
for k := range m.genHeader {
|
||||||
keys = append(keys, string(key))
|
gk = append(gk, string(k))
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(gk)
|
||||||
for _, key := range keys {
|
for _, k := range gk {
|
||||||
mw.writeHeader(Header(key), msg.genHeader[Header(key)]...)
|
mw.writeHeader(Header(k), m.genHeader[Header(k)]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePreformatedHeader writes out all preformated generic headers to the msgWriter
|
// writePreformatedHeader writes out all preformated generic headers to the msgWriter
|
||||||
func (mw *msgWriter) writePreformattedGenHeader(msg *Msg) {
|
func (mw *msgWriter) writePreformattedGenHeader(m *Msg) {
|
||||||
for key, val := range msg.preformHeader {
|
for k, v := range m.preformHeader {
|
||||||
mw.writeString(fmt.Sprintf("%s: %s%s", key, val, SingleNewLine))
|
mw.writeString(fmt.Sprintf("%s: %s%s", k, v, SingleNewLine))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startMP writes a multipart beginning
|
// startMP writes a multipart beginning
|
||||||
func (mw *msgWriter) startMP(mimeType MIMEType, boundary string) {
|
func (mw *msgWriter) startMP(mt MIMEType, b string) {
|
||||||
multiPartWriter := multipart.NewWriter(mw)
|
mp := multipart.NewWriter(mw)
|
||||||
if boundary != "" {
|
if b != "" {
|
||||||
mw.err = multiPartWriter.SetBoundary(boundary)
|
mw.err = mp.SetBoundary(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mimeType,
|
ct := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mt, mp.Boundary())
|
||||||
multiPartWriter.Boundary())
|
mw.mpw[mw.d] = mp
|
||||||
mw.multiPartWriter[mw.depth] = multiPartWriter
|
|
||||||
|
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, contentType))
|
mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, ct))
|
||||||
}
|
}
|
||||||
if mw.depth > 0 {
|
if mw.d > 0 {
|
||||||
mw.newPart(map[string][]string{"Content-Type": {contentType}})
|
mw.newPart(map[string][]string{"Content-Type": {ct}})
|
||||||
}
|
}
|
||||||
mw.depth++
|
mw.d++
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopMP closes the multipart
|
// stopMP closes the multipart
|
||||||
func (mw *msgWriter) stopMP() {
|
func (mw *msgWriter) stopMP() {
|
||||||
if mw.depth > 0 {
|
if mw.d > 0 {
|
||||||
mw.err = mw.multiPartWriter[mw.depth-1].Close()
|
mw.err = mw.mpw[mw.d-1].Close()
|
||||||
mw.depth--
|
mw.d--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFiles adds the attachments/embeds file content to the mail body
|
// addFiles adds the attachments/embeds file content to the mail body
|
||||||
func (mw *msgWriter) addFiles(files []*File, isAttachment bool) {
|
func (mw *msgWriter) addFiles(fl []*File, a bool) {
|
||||||
for _, file := range files {
|
for _, f := range fl {
|
||||||
encoding := EncodingB64
|
e := EncodingB64
|
||||||
if _, ok := file.getHeader(HeaderContentType); !ok {
|
if _, ok := f.getHeader(HeaderContentType); !ok {
|
||||||
mimeType := mime.TypeByExtension(filepath.Ext(file.Name))
|
mt := mime.TypeByExtension(filepath.Ext(f.Name))
|
||||||
if mimeType == "" {
|
if mt == "" {
|
||||||
mimeType = "application/octet-stream"
|
mt = "application/octet-stream"
|
||||||
}
|
}
|
||||||
if file.ContentType != "" {
|
if f.ContentType != "" {
|
||||||
mimeType = string(file.ContentType)
|
mt = string(f.ContentType)
|
||||||
}
|
}
|
||||||
file.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mimeType,
|
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt,
|
||||||
mw.encoder.Encode(mw.charset.String(), file.Name)))
|
mw.en.Encode(mw.c.String(), f.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := file.getHeader(HeaderContentTransferEnc); !ok {
|
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok {
|
||||||
if file.Enc != "" {
|
if f.Enc != "" {
|
||||||
encoding = file.Enc
|
e = f.Enc
|
||||||
}
|
}
|
||||||
file.setHeader(HeaderContentTransferEnc, string(encoding))
|
f.setHeader(HeaderContentTransferEnc, string(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.Desc != "" {
|
if f.Desc != "" {
|
||||||
if _, ok := file.getHeader(HeaderContentDescription); !ok {
|
if _, ok := f.getHeader(HeaderContentDescription); !ok {
|
||||||
file.setHeader(HeaderContentDescription, file.Desc)
|
f.setHeader(HeaderContentDescription, f.Desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := file.getHeader(HeaderContentDisposition); !ok {
|
if _, ok := f.getHeader(HeaderContentDisposition); !ok {
|
||||||
disposition := "inline"
|
d := "inline"
|
||||||
if isAttachment {
|
if a {
|
||||||
disposition = "attachment"
|
d = "attachment"
|
||||||
}
|
}
|
||||||
file.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`,
|
f.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, d,
|
||||||
disposition, mw.encoder.Encode(mw.charset.String(), file.Name)))
|
mw.en.Encode(mw.c.String(), f.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isAttachment {
|
if !a {
|
||||||
if _, ok := file.getHeader(HeaderContentID); !ok {
|
if _, ok := f.getHeader(HeaderContentID); !ok {
|
||||||
file.setHeader(HeaderContentID, fmt.Sprintf("<%s>", file.Name))
|
f.setHeader(HeaderContentID, fmt.Sprintf("<%s>", f.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
for header, val := range file.Header {
|
for h, v := range f.Header {
|
||||||
mw.writeHeader(Header(header), val...)
|
mw.writeHeader(Header(h), v...)
|
||||||
}
|
}
|
||||||
mw.writeString(SingleNewLine)
|
mw.writeString(SingleNewLine)
|
||||||
}
|
}
|
||||||
if mw.depth > 0 {
|
if mw.d > 0 {
|
||||||
mw.newPart(file.Header)
|
mw.newPart(f.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mw.err == nil {
|
if mw.err == nil {
|
||||||
mw.writeBody(file.Writer, encoding)
|
mw.writeBody(f.Writer, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it
|
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it
|
||||||
func (mw *msgWriter) newPart(header map[string][]string) {
|
func (mw *msgWriter) newPart(h map[string][]string) {
|
||||||
mw.partWriter, mw.err = mw.multiPartWriter[mw.depth-1].CreatePart(header)
|
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePart writes the corresponding part to the Msg body
|
// writePart writes the corresponding part to the Msg body
|
||||||
func (mw *msgWriter) writePart(part *Part, charset Charset) {
|
func (mw *msgWriter) writePart(p *Part, cs Charset) {
|
||||||
partCharset := part.cset
|
pcs := p.cset
|
||||||
if partCharset.String() == "" {
|
if pcs.String() == "" {
|
||||||
partCharset = charset
|
pcs = cs
|
||||||
}
|
}
|
||||||
contentType := fmt.Sprintf("%s; charset=%s", part.ctype, partCharset)
|
ct := fmt.Sprintf("%s; charset=%s", p.ctype, pcs)
|
||||||
contentTransferEnc := part.enc.String()
|
cte := p.enc.String()
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
mw.writeHeader(HeaderContentType, contentType)
|
mw.writeHeader(HeaderContentType, ct)
|
||||||
mw.writeHeader(HeaderContentTransferEnc, contentTransferEnc)
|
mw.writeHeader(HeaderContentTransferEnc, cte)
|
||||||
mw.writeString(SingleNewLine)
|
mw.writeString(SingleNewLine)
|
||||||
}
|
}
|
||||||
if mw.depth > 0 {
|
if mw.d > 0 {
|
||||||
mimeHeader := textproto.MIMEHeader{}
|
mh := textproto.MIMEHeader{}
|
||||||
if part.desc != "" {
|
if p.desc != "" {
|
||||||
mimeHeader.Add(string(HeaderContentDescription), part.desc)
|
mh.Add(string(HeaderContentDescription), p.desc)
|
||||||
}
|
}
|
||||||
mimeHeader.Add(string(HeaderContentType), contentType)
|
mh.Add(string(HeaderContentType), ct)
|
||||||
mimeHeader.Add(string(HeaderContentTransferEnc), contentTransferEnc)
|
mh.Add(string(HeaderContentTransferEnc), cte)
|
||||||
mw.newPart(mimeHeader)
|
mw.newPart(mh)
|
||||||
}
|
}
|
||||||
mw.writeBody(part.w, part.enc)
|
mw.writeBody(p.w, p.enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeString writes a string into the msgWriter's io.Writer interface
|
// writeString writes a string into the msgWriter's io.Writer interface
|
||||||
|
@ -278,103 +274,102 @@ func (mw *msgWriter) writeString(s string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var n int
|
var n int
|
||||||
n, mw.err = io.WriteString(mw.writer, s)
|
n, mw.err = io.WriteString(mw.w, s)
|
||||||
mw.bytesWritten += int64(n)
|
mw.n += int64(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHeader writes a header into the msgWriter's io.Writer
|
// writeHeader writes a header into the msgWriter's io.Writer
|
||||||
func (mw *msgWriter) writeHeader(key Header, values ...string) {
|
func (mw *msgWriter) writeHeader(k Header, vl ...string) {
|
||||||
buffer := strings.Builder{}
|
wbuf := bytes.Buffer{}
|
||||||
charLength := MaxHeaderLength - 2
|
cl := MaxHeaderLength - 2
|
||||||
buffer.WriteString(string(key))
|
wbuf.WriteString(string(k))
|
||||||
charLength -= len(key)
|
cl -= len(k)
|
||||||
if len(values) == 0 {
|
if len(vl) == 0 {
|
||||||
buffer.WriteString(":\r\n")
|
wbuf.WriteString(":\r\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer.WriteString(": ")
|
wbuf.WriteString(": ")
|
||||||
charLength -= 2
|
cl -= 2
|
||||||
|
|
||||||
fullValueStr := strings.Join(values, ", ")
|
fs := strings.Join(vl, ", ")
|
||||||
words := strings.Split(fullValueStr, " ")
|
sfs := strings.Split(fs, " ")
|
||||||
for i, val := range words {
|
for i, v := range sfs {
|
||||||
if charLength-len(val) <= 1 {
|
if cl-len(v) <= 1 {
|
||||||
buffer.WriteString(fmt.Sprintf("%s ", SingleNewLine))
|
wbuf.WriteString(fmt.Sprintf("%s ", SingleNewLine))
|
||||||
charLength = MaxHeaderLength - 3
|
cl = MaxHeaderLength - 3
|
||||||
}
|
}
|
||||||
buffer.WriteString(val)
|
wbuf.WriteString(v)
|
||||||
if i < len(words)-1 {
|
if i < len(sfs)-1 {
|
||||||
buffer.WriteString(" ")
|
wbuf.WriteString(" ")
|
||||||
charLength -= 1
|
cl -= 1
|
||||||
}
|
}
|
||||||
charLength -= len(val)
|
cl -= len(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferString := buffer.String()
|
bufs := wbuf.String()
|
||||||
bufferString = strings.ReplaceAll(bufferString, fmt.Sprintf(" %s", SingleNewLine),
|
bufs = strings.ReplaceAll(bufs, fmt.Sprintf(" %s", SingleNewLine), SingleNewLine)
|
||||||
SingleNewLine)
|
mw.writeString(bufs)
|
||||||
mw.writeString(bufferString)
|
|
||||||
mw.writeString("\r\n")
|
mw.writeString("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeBody writes an io.Reader into an io.Writer using provided Encoding
|
// writeBody writes an io.Reader into an io.Writer using provided Encoding
|
||||||
func (mw *msgWriter) writeBody(writeFunc func(io.Writer) (int64, error), encoding Encoding) {
|
func (mw *msgWriter) writeBody(f func(io.Writer) (int64, error), e Encoding) {
|
||||||
var writer io.Writer
|
var w io.Writer
|
||||||
var encodedWriter io.WriteCloser
|
var ew io.WriteCloser
|
||||||
var n int64
|
var n int64
|
||||||
var err error
|
var err error
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
writer = mw.writer
|
w = mw.w
|
||||||
}
|
}
|
||||||
if mw.depth > 0 {
|
if mw.d > 0 {
|
||||||
writer = mw.partWriter
|
w = mw.pw
|
||||||
}
|
}
|
||||||
writeBuffer := bytes.Buffer{}
|
wbuf := bytes.Buffer{}
|
||||||
lineBreaker := Base64LineBreaker{}
|
lb := Base64LineBreaker{}
|
||||||
lineBreaker.out = &writeBuffer
|
lb.out = &wbuf
|
||||||
|
|
||||||
switch encoding {
|
switch e {
|
||||||
case EncodingQP:
|
case EncodingQP:
|
||||||
encodedWriter = quotedprintable.NewWriter(&writeBuffer)
|
ew = quotedprintable.NewWriter(&wbuf)
|
||||||
case EncodingB64:
|
case EncodingB64:
|
||||||
encodedWriter = base64.NewEncoder(base64.StdEncoding, &lineBreaker)
|
ew = base64.NewEncoder(base64.StdEncoding, &lb)
|
||||||
case NoEncoding:
|
case NoEncoding:
|
||||||
_, err = writeFunc(&writeBuffer)
|
_, err = f(&wbuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter function: %w", err)
|
mw.err = fmt.Errorf("bodyWriter function: %w", err)
|
||||||
}
|
}
|
||||||
n, err = io.Copy(writer, &writeBuffer)
|
n, err = io.Copy(w, &wbuf)
|
||||||
if err != nil && mw.err == nil {
|
if err != nil && mw.err == nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err)
|
mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err)
|
||||||
}
|
}
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
mw.bytesWritten += n
|
mw.n += n
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
encodedWriter = quotedprintable.NewWriter(writer)
|
ew = quotedprintable.NewWriter(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = writeFunc(encodedWriter)
|
_, err = f(ew)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter function: %w", err)
|
mw.err = fmt.Errorf("bodyWriter function: %w", err)
|
||||||
}
|
}
|
||||||
err = encodedWriter.Close()
|
err = ew.Close()
|
||||||
if err != nil && mw.err == nil {
|
if err != nil && mw.err == nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter close encoded writer: %w", err)
|
mw.err = fmt.Errorf("bodyWriter close encoded writer: %w", err)
|
||||||
}
|
}
|
||||||
err = lineBreaker.Close()
|
err = lb.Close()
|
||||||
if err != nil && mw.err == nil {
|
if err != nil && mw.err == nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter close linebreaker: %w", err)
|
mw.err = fmt.Errorf("bodyWriter close linebreaker: %w", err)
|
||||||
}
|
}
|
||||||
n, err = io.Copy(writer, &writeBuffer)
|
n, err = io.Copy(w, &wbuf)
|
||||||
if err != nil && mw.err == nil {
|
if err != nil && mw.err == nil {
|
||||||
mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err)
|
mw.err = fmt.Errorf("bodyWriter io.Copy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the part writer uses the WriteTo() method, we don't need to add the
|
// Since the part writer uses the WriteTo() method, we don't need to add the
|
||||||
// bytes twice
|
// bytes twice
|
||||||
if mw.depth == 0 {
|
if mw.d == 0 {
|
||||||
mw.bytesWritten += n
|
mw.n += n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (bw *brokenWriter) Write([]byte) (int, error) {
|
||||||
// TestMsgWriter_Write tests the WriteTo() method of the msgWriter
|
// TestMsgWriter_Write tests the WriteTo() method of the msgWriter
|
||||||
func TestMsgWriter_Write(t *testing.T) {
|
func TestMsgWriter_Write(t *testing.T) {
|
||||||
bw := &brokenWriter{}
|
bw := &brokenWriter{}
|
||||||
mw := &msgWriter{writer: bw, charset: CharsetUTF8, encoder: mime.QEncoding}
|
mw := &msgWriter{w: bw, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
_, err := mw.Write([]byte("test"))
|
_, err := mw.Write([]byte("test"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't")
|
t.Errorf("msgWriter WriteTo() with brokenWriter should fail, but didn't")
|
||||||
|
@ -55,7 +55,7 @@ func TestMsgWriter_writeMsg(t *testing.T) {
|
||||||
m.SetBodyString(TypeTextPlain, "This is the body")
|
m.SetBodyString(TypeTextPlain, "This is the body")
|
||||||
m.AddAlternativeString(TypeTextHTML, "This is the alternative body")
|
m.AddAlternativeString(TypeTextHTML, "This is the alternative body")
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
|
mw := &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
mw.writeMsg(m)
|
mw.writeMsg(m)
|
||||||
ms := buf.String()
|
ms := buf.String()
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) {
|
||||||
m.Subject("This is a subject")
|
m.Subject("This is a subject")
|
||||||
m.SetBodyString(TypeTextPlain, "This is the body")
|
m.SetBodyString(TypeTextPlain, "This is the body")
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
|
mw := &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
mw.writeMsg(m)
|
mw.writeMsg(m)
|
||||||
ms := buf.String()
|
ms := buf.String()
|
||||||
if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) {
|
if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) {
|
||||||
|
@ -147,7 +147,7 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) {
|
||||||
m.Subject("This is a subject")
|
m.Subject("This is a subject")
|
||||||
m.SetBodyString(TypeTextPlain, "This is the body")
|
m.SetBodyString(TypeTextPlain, "This is the body")
|
||||||
buf = bytes.Buffer{}
|
buf = bytes.Buffer{}
|
||||||
mw = &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: mime.QEncoding}
|
mw = &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
mw.writeMsg(m)
|
mw.writeMsg(m)
|
||||||
ms = buf.String()
|
ms = buf.String()
|
||||||
if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) {
|
if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) {
|
||||||
|
|
Loading…
Reference in a new issue