Compare commits

..

3 commits

Author SHA1 Message Date
4e880ab31c
Refactor variable and function names for improved clarity in msg.go
The change updates various variable and function names in msg.go to make the code more intuitive. Updated names better capture what they represent or do, improving code readability and maintainability. This refactor does not impact functionality or logic of the code.
2024-02-24 18:26:30 +01:00
c126670f70
Refactor variable and function names in msgWriter
The commit includes changes such as renaming the variables `wbuf` to `buffer`, `cl` to `charLength` and functions `f` to `writeFunc` in msgWriter. This refactoring makes the code easier to read and understand. All the changes are aimed at enhancing code clarity without altering underlying logic or functionality.
2024-02-24 17:38:42 +01:00
263f6bb3de
Refactor SMTP client code for better readability
The variable names in the code related to the I/O of the SMTP client have been clarified for improved readability and comprehension. For example, unclear variable names like `d` and `w` have been replaced with more meaningful names like `depth` and `writer`. The same naming improvements have also been applied to function parameters. This update aims to enhance code maintenance and simplify future development processes.
2024-02-24 12:43:01 +01:00
3 changed files with 355 additions and 349 deletions

345
msg.go
View file

@ -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(o ...MsgOption) *Msg { func NewMsg(opts ...MsgOption) *Msg {
m := &Msg{ msg := &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(o ...MsgOption) *Msg {
} }
// Override defaults with optionally provided MsgOption functions // Override defaults with optionally provided MsgOption functions
for _, co := range o { for _, option := range opts {
if co == nil { if option == nil {
continue continue
} }
co(m) option(msg)
} }
// Set the matcing mime.WordEncoder for the Msg // Set the matcing mime.WordEncoder for the Msg
m.setEncoder() msg.setEncoder()
return m return msg
} }
// 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(t PGPType) MsgOption { func WithPGPType(pt PGPType) MsgOption {
return func(m *Msg) { return func(m *Msg) {
m.pgptype = t m.pgptype = pt
} }
} }
@ -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(h Header, v ...string) { func (m *Msg) SetHeader(header Header, values ...string) {
m.SetGenHeader(h, v...) m.SetGenHeader(header, values...)
} }
// 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(h Header, v ...string) { func (m *Msg) SetGenHeader(header Header, values ...string) {
if m.genHeader == nil { if m.genHeader == nil {
m.genHeader = make(map[Header][]string) m.genHeader = make(map[Header][]string)
} }
for i, hv := range v { for i, val := range values {
v[i] = m.encodeString(hv) values[i] = m.encodeString(val)
} }
m.genHeader[h] = v m.genHeader[header] = values
} }
// 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(h Header, v ...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(h Header, v string) { func (m *Msg) SetHeaderPreformatted(header Header, value string) {
m.SetGenHeaderPreformatted(h, v) m.SetGenHeaderPreformatted(header, value)
} }
// 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,206 +268,207 @@ func (m *Msg) SetHeaderPreformatted(h Header, v 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(h Header, v string) { func (m *Msg) SetGenHeaderPreformatted(header Header, value string) {
if m.preformHeader == nil { if m.preformHeader == nil {
m.preformHeader = make(map[Header]string) m.preformHeader = make(map[Header]string)
} }
m.preformHeader[h] = v m.preformHeader[header] = value
} }
// SetAddrHeader sets an address related header field of the Msg // SetAddrHeader sets an address related header field of the Msg
func (m *Msg) SetAddrHeader(h AddrHeader, v ...string) error { func (m *Msg) SetAddrHeader(header AddrHeader, values ...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 al []*mail.Address var addresses []*mail.Address
for _, av := range v { for _, addrVal := range values {
a, err := mail.ParseAddress(av) address, err := mail.ParseAddress(addrVal)
if err != nil { if err != nil {
return fmt.Errorf(errParseMailAddr, av, err) return fmt.Errorf(errParseMailAddr, addrVal, err)
} }
al = append(al, a) addresses = append(addresses, address)
} }
switch h { switch header {
case HeaderFrom: case HeaderFrom:
if len(al) > 0 { if len(addresses) > 0 {
m.addrHeader[h] = []*mail.Address{al[0]} m.addrHeader[header] = []*mail.Address{addresses[0]}
} }
default: default:
m.addrHeader[h] = al m.addrHeader[header] = addresses
} }
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(h AddrHeader, v ...string) { func (m *Msg) SetAddrHeaderIgnoreInvalid(header AddrHeader, values ...string) {
var al []*mail.Address var addresses []*mail.Address
for _, av := range v { for _, addrVal := range values {
a, err := mail.ParseAddress(m.encodeString(av)) address, err := mail.ParseAddress(m.encodeString(addrVal))
if err != nil { if err != nil {
continue continue
} }
al = append(al, a) addresses = append(addresses, address)
} }
m.addrHeader[h] = al m.addrHeader[header] = addresses
} }
// 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(f string) error { func (m *Msg) EnvelopeFrom(from string) error {
return m.SetAddrHeader(HeaderEnvelopeFrom, f) return m.SetAddrHeader(HeaderEnvelopeFrom, from)
} }
// 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(n, a string) error { func (m *Msg) EnvelopeFromFormat(name, addr string) error {
return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, n, a)) return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(f string) error { func (m *Msg) From(from string) error {
return m.SetAddrHeader(HeaderFrom, f) return m.SetAddrHeader(HeaderFrom, from)
} }
// 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(n, a string) error { func (m *Msg) FromFormat(name, addr string) error {
return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, n, a)) return m.SetAddrHeader(HeaderFrom, fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(t ...string) error { func (m *Msg) To(rcpts ...string) error {
return m.SetAddrHeader(HeaderTo, t...) return m.SetAddrHeader(HeaderTo, rcpts...)
} }
// 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(t string) error { func (m *Msg) AddTo(rcpt string) error {
return m.addAddr(HeaderTo, t) return m.addAddr(HeaderTo, rcpt)
} }
// 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(n, a string) error { func (m *Msg) AddToFormat(name, addr string) error {
return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, n, a)) return m.addAddr(HeaderTo, fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(t ...string) { func (m *Msg) ToIgnoreInvalid(rcpts ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderTo, t...) m.SetAddrHeaderIgnoreInvalid(HeaderTo, rcpts...)
} }
// 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(v string) error { func (m *Msg) ToFromString(rcpts string) error {
return m.To(strings.Split(v, ",")...) return m.To(strings.Split(rcpts, ",")...)
} }
// 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(c ...string) error { func (m *Msg) Cc(rcpts ...string) error {
return m.SetAddrHeader(HeaderCc, c...) return m.SetAddrHeader(HeaderCc, rcpts...)
} }
// 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(t string) error { func (m *Msg) AddCc(rcpt string) error {
return m.addAddr(HeaderCc, t) return m.addAddr(HeaderCc, rcpt)
} }
// 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(n, a string) error { func (m *Msg) AddCcFormat(name, addr string) error {
return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, n, a)) return m.addAddr(HeaderCc, fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(c ...string) { func (m *Msg) CcIgnoreInvalid(rcpts ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderCc, c...) m.SetAddrHeaderIgnoreInvalid(HeaderCc, rcpts...)
} }
// 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(v string) error { func (m *Msg) CcFromString(rcpts string) error {
return m.Cc(strings.Split(v, ",")...) return m.Cc(strings.Split(rcpts, ",")...)
} }
// 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(b ...string) error { func (m *Msg) Bcc(rcpts ...string) error {
return m.SetAddrHeader(HeaderBcc, b...) return m.SetAddrHeader(HeaderBcc, rcpts...)
} }
// 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(t string) error { func (m *Msg) AddBcc(rcpt string) error {
return m.addAddr(HeaderBcc, t) return m.addAddr(HeaderBcc, rcpt)
} }
// 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(n, a string) error { func (m *Msg) AddBccFormat(name, addr string) error {
return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, n, a)) return m.addAddr(HeaderBcc, fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(b ...string) { func (m *Msg) BccIgnoreInvalid(rcpts ...string) {
m.SetAddrHeaderIgnoreInvalid(HeaderBcc, b...) m.SetAddrHeaderIgnoreInvalid(HeaderBcc, rcpts...)
} }
// 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(v string) error { func (m *Msg) BccFromString(rcpts string) error {
return m.Bcc(strings.Split(v, ",")...) return m.Bcc(strings.Split(rcpts, ",")...)
} }
// 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(r string) error { func (m *Msg) ReplyTo(addr string) error {
rt, err := mail.ParseAddress(r) replyTo, err := mail.ParseAddress(addr)
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, rt.String()) m.SetGenHeader(HeaderReplyTo, replyTo.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(n, a string) error { func (m *Msg) ReplyToFormat(name, addr string) error {
return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, n, a)) return m.ReplyTo(fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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(h AddrHeader, a string) error { func (m *Msg) addAddr(header AddrHeader, addr string) error {
var al []string var addresses []string
for _, ca := range m.addrHeader[h] { for _, address := range m.addrHeader[header] {
al = append(al, ca.String()) addresses = append(addresses, address.String())
} }
al = append(al, a) addresses = append(addresses, addr)
return m.SetAddrHeader(h, al...) return m.SetAddrHeader(header, addresses...)
} }
// Subject sets the "Subject" header field of the Msg // Subject sets the "Subject" header field of the Msg
func (m *Msg) Subject(s string) { func (m *Msg) Subject(subj string) {
m.SetGenHeader(HeaderSubject, s) m.SetGenHeader(HeaderSubject, subj)
} }
// 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() {
hn, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
hn = "localhost.localdomain" hostname = "localhost.localdomain"
} }
rn, _ := randNum(100000000) randNumPrimary, _ := randNum(100000000)
rm, _ := randNum(10000) randNumSecondary, _ := randNum(10000)
rs, _ := randomStringSecure(17) randString, _ := randomStringSecure(17)
pid := os.Getpid() * rm procID := os.Getpid() * randNumSecondary
mid := fmt.Sprintf("%d.%d%d.%s@%s", pid, rn, rm, rs, hn) messageID := fmt.Sprintf("%d.%d%d.%s@%s", procID, randNumPrimary, randNumSecondary,
m.SetMessageIDWithValue(mid) randString, hostname)
m.SetMessageIDWithValue(messageID)
} }
// SetMessageIDWithValue sets the message id for the mail // SetMessageIDWithValue sets the message id for the mail
func (m *Msg) SetMessageIDWithValue(v string) { func (m *Msg) SetMessageIDWithValue(messageID string) {
m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", v)) m.SetGenHeader(HeaderMessageID, fmt.Sprintf("<%s>", messageID))
} }
// 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
@ -481,35 +482,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() {
ts := time.Now().Format(time.RFC1123Z) now := time.Now().Format(time.RFC1123Z)
m.SetGenHeader(HeaderDate, ts) m.SetGenHeader(HeaderDate, now)
} }
// 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(t time.Time) { func (m *Msg) SetDateWithValue(timeVal time.Time) {
m.SetGenHeader(HeaderDate, t.Format(time.RFC1123Z)) m.SetGenHeader(HeaderDate, timeVal.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(i Importance) { func (m *Msg) SetImportance(importance Importance) {
if i == ImportanceNormal { if importance == ImportanceNormal {
return return
} }
m.SetGenHeader(HeaderImportance, i.String()) m.SetGenHeader(HeaderImportance, importance.String())
m.SetGenHeader(HeaderPriority, i.NumString()) m.SetGenHeader(HeaderPriority, importance.NumString())
m.SetGenHeader(HeaderXPriority, i.XPrioString()) m.SetGenHeader(HeaderXPriority, importance.XPrioString())
m.SetGenHeader(HeaderXMSMailPriority, i.NumString()) m.SetGenHeader(HeaderXMSMailPriority, importance.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(o string) { func (m *Msg) SetOrganization(org string) {
m.SetGenHeader(HeaderOrganization, o) m.SetGenHeader(HeaderOrganization, org)
} }
// 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(a string) { func (m *Msg) SetUserAgent(userAgent string) {
m.SetGenHeader(HeaderUserAgent, a) m.SetGenHeader(HeaderUserAgent, userAgent)
m.SetGenHeader(HeaderXMailer, a) m.SetGenHeader(HeaderXMailer, userAgent)
} }
// IsDelivered will return true if the Msg has been successfully delivered // IsDelivered will return true if the Msg has been successfully delivered
@ -521,17 +522,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(t ...string) error { func (m *Msg) RequestMDNTo(rcpts ...string) error {
var tl []string var addresses []string
for _, at := range t { for _, addrVal := range rcpts {
a, err := mail.ParseAddress(at) address, err := mail.ParseAddress(addrVal)
if err != nil { if err != nil {
return fmt.Errorf(errParseMailAddr, at, err) return fmt.Errorf(errParseMailAddr, addrVal, err)
} }
tl = append(tl, a.String()) addresses = append(addresses, address.String())
} }
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok { if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
m.genHeader[HeaderDispositionNotificationTo] = tl m.genHeader[HeaderDispositionNotificationTo] = addresses
} }
return nil return nil
} }
@ -540,77 +541,77 @@ func (m *Msg) RequestMDNTo(t ...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(n, a string) error { func (m *Msg) RequestMDNToFormat(name, addr string) error {
return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, n, a)) return m.RequestMDNTo(fmt.Sprintf(`%s <%s>`, name, addr))
} }
// 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(t string) error { func (m *Msg) RequestMDNAddTo(rcpt string) error {
a, err := mail.ParseAddress(t) address, err := mail.ParseAddress(rcpt)
if err != nil { if err != nil {
return fmt.Errorf(errParseMailAddr, t, err) return fmt.Errorf(errParseMailAddr, rcpt, err)
} }
var tl []string var addresses []string
tl = append(tl, m.genHeader[HeaderDispositionNotificationTo]...) addresses = append(addresses, m.genHeader[HeaderDispositionNotificationTo]...)
tl = append(tl, a.String()) addresses = append(addresses, address.String())
if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok { if _, ok := m.genHeader[HeaderDispositionNotificationTo]; ok {
m.genHeader[HeaderDispositionNotificationTo] = tl m.genHeader[HeaderDispositionNotificationTo] = addresses
} }
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(n, a string) error { func (m *Msg) RequestMDNAddToFormat(name, addr string) error {
return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, n, a)) return m.RequestMDNAddTo(fmt.Sprintf(`"%s" <%s>`, name, addr))
} }
// 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 ff is true, it will return the full address string including // the first mail body FROM address. If useFullAddr is true, it will return the full address string
// the address name, if set // including the address name, if set
func (m *Msg) GetSender(ff bool) (string, error) { func (m *Msg) GetSender(useFullAddr bool) (string, error) {
f, ok := m.addrHeader[HeaderEnvelopeFrom] from, ok := m.addrHeader[HeaderEnvelopeFrom]
if !ok || len(f) == 0 { if !ok || len(from) == 0 {
f, ok = m.addrHeader[HeaderFrom] from, ok = m.addrHeader[HeaderFrom]
if !ok || len(f) == 0 { if !ok || len(from) == 0 {
return "", ErrNoFromAddress return "", ErrNoFromAddress
} }
} }
if ff { if useFullAddr {
return f[0].String(), nil return from[0].String(), nil
} }
return f[0].Address, nil return from[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 rl []string var rcpts []string
for _, t := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} { for _, addressType := range []AddrHeader{HeaderTo, HeaderCc, HeaderBcc} {
al, ok := m.addrHeader[t] addresses, ok := m.addrHeader[addressType]
if !ok || len(al) == 0 { if !ok || len(addresses) == 0 {
continue continue
} }
for _, r := range al { for _, r := range addresses {
rl = append(rl, r.Address) rcpts = append(rcpts, r.Address)
} }
} }
if len(rl) <= 0 { if len(rcpts) <= 0 {
return rl, ErrNoRcptAddresses return rcpts, ErrNoRcptAddresses
} }
return rl, nil return rcpts, 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(h AddrHeader) []*mail.Address { func (m *Msg) GetAddrHeader(header AddrHeader) []*mail.Address {
return m.addrHeader[h] return m.addrHeader[header]
} }
// 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(h AddrHeader) []string { func (m *Msg) GetAddrHeaderString(header AddrHeader) []string {
var al []string var addresses []string
for _, mh := range m.addrHeader[h] { for _, mh := range m.addrHeader[header] {
al = append(al, mh.String()) addresses = append(addresses, mh.String())
} }
return al return addresses
} }
// GetFrom returns the content of the From address header of the Msg // GetFrom returns the content of the From address header of the Msg
@ -654,8 +655,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(h Header) []string { func (m *Msg) GetGenHeader(header Header) []string {
return m.genHeader[h] return m.genHeader[header]
} }
// GetParts returns the message parts of the Msg // GetParts returns the message parts of the Msg
@ -669,8 +670,8 @@ func (m *Msg) GetAttachments() []*File {
} }
// SetAttachements sets the attachements of the message. // SetAttachements sets the attachements of the message.
func (m *Msg) SetAttachements(ff []*File) { func (m *Msg) SetAttachements(files []*File) {
m.attachments = ff m.attachments = files
} }
// UnsetAllAttachments unset the attachments of the message. // UnsetAllAttachments unset the attachments of the message.
@ -684,8 +685,8 @@ func (m *Msg) GetEmbeds() []*File {
} }
// SetEmbeds sets the embeds of the message. // SetEmbeds sets the embeds of the message.
func (m *Msg) SetEmbeds(ff []*File) { func (m *Msg) SetEmbeds(files []*File) {
m.embeds = ff m.embeds = files
} }
// UnsetAllEmbeds unset the embeds of the message. // UnsetAllEmbeds unset the embeds of the message.
@ -700,16 +701,16 @@ func (m *Msg) UnsetAllParts() {
} }
// SetBodyString sets the body of the message. // SetBodyString sets the body of the message.
func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) { func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...PartOption) {
buf := bytes.NewBufferString(b) buffer := bytes.NewBufferString(content)
w := writeFuncFromBuffer(buf) writeFunc := writeFuncFromBuffer(buffer)
m.SetBodyWriter(ct, w, o...) m.SetBodyWriter(contentType, writeFunc, opts...)
} }
// SetBodyWriter sets the body of the message. // SetBodyWriter sets the body of the message.
func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) { func (m *Msg) SetBodyWriter(contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption) {
p := m.newPart(ct, o...) p := m.newPart(contentType, opts...)
p.w = w p.w = writeFunc
m.parts = []*Part{p} m.parts = []*Part{p}
} }
@ -933,9 +934,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{w: w, c: m.charset, en: m.encoder} mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
mw.writeMsg(m.applyMiddlewares(m)) mw.writeMsg(m.applyMiddlewares(m))
return mw.n, mw.err return mw.bytesWritten, 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
@ -950,10 +951,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{w: w, c: m.charset, en: m.encoder} mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
mw.writeMsg(m.applyMiddlewares(m)) mw.writeMsg(m.applyMiddlewares(m))
m.middlewares = omwl m.middlewares = omwl
return mw.n, mw.err return mw.bytesWritten, mw.err
} }
// Write is an alias method to WriteTo due to compatibility reasons // Write is an alias method to WriteTo due to compatibility reasons

View file

@ -35,237 +35,241 @@ 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 {
c Charset bytesWritten int64
d int8 charset Charset
en mime.WordEncoder depth int8
err error encoder mime.WordEncoder
mpw [3]*multipart.Writer err error
n int64 multiPartWriter [3]*multipart.Writer
pw io.Writer partWriter io.Writer
w io.Writer writer io.Writer
} }
// Write implements the io.Writer interface for msgWriter // Write implements the io.Writer interface for msgWriter
func (mw *msgWriter) Write(p []byte) (int, error) { func (mw *msgWriter) Write(payload []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.w.Write(p) n, mw.err = mw.writer.Write(payload)
mw.n += int64(n) mw.bytesWritten += 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(m *Msg) { func (mw *msgWriter) writeMsg(msg *Msg) {
m.addDefaultHeader() msg.addDefaultHeader()
m.checkUserAgent() msg.checkUserAgent()
mw.writeGenHeader(m) mw.writeGenHeader(msg)
mw.writePreformattedGenHeader(m) mw.writePreformattedGenHeader(msg)
// Set the FROM header (or envelope FROM if FROM is empty) // Set the FROM header (or envelope FROM if FROM is empty)
hf := true hasFrom := true
f, ok := m.addrHeader[HeaderFrom] from, ok := msg.addrHeader[HeaderFrom]
if !ok || (len(f) == 0 || f == nil) { if !ok || (len(from) == 0 || from == nil) {
f, ok = m.addrHeader[HeaderEnvelopeFrom] from, ok = msg.addrHeader[HeaderEnvelopeFrom]
if !ok || (len(f) == 0 || f == nil) { if !ok || (len(from) == 0 || from == nil) {
hf = false hasFrom = false
} }
} }
if hf && (len(f) > 0 && f[0] != nil) { if hasFrom && (len(from) > 0 && from[0] != nil) {
mw.writeHeader(Header(HeaderFrom), f[0].String()) mw.writeHeader(Header(HeaderFrom), from[0].String())
} }
// Set the rest of the address headers // Set the rest of the address headers
for _, t := range []AddrHeader{HeaderTo, HeaderCc} { for _, to := range []AddrHeader{HeaderTo, HeaderCc} {
if al, ok := m.addrHeader[t]; ok { if addresses, ok := msg.addrHeader[to]; ok {
var v []string var val []string
for _, a := range al { for _, addr := range addresses {
v = append(v, a.String()) val = append(val, addr.String())
} }
mw.writeHeader(Header(t), v...) mw.writeHeader(Header(to), val...)
} }
} }
if m.hasMixed() { if msg.hasMixed() {
mw.startMP("mixed", m.boundary) mw.startMP("mixed", msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasRelated() { if msg.hasRelated() {
mw.startMP("related", m.boundary) mw.startMP("related", msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasAlt() { if msg.hasAlt() {
mw.startMP(MIMEAlternative, m.boundary) mw.startMP(MIMEAlternative, msg.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasPGPType() { if msg.hasPGPType() {
switch m.pgptype { switch msg.pgptype {
case PGPEncrypt: case PGPEncrypt:
mw.startMP(`encrypted; protocol="application/pgp-encrypted"`, m.boundary) mw.startMP(`encrypted; protocol="application/pgp-encrypted"`,
msg.boundary)
case PGPSignature: case PGPSignature:
mw.startMP(`signed; protocol="application/pgp-signature";`, m.boundary) mw.startMP(`signed; protocol="application/pgp-signature";`,
msg.boundary)
default:
} }
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
for _, p := range m.parts { for _, part := range msg.parts {
if !p.del { if !part.del {
mw.writePart(p, m.charset) mw.writePart(part, msg.charset)
} }
} }
if m.hasAlt() { if msg.hasAlt() {
mw.stopMP() mw.stopMP()
} }
// Add embeds // Add embeds
mw.addFiles(m.embeds, false) mw.addFiles(msg.embeds, false)
if m.hasRelated() { if msg.hasRelated() {
mw.stopMP() mw.stopMP()
} }
// Add attachments // Add attachments
mw.addFiles(m.attachments, true) mw.addFiles(msg.attachments, true)
if m.hasMixed() { if msg.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(m *Msg) { func (mw *msgWriter) writeGenHeader(msg *Msg) {
gk := make([]string, 0, len(m.genHeader)) keys := make([]string, 0, len(msg.genHeader))
for k := range m.genHeader { for key := range msg.genHeader {
gk = append(gk, string(k)) keys = append(keys, string(key))
} }
sort.Strings(gk) sort.Strings(keys)
for _, k := range gk { for _, key := range keys {
mw.writeHeader(Header(k), m.genHeader[Header(k)]...) mw.writeHeader(Header(key), msg.genHeader[Header(key)]...)
} }
} }
// writePreformatedHeader writes out all preformated generic headers to the msgWriter // writePreformatedHeader writes out all preformated generic headers to the msgWriter
func (mw *msgWriter) writePreformattedGenHeader(m *Msg) { func (mw *msgWriter) writePreformattedGenHeader(msg *Msg) {
for k, v := range m.preformHeader { for key, val := range msg.preformHeader {
mw.writeString(fmt.Sprintf("%s: %s%s", k, v, SingleNewLine)) mw.writeString(fmt.Sprintf("%s: %s%s", key, val, SingleNewLine))
} }
} }
// startMP writes a multipart beginning // startMP writes a multipart beginning
func (mw *msgWriter) startMP(mt MIMEType, b string) { func (mw *msgWriter) startMP(mimeType MIMEType, boundary string) {
mp := multipart.NewWriter(mw) multiPartWriter := multipart.NewWriter(mw)
if b != "" { if boundary != "" {
mw.err = mp.SetBoundary(b) mw.err = multiPartWriter.SetBoundary(boundary)
} }
ct := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mt, mp.Boundary()) contentType := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mimeType,
mw.mpw[mw.d] = mp multiPartWriter.Boundary())
mw.multiPartWriter[mw.depth] = multiPartWriter
if mw.d == 0 { if mw.depth == 0 {
mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, ct)) mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, contentType))
} }
if mw.d > 0 { if mw.depth > 0 {
mw.newPart(map[string][]string{"Content-Type": {ct}}) mw.newPart(map[string][]string{"Content-Type": {contentType}})
} }
mw.d++ mw.depth++
} }
// stopMP closes the multipart // stopMP closes the multipart
func (mw *msgWriter) stopMP() { func (mw *msgWriter) stopMP() {
if mw.d > 0 { if mw.depth > 0 {
mw.err = mw.mpw[mw.d-1].Close() mw.err = mw.multiPartWriter[mw.depth-1].Close()
mw.d-- mw.depth--
} }
} }
// 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(fl []*File, a bool) { func (mw *msgWriter) addFiles(files []*File, isAttachment bool) {
for _, f := range fl { for _, file := range files {
e := EncodingB64 encoding := EncodingB64
if _, ok := f.getHeader(HeaderContentType); !ok { if _, ok := file.getHeader(HeaderContentType); !ok {
mt := mime.TypeByExtension(filepath.Ext(f.Name)) mimeType := mime.TypeByExtension(filepath.Ext(file.Name))
if mt == "" { if mimeType == "" {
mt = "application/octet-stream" mimeType = "application/octet-stream"
} }
if f.ContentType != "" { if file.ContentType != "" {
mt = string(f.ContentType) mimeType = string(file.ContentType)
} }
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt, file.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mimeType,
mw.en.Encode(mw.c.String(), f.Name))) mw.encoder.Encode(mw.charset.String(), file.Name)))
} }
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok { if _, ok := file.getHeader(HeaderContentTransferEnc); !ok {
if f.Enc != "" { if file.Enc != "" {
e = f.Enc encoding = file.Enc
} }
f.setHeader(HeaderContentTransferEnc, string(e)) file.setHeader(HeaderContentTransferEnc, string(encoding))
} }
if f.Desc != "" { if file.Desc != "" {
if _, ok := f.getHeader(HeaderContentDescription); !ok { if _, ok := file.getHeader(HeaderContentDescription); !ok {
f.setHeader(HeaderContentDescription, f.Desc) file.setHeader(HeaderContentDescription, file.Desc)
} }
} }
if _, ok := f.getHeader(HeaderContentDisposition); !ok { if _, ok := file.getHeader(HeaderContentDisposition); !ok {
d := "inline" disposition := "inline"
if a { if isAttachment {
d = "attachment" disposition = "attachment"
} }
f.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, d, file.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`,
mw.en.Encode(mw.c.String(), f.Name))) disposition, mw.encoder.Encode(mw.charset.String(), file.Name)))
} }
if !a { if !isAttachment {
if _, ok := f.getHeader(HeaderContentID); !ok { if _, ok := file.getHeader(HeaderContentID); !ok {
f.setHeader(HeaderContentID, fmt.Sprintf("<%s>", f.Name)) file.setHeader(HeaderContentID, fmt.Sprintf("<%s>", file.Name))
} }
} }
if mw.d == 0 { if mw.depth == 0 {
for h, v := range f.Header { for header, val := range file.Header {
mw.writeHeader(Header(h), v...) mw.writeHeader(Header(header), val...)
} }
mw.writeString(SingleNewLine) mw.writeString(SingleNewLine)
} }
if mw.d > 0 { if mw.depth > 0 {
mw.newPart(f.Header) mw.newPart(file.Header)
} }
if mw.err == nil { if mw.err == nil {
mw.writeBody(f.Writer, e) mw.writeBody(file.Writer, encoding)
} }
} }
} }
// 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(h map[string][]string) { func (mw *msgWriter) newPart(header map[string][]string) {
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h) mw.partWriter, mw.err = mw.multiPartWriter[mw.depth-1].CreatePart(header)
} }
// writePart writes the corresponding part to the Msg body // writePart writes the corresponding part to the Msg body
func (mw *msgWriter) writePart(p *Part, cs Charset) { func (mw *msgWriter) writePart(part *Part, charset Charset) {
pcs := p.cset partCharset := part.cset
if pcs.String() == "" { if partCharset.String() == "" {
pcs = cs partCharset = charset
} }
ct := fmt.Sprintf("%s; charset=%s", p.ctype, pcs) contentType := fmt.Sprintf("%s; charset=%s", part.ctype, partCharset)
cte := p.enc.String() contentTransferEnc := part.enc.String()
if mw.d == 0 { if mw.depth == 0 {
mw.writeHeader(HeaderContentType, ct) mw.writeHeader(HeaderContentType, contentType)
mw.writeHeader(HeaderContentTransferEnc, cte) mw.writeHeader(HeaderContentTransferEnc, contentTransferEnc)
mw.writeString(SingleNewLine) mw.writeString(SingleNewLine)
} }
if mw.d > 0 { if mw.depth > 0 {
mh := textproto.MIMEHeader{} mimeHeader := textproto.MIMEHeader{}
if p.desc != "" { if part.desc != "" {
mh.Add(string(HeaderContentDescription), p.desc) mimeHeader.Add(string(HeaderContentDescription), part.desc)
} }
mh.Add(string(HeaderContentType), ct) mimeHeader.Add(string(HeaderContentType), contentType)
mh.Add(string(HeaderContentTransferEnc), cte) mimeHeader.Add(string(HeaderContentTransferEnc), contentTransferEnc)
mw.newPart(mh) mw.newPart(mimeHeader)
} }
mw.writeBody(p.w, p.enc) mw.writeBody(part.w, part.enc)
} }
// writeString writes a string into the msgWriter's io.Writer interface // writeString writes a string into the msgWriter's io.Writer interface
@ -274,102 +278,103 @@ func (mw *msgWriter) writeString(s string) {
return return
} }
var n int var n int
n, mw.err = io.WriteString(mw.w, s) n, mw.err = io.WriteString(mw.writer, s)
mw.n += int64(n) mw.bytesWritten += 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(k Header, vl ...string) { func (mw *msgWriter) writeHeader(key Header, values ...string) {
wbuf := bytes.Buffer{} buffer := strings.Builder{}
cl := MaxHeaderLength - 2 charLength := MaxHeaderLength - 2
wbuf.WriteString(string(k)) buffer.WriteString(string(key))
cl -= len(k) charLength -= len(key)
if len(vl) == 0 { if len(values) == 0 {
wbuf.WriteString(":\r\n") buffer.WriteString(":\r\n")
return return
} }
wbuf.WriteString(": ") buffer.WriteString(": ")
cl -= 2 charLength -= 2
fs := strings.Join(vl, ", ") fullValueStr := strings.Join(values, ", ")
sfs := strings.Split(fs, " ") words := strings.Split(fullValueStr, " ")
for i, v := range sfs { for i, val := range words {
if cl-len(v) <= 1 { if charLength-len(val) <= 1 {
wbuf.WriteString(fmt.Sprintf("%s ", SingleNewLine)) buffer.WriteString(fmt.Sprintf("%s ", SingleNewLine))
cl = MaxHeaderLength - 3 charLength = MaxHeaderLength - 3
} }
wbuf.WriteString(v) buffer.WriteString(val)
if i < len(sfs)-1 { if i < len(words)-1 {
wbuf.WriteString(" ") buffer.WriteString(" ")
cl -= 1 charLength -= 1
} }
cl -= len(v) charLength -= len(val)
} }
bufs := wbuf.String() bufferString := buffer.String()
bufs = strings.ReplaceAll(bufs, fmt.Sprintf(" %s", SingleNewLine), SingleNewLine) bufferString = strings.ReplaceAll(bufferString, fmt.Sprintf(" %s", SingleNewLine),
mw.writeString(bufs) SingleNewLine)
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(f func(io.Writer) (int64, error), e Encoding) { func (mw *msgWriter) writeBody(writeFunc func(io.Writer) (int64, error), encoding Encoding) {
var w io.Writer var writer io.Writer
var ew io.WriteCloser var encodedWriter io.WriteCloser
var n int64 var n int64
var err error var err error
if mw.d == 0 { if mw.depth == 0 {
w = mw.w writer = mw.writer
} }
if mw.d > 0 { if mw.depth > 0 {
w = mw.pw writer = mw.partWriter
} }
wbuf := bytes.Buffer{} writeBuffer := bytes.Buffer{}
lb := Base64LineBreaker{} lineBreaker := Base64LineBreaker{}
lb.out = &wbuf lineBreaker.out = &writeBuffer
switch e { switch encoding {
case EncodingQP: case EncodingQP:
ew = quotedprintable.NewWriter(&wbuf) encodedWriter = quotedprintable.NewWriter(&writeBuffer)
case EncodingB64: case EncodingB64:
ew = base64.NewEncoder(base64.StdEncoding, &lb) encodedWriter = base64.NewEncoder(base64.StdEncoding, &lineBreaker)
case NoEncoding: case NoEncoding:
_, err = f(&wbuf) _, err = writeFunc(&writeBuffer)
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(w, &wbuf) n, err = io.Copy(writer, &writeBuffer)
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.d == 0 { if mw.depth == 0 {
mw.n += n mw.bytesWritten += n
} }
return return
default: default:
ew = quotedprintable.NewWriter(w) encodedWriter = quotedprintable.NewWriter(writer)
} }
_, err = f(ew) _, err = writeFunc(encodedWriter)
if err != nil { if err != nil {
mw.err = fmt.Errorf("bodyWriter function: %w", err) mw.err = fmt.Errorf("bodyWriter function: %w", err)
} }
err = ew.Close() err = encodedWriter.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 = lb.Close() err = lineBreaker.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(w, &wbuf) n, err = io.Copy(writer, &writeBuffer)
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.d == 0 { if mw.depth == 0 {
mw.n += n mw.bytesWritten += n
} }
} }

View file

@ -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{w: bw, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: bw, charset: CharsetUTF8, encoder: 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{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: 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{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw := &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: 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{w: &buf, c: CharsetUTF8, en: mime.QEncoding} mw = &msgWriter{writer: &buf, charset: CharsetUTF8, encoder: 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"`) {