mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-09 15:32:54 +01:00
Refactor DSN handling from client.go to smtp.go
This PR refactors the the DSN (RFC 1891) SMTP client handling, that was introduced in f4cdc61dd0
.
While most of the Client options stay the same, the whole workaround logic for the SMTP client has been removed and added as part of the SMTP client instead.
This was we got rid of the Client's own `mail()`, `rcpt()`, `dsnRcpt()`, `dsnMail()` methods as well as the copies of the `cmd()` and `validateLine()` methods. The Client is now using the proper `Mail()` and `Rcpt()` methods of the SMTP client instead.
This commit is contained in:
parent
f48c8326ca
commit
63d8cef8ca
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