mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-14 01:42:54 +01:00
Merge pull request #104 from wneessen/refactor_dsn_to_smtp
Refactor DSN handling from client.go to smtp.go
This commit is contained in:
commit
91bfed3f3d
5 changed files with 45 additions and 114 deletions
88
client.go
88
client.go
|
@ -633,91 +633,3 @@ func (c *Client) auth() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mail is an extension to the Go std library mail method. It decideds wether to call the
|
|
||||||
// original mail method from the std library or in case DSN is enabled on the Client to
|
|
||||||
// call our own method instead
|
|
||||||
func (c *Client) mail(f string) error {
|
|
||||||
ok, _ := c.sc.Extension("DSN")
|
|
||||||
if ok && c.dsn {
|
|
||||||
return c.dsnMail(f)
|
|
||||||
}
|
|
||||||
return c.sc.Mail(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// rcpt is an extension to the Go std library rcpt method. It decideds wether to call
|
|
||||||
// original rcpt method from the std library or in case DSN is enabled on the Client to
|
|
||||||
// call our own method instead
|
|
||||||
func (c *Client) rcpt(t string) error {
|
|
||||||
ok, _ := c.sc.Extension("DSN")
|
|
||||||
if ok && c.dsn {
|
|
||||||
return c.dsnRcpt(t)
|
|
||||||
}
|
|
||||||
return c.sc.Rcpt(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dsnRcpt issues a RCPT command to the server using the provided email address.
|
|
||||||
// A call to rcpt must be preceded by a call to mail and may be followed by
|
|
||||||
// a Data call or another rcpt call.
|
|
||||||
//
|
|
||||||
// This is a copy of the original Go std library net/smtp function with additions
|
|
||||||
// for the DSN extension
|
|
||||||
func (c *Client) dsnRcpt(t string) error {
|
|
||||||
if err := validateLine(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(c.dsnrntype) <= 0 {
|
|
||||||
return c.sc.Rcpt(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
rno := strings.Join(c.dsnrntype, ",")
|
|
||||||
_, _, err := c.cmd(25, "RCPT TO:<%s> NOTIFY=%s", t, rno)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// dsnMail issues a MAIL command to the server using the provided email address.
|
|
||||||
// If the server supports the 8BITMIME extension, mail adds the BODY=8BITMIME
|
|
||||||
// parameter. If the server supports the SMTPUTF8 extension, mail adds the
|
|
||||||
// SMTPUTF8 parameter.
|
|
||||||
// This initiates a mail transaction and is followed by one or more rcpt calls.
|
|
||||||
//
|
|
||||||
// This is a copy of the original Go std library net/smtp function with additions
|
|
||||||
// for the DSN extension
|
|
||||||
func (c *Client) dsnMail(f string) error {
|
|
||||||
if err := validateLine(f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmdStr := "MAIL FROM:<%s>"
|
|
||||||
if ok, _ := c.sc.Extension("8BITMIME"); ok {
|
|
||||||
cmdStr += " BODY=8BITMIME"
|
|
||||||
}
|
|
||||||
if ok, _ := c.sc.Extension("SMTPUTF8"); ok {
|
|
||||||
cmdStr += " SMTPUTF8"
|
|
||||||
}
|
|
||||||
cmdStr += fmt.Sprintf(" RET=%s", c.dsnmrtype)
|
|
||||||
|
|
||||||
_, _, err := c.cmd(250, cmdStr, f)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateLine checks to see if a line has CR or LF as per RFC 5321
|
|
||||||
// This is a 1:1 copy of the method from the original Go std library net/smtp
|
|
||||||
func validateLine(line string) error {
|
|
||||||
if strings.ContainsAny(line, "\n\r") {
|
|
||||||
return errors.New("smtp: A line must not contain CR or LF")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmd is a convenience function that sends a command and returns the response
|
|
||||||
// This is a 1:1 copy of the method from the original Go std library net/smtp
|
|
||||||
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
|
|
||||||
id, err := c.sc.Text.Cmd(format, args...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
c.sc.Text.StartResponse(id)
|
|
||||||
defer c.sc.Text.EndResponse(id)
|
|
||||||
code, msg, err := c.sc.Text.ReadResponse(expectCode)
|
|
||||||
return code, msg, err
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// Send sends out the mail message
|
// Send sends out the mail message
|
||||||
func (c *Client) Send(ml ...*Msg) error {
|
func (c *Client) Send(ml ...*Msg) error {
|
||||||
if cerr := c.checkConn(); cerr != nil {
|
if cerr := c.checkConn(); cerr != nil {
|
||||||
|
@ -38,7 +40,12 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.mail(f); err != nil {
|
if c.dsn {
|
||||||
|
if c.dsnmrtype != "" {
|
||||||
|
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.sc.Mail(f); err != nil {
|
||||||
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
se.errlist = append(se.errlist, reserr)
|
se.errlist = append(se.errlist, reserr)
|
||||||
|
@ -51,8 +58,10 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
rse := &SendError{}
|
rse := &SendError{}
|
||||||
rse.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
|
c.sc.SetDSNRcptNotifyOption(rno)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.rcpt(r); err != nil {
|
if err := c.sc.Rcpt(r); err != nil {
|
||||||
rse.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.errlist = append(rse.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
|
|
@ -9,6 +9,7 @@ package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send sends out the mail message
|
// Send sends out the mail message
|
||||||
|
@ -39,7 +40,12 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.mail(f); err != nil {
|
if c.dsn {
|
||||||
|
if c.dsnmrtype != "" {
|
||||||
|
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.sc.Mail(f); err != nil {
|
||||||
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
rerr = errors.Join(rerr, m.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
if reserr := c.sc.Reset(); reserr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
|
@ -51,8 +57,10 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
rse := &SendError{}
|
rse := &SendError{}
|
||||||
rse.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rse.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
|
c.sc.SetDSNRcptNotifyOption(rno)
|
||||||
for _, r := range rl {
|
for _, r := range rl {
|
||||||
if err := c.rcpt(r); err != nil {
|
if err := c.sc.Rcpt(r); err != nil {
|
||||||
rse.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rse.errlist = append(rse.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rse.rcpt = append(rse.rcpt, r)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
|
|
|
@ -994,27 +994,6 @@ func TestClient_auth(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidateLine tests the validateLine() method
|
|
||||||
func TestValidateLine(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
sf bool
|
|
||||||
}{
|
|
||||||
{"valid line", "valid line", false},
|
|
||||||
{`invalid line: \n`, "invalid line\n", true},
|
|
||||||
{`invalid line: \r`, "invalid line\r", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := validateLine(tt.value); err != nil && !tt.sf {
|
|
||||||
t.Errorf("validateLine failed: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestClient_Send_MsgSendError tests the Client.Send method with a broken recipient and verifies
|
// TestClient_Send_MsgSendError tests the Client.Send method with a broken recipient and verifies
|
||||||
// that the SendError type works properly
|
// that the SendError type works properly
|
||||||
func TestClient_Send_MsgSendError(t *testing.T) {
|
func TestClient_Send_MsgSendError(t *testing.T) {
|
||||||
|
|
25
smtp/smtp.go
25
smtp/smtp.go
|
@ -17,6 +17,7 @@
|
||||||
// 8BITMIME RFC 1652
|
// 8BITMIME RFC 1652
|
||||||
// AUTH RFC 2554
|
// AUTH RFC 2554
|
||||||
// STARTTLS RFC 3207
|
// STARTTLS RFC 3207
|
||||||
|
// DSN RFC 1891
|
||||||
package smtp
|
package smtp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -50,9 +51,12 @@ type Client struct {
|
||||||
localName string // the name to use in HELO/EHLO
|
localName string // the name to use in HELO/EHLO
|
||||||
didHello bool // whether we've said HELO/EHLO
|
didHello bool // whether we've said HELO/EHLO
|
||||||
helloError error // the error from the hello
|
helloError error // the error from the hello
|
||||||
|
// debug logging
|
||||||
debug bool // debug logging is enabled
|
debug bool // debug logging is enabled
|
||||||
logger *log.Logger // logger will be used for debug logging
|
logger *log.Logger // logger will be used for debug logging
|
||||||
|
// DSN support
|
||||||
|
dsnmrtype string // dsnmrtype defines the mail return option in case DSN is enabled
|
||||||
|
dsnrntype string // dsnrntype defines the recipient notify option in case DSN is enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// logDirection is a type wrapper for the direction a debug log message goes
|
// logDirection is a type wrapper for the direction a debug log message goes
|
||||||
|
@ -256,6 +260,10 @@ func (c *Client) Mail(from string) error {
|
||||||
if _, ok := c.ext["SMTPUTF8"]; ok {
|
if _, ok := c.ext["SMTPUTF8"]; ok {
|
||||||
cmdStr += " SMTPUTF8"
|
cmdStr += " SMTPUTF8"
|
||||||
}
|
}
|
||||||
|
_, ok := c.ext["DSN"]
|
||||||
|
if ok && c.dsnmrtype != "" {
|
||||||
|
cmdStr += fmt.Sprintf(" RET=%s", c.dsnmrtype)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, _, err := c.cmd(250, cmdStr, from)
|
_, _, err := c.cmd(250, cmdStr, from)
|
||||||
return err
|
return err
|
||||||
|
@ -268,6 +276,11 @@ func (c *Client) Rcpt(to string) error {
|
||||||
if err := validateLine(to); err != nil {
|
if err := validateLine(to); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, ok := c.ext["DSN"]
|
||||||
|
if ok && c.dsnrntype != "" {
|
||||||
|
_, _, err := c.cmd(25, "RCPT TO:<%s> NOTIFY=%s", to, c.dsnrntype)
|
||||||
|
return err
|
||||||
|
}
|
||||||
_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
|
_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -434,6 +447,16 @@ func (c *Client) SetDebugLog(v bool) {
|
||||||
c.logger = nil
|
c.logger = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDSNMailReturnOption sets the DSN mail return option for the Mail method
|
||||||
|
func (c *Client) SetDSNMailReturnOption(d string) {
|
||||||
|
c.dsnmrtype = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDSNRcptNotifyOption sets the DSN recipient notify option for the Mail method
|
||||||
|
func (c *Client) SetDSNRcptNotifyOption(d string) {
|
||||||
|
c.dsnrntype = d
|
||||||
|
}
|
||||||
|
|
||||||
// debugLog checks if the debug flag is set and if so logs the provided message to StdErr
|
// debugLog checks if the debug flag is set and if so logs the provided message to StdErr
|
||||||
func (c *Client) debugLog(d logDirection, f string, a ...interface{}) {
|
func (c *Client) debugLog(d logDirection, f string, a ...interface{}) {
|
||||||
if c.debug {
|
if c.debug {
|
||||||
|
|
Loading…
Reference in a new issue