mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +01:00
Compare commits
No commits in common. "1c883a8ed4182f07165a2a6b33b4151b7cd70e98" and "5c143cb74c08f606452d5c33be72fa919fc6e9d9" have entirely different histories.
1c883a8ed4
...
5c143cb74c
21 changed files with 1059 additions and 1086 deletions
|
@ -20,39 +20,39 @@ type Base64LineBreaker struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var newlineBytes = []byte(SingleNewLine)
|
var nl = []byte(SingleNewLine)
|
||||||
|
|
||||||
// Write writes the data stream and inserts a SingleNewLine when the maximum
|
// Write writes the data stream and inserts a SingleNewLine when the maximum
|
||||||
// line length is reached
|
// line length is reached
|
||||||
func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
func (l *Base64LineBreaker) Write(b []byte) (n int, err error) {
|
||||||
if l.out == nil {
|
if l.out == nil {
|
||||||
err = fmt.Errorf(ErrNoOutWriter)
|
err = fmt.Errorf(ErrNoOutWriter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if l.used+len(data) < MaxBodyLength {
|
if l.used+len(b) < MaxBodyLength {
|
||||||
copy(l.line[l.used:], data)
|
copy(l.line[l.used:], b)
|
||||||
l.used += len(data)
|
l.used += len(b)
|
||||||
return len(data), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
numBytes, err = l.out.Write(l.line[0:l.used])
|
n, err = l.out.Write(l.line[0:l.used])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
excess := MaxBodyLength - l.used
|
excess := MaxBodyLength - l.used
|
||||||
l.used = 0
|
l.used = 0
|
||||||
|
|
||||||
numBytes, err = l.out.Write(data[0:excess])
|
n, err = l.out.Write(b[0:excess])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
numBytes, err = l.out.Write(newlineBytes)
|
n, err = l.out.Write(nl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.Write(data[excess:])
|
return l.Write(b[excess:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the Base64LineBreaker and writes any access data that is still
|
// Close closes the Base64LineBreaker and writes any access data that is still
|
||||||
|
@ -63,7 +63,7 @@ func (l *Base64LineBreaker) Close() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = l.out.Write(newlineBytes)
|
_, err = l.out.Write(nl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
314
client.go
314
client.go
|
@ -87,11 +87,11 @@ type DialContextFunc func(ctx context.Context, network, address string) (net.Con
|
||||||
|
|
||||||
// Client is the SMTP client struct
|
// Client is the SMTP client struct
|
||||||
type Client struct {
|
type Client struct {
|
||||||
// connection is the net.Conn that the smtp.Client is based on
|
// co is the net.Conn that the smtp.Client is based on
|
||||||
connection net.Conn
|
co net.Conn
|
||||||
|
|
||||||
// Timeout for the SMTP server connection
|
// Timeout for the SMTP server connection
|
||||||
connTimeout time.Duration
|
cto time.Duration
|
||||||
|
|
||||||
// dsn indicates that we want to use DSN for the Client
|
// dsn indicates that we want to use DSN for the Client
|
||||||
dsn bool
|
dsn bool
|
||||||
|
@ -102,8 +102,8 @@ type Client struct {
|
||||||
// dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled
|
// dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled
|
||||||
dsnrntype []string
|
dsnrntype []string
|
||||||
|
|
||||||
// isEncrypted indicates if a Client connection is encrypted or not
|
// enc indicates if a Client connection is encrypted or not
|
||||||
isEncrypted bool
|
enc bool
|
||||||
|
|
||||||
// noNoop indicates the Noop is to be skipped
|
// noNoop indicates the Noop is to be skipped
|
||||||
noNoop bool
|
noNoop bool
|
||||||
|
@ -121,17 +121,17 @@ type Client struct {
|
||||||
port int
|
port int
|
||||||
fallbackPort int
|
fallbackPort int
|
||||||
|
|
||||||
// smtpAuth is a pointer to smtp.Auth
|
// sa is a pointer to smtp.Auth
|
||||||
smtpAuth smtp.Auth
|
sa smtp.Auth
|
||||||
|
|
||||||
// smtpAuthType represents the authentication type for SMTP AUTH
|
// satype represents the authentication type for SMTP AUTH
|
||||||
smtpAuthType SMTPAuthType
|
satype SMTPAuthType
|
||||||
|
|
||||||
// smtpClient is the smtp.Client that is set up when using the Dial*() methods
|
// sc is the smtp.Client that is set up when using the Dial*() methods
|
||||||
smtpClient *smtp.Client
|
sc *smtp.Client
|
||||||
|
|
||||||
// Use SSL for the connection
|
// Use SSL for the connection
|
||||||
useSSL bool
|
ssl bool
|
||||||
|
|
||||||
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
||||||
tlspolicy TLSPolicy
|
tlspolicy TLSPolicy
|
||||||
|
@ -142,11 +142,11 @@ type Client struct {
|
||||||
// user is the SMTP AUTH username
|
// user is the SMTP AUTH username
|
||||||
user string
|
user string
|
||||||
|
|
||||||
// useDebugLog enables the debug logging on the SMTP client
|
// dl enables the debug logging on the SMTP client
|
||||||
useDebugLog bool
|
dl bool
|
||||||
|
|
||||||
// logger is a logger that implements the log.Logger interface
|
// l is a logger that implements the log.Logger interface
|
||||||
logger log.Logger
|
l log.Logger
|
||||||
|
|
||||||
// dialContextFunc is a custom DialContext function to dial target SMTP server
|
// dialContextFunc is a custom DialContext function to dial target SMTP server
|
||||||
dialContextFunc DialContextFunc
|
dialContextFunc DialContextFunc
|
||||||
|
@ -198,13 +198,13 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient returns a new Session client object
|
// NewClient returns a new Session client object
|
||||||
func NewClient(host string, opts ...Option) (*Client, error) {
|
func NewClient(h string, o ...Option) (*Client, error) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
connTimeout: DefaultTimeout,
|
cto: DefaultTimeout,
|
||||||
host: host,
|
host: h,
|
||||||
port: DefaultPort,
|
port: DefaultPort,
|
||||||
tlsconfig: &tls.Config{ServerName: host, MinVersion: DefaultTLSMinVersion},
|
tlsconfig: &tls.Config{ServerName: h, MinVersion: DefaultTLSMinVersion},
|
||||||
tlspolicy: DefaultTLSPolicy,
|
tlspolicy: DefaultTLSPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default HELO/EHLO hostname
|
// Set default HELO/EHLO hostname
|
||||||
|
@ -213,11 +213,11 @@ func NewClient(host string, opts ...Option) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided Option functions
|
// Override defaults with optionally provided Option functions
|
||||||
for _, opt := range opts {
|
for _, co := range o {
|
||||||
if opt == nil {
|
if co == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := opt(c); err != nil {
|
if err := co(c); err != nil {
|
||||||
return c, fmt.Errorf("failed to apply option: %w", err)
|
return c, fmt.Errorf("failed to apply option: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,23 +231,23 @@ func NewClient(host string, opts ...Option) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPort overrides the default connection port
|
// WithPort overrides the default connection port
|
||||||
func WithPort(port int) Option {
|
func WithPort(p int) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if port < 1 || port > 65535 {
|
if p < 1 || p > 65535 {
|
||||||
return ErrInvalidPort
|
return ErrInvalidPort
|
||||||
}
|
}
|
||||||
c.port = port
|
c.port = p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTimeout overrides the default connection timeout
|
// WithTimeout overrides the default connection timeout
|
||||||
func WithTimeout(timeout time.Duration) Option {
|
func WithTimeout(t time.Duration) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if timeout <= 0 {
|
if t <= 0 {
|
||||||
return ErrInvalidTimeout
|
return ErrInvalidTimeout
|
||||||
}
|
}
|
||||||
c.connTimeout = timeout
|
c.cto = t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func WithTimeout(timeout time.Duration) Option {
|
||||||
// Deprecated: use WithSSLPort instead.
|
// Deprecated: use WithSSLPort instead.
|
||||||
func WithSSL() Option {
|
func WithSSL() Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.useSSL = true
|
c.ssl = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,9 +267,9 @@ func WithSSL() Option {
|
||||||
//
|
//
|
||||||
// When the SSL connection fails and fallback is set to true,
|
// When the SSL connection fails and fallback is set to true,
|
||||||
// the client will attempt to connect on port 25 using plaintext.
|
// the client will attempt to connect on port 25 using plaintext.
|
||||||
func WithSSLPort(fallback bool) Option {
|
func WithSSLPort(fb bool) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.SetSSLPort(true, fallback)
|
c.SetSSLPort(true, fb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,26 +278,26 @@ func WithSSLPort(fallback bool) Option {
|
||||||
// to StdErr
|
// to StdErr
|
||||||
func WithDebugLog() Option {
|
func WithDebugLog() Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.useDebugLog = true
|
c.dl = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLogger overrides the default log.Logger that is used for debug logging
|
// WithLogger overrides the default log.Logger that is used for debug logging
|
||||||
func WithLogger(logger log.Logger) Option {
|
func WithLogger(l log.Logger) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.logger = logger
|
c.l = l
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
||||||
func WithHELO(helo string) Option {
|
func WithHELO(h string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if helo == "" {
|
if h == "" {
|
||||||
return ErrInvalidHELO
|
return ErrInvalidHELO
|
||||||
}
|
}
|
||||||
c.helo = helo
|
c.helo = h
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,9 +305,9 @@ func WithHELO(helo string) Option {
|
||||||
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
||||||
//
|
//
|
||||||
// Deprecated: use WithTLSPortPolicy instead.
|
// Deprecated: use WithTLSPortPolicy instead.
|
||||||
func WithTLSPolicy(policy TLSPolicy) Option {
|
func WithTLSPolicy(p TLSPolicy) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.tlspolicy = policy
|
c.tlspolicy = p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,52 +319,52 @@ func WithTLSPolicy(policy TLSPolicy) Option {
|
||||||
// If the connection fails with TLSOpportunistic,
|
// If the connection fails with TLSOpportunistic,
|
||||||
// a plaintext connection is attempted on port 25 as a fallback.
|
// a plaintext connection is attempted on port 25 as a fallback.
|
||||||
// NoTLS will allways use port 25.
|
// NoTLS will allways use port 25.
|
||||||
func WithTLSPortPolicy(policy TLSPolicy) Option {
|
func WithTLSPortPolicy(p TLSPolicy) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.SetTLSPortPolicy(policy)
|
c.SetTLSPortPolicy(p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTLSConfig tells the client to use the provided *tls.Config
|
// WithTLSConfig tells the client to use the provided *tls.Config
|
||||||
func WithTLSConfig(tlsconfig *tls.Config) Option {
|
func WithTLSConfig(co *tls.Config) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if tlsconfig == nil {
|
if co == nil {
|
||||||
return ErrInvalidTLSConfig
|
return ErrInvalidTLSConfig
|
||||||
}
|
}
|
||||||
c.tlsconfig = tlsconfig
|
c.tlsconfig = co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication
|
// WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication
|
||||||
func WithSMTPAuth(authtype SMTPAuthType) Option {
|
func WithSMTPAuth(t SMTPAuthType) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.smtpAuthType = authtype
|
c.satype = t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication
|
// WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication
|
||||||
func WithSMTPAuthCustom(smtpAuth smtp.Auth) Option {
|
func WithSMTPAuthCustom(a smtp.Auth) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.smtpAuth = smtpAuth
|
c.sa = a
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUsername tells the client to use the provided string as username for authentication
|
// WithUsername tells the client to use the provided string as username for authentication
|
||||||
func WithUsername(username string) Option {
|
func WithUsername(u string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.user = username
|
c.user = u
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPassword tells the client to use the provided string as password/secret for authentication
|
// WithPassword tells the client to use the provided string as password/secret for authentication
|
||||||
func WithPassword(password string) Option {
|
func WithPassword(p string) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.pass = password
|
c.pass = p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,9 +386,9 @@ func WithDSN() Option {
|
||||||
// as described in the RFC 1891 and set the MAIL FROM Return option type to the
|
// as described in the RFC 1891 and set the MAIL FROM Return option type to the
|
||||||
// given DSNMailReturnOption
|
// given DSNMailReturnOption
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||||
func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
func WithDSNMailReturnType(mro DSNMailReturnOption) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
switch option {
|
switch mro {
|
||||||
case DSNMailReturnHeadersOnly:
|
case DSNMailReturnHeadersOnly:
|
||||||
case DSNMailReturnFull:
|
case DSNMailReturnFull:
|
||||||
default:
|
default:
|
||||||
|
@ -396,7 +396,7 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.dsn = true
|
c.dsn = true
|
||||||
c.dsnmrtype = option
|
c.dsnmrtype = mro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,13 +404,13 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
||||||
// WithDSNRcptNotifyType enables the Client to request DSNs as described in the RFC 1891
|
// WithDSNRcptNotifyType enables the Client to request DSNs as described in the RFC 1891
|
||||||
// and sets the RCPT TO notify options to the given list of DSNRcptNotifyOption
|
// and sets the RCPT TO notify options to the given list of DSNRcptNotifyOption
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||||
func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
func WithDSNRcptNotifyType(rno ...DSNRcptNotifyOption) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
var rcptOpts []string
|
var rnol []string
|
||||||
var ns, nns bool
|
var ns, nns bool
|
||||||
if len(opts) > 0 {
|
if len(rno) > 0 {
|
||||||
for _, opt := range opts {
|
for _, crno := range rno {
|
||||||
switch opt {
|
switch crno {
|
||||||
case DSNRcptNotifyNever:
|
case DSNRcptNotifyNever:
|
||||||
ns = true
|
ns = true
|
||||||
case DSNRcptNotifySuccess:
|
case DSNRcptNotifySuccess:
|
||||||
|
@ -422,7 +422,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
||||||
default:
|
default:
|
||||||
return ErrInvalidDSNRcptNotifyOption
|
return ErrInvalidDSNRcptNotifyOption
|
||||||
}
|
}
|
||||||
rcptOpts = append(rcptOpts, string(opt))
|
rnol = append(rnol, string(crno))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ns && nns {
|
if ns && nns {
|
||||||
|
@ -430,7 +430,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.dsn = true
|
c.dsn = true
|
||||||
c.dsnrntype = rcptOpts
|
c.dsnrntype = rnol
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,9 +445,9 @@ func WithoutNoop() Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDialContextFunc overrides the default DialContext for connecting SMTP server
|
// WithDialContextFunc overrides the default DialContext for connecting SMTP server
|
||||||
func WithDialContextFunc(dialCtxFunc DialContextFunc) Option {
|
func WithDialContextFunc(f DialContextFunc) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.dialContextFunc = dialCtxFunc
|
c.dialContextFunc = f
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,8 +463,8 @@ func (c *Client) ServerAddr() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
||||||
func (c *Client) SetTLSPolicy(policy TLSPolicy) {
|
func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
||||||
c.tlspolicy = policy
|
c.tlspolicy = p
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
|
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
|
||||||
|
@ -474,22 +474,22 @@ func (c *Client) SetTLSPolicy(policy TLSPolicy) {
|
||||||
// If the connection fails with TLSOpportunistic, a plaintext connection is
|
// If the connection fails with TLSOpportunistic, a plaintext connection is
|
||||||
// attempted on port 25 as a fallback.
|
// attempted on port 25 as a fallback.
|
||||||
// NoTLS will allways use port 25.
|
// NoTLS will allways use port 25.
|
||||||
func (c *Client) SetTLSPortPolicy(policy TLSPolicy) {
|
func (c *Client) SetTLSPortPolicy(p TLSPolicy) {
|
||||||
c.port = DefaultPortTLS
|
c.port = DefaultPortTLS
|
||||||
|
|
||||||
if policy == TLSOpportunistic {
|
if p == TLSOpportunistic {
|
||||||
c.fallbackPort = DefaultPort
|
c.fallbackPort = DefaultPort
|
||||||
}
|
}
|
||||||
if policy == NoTLS {
|
if p == NoTLS {
|
||||||
c.port = DefaultPort
|
c.port = DefaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
c.tlspolicy = policy
|
c.tlspolicy = p
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSSL tells the Client wether to use SSL or not
|
// SetSSL tells the Client wether to use SSL or not
|
||||||
func (c *Client) SetSSL(ssl bool) {
|
func (c *Client) SetSSL(s bool) {
|
||||||
c.useSSL = ssl
|
c.ssl = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSSLPort tells the Client wether or not to use SSL and fallback.
|
// SetSSLPort tells the Client wether or not to use SSL and fallback.
|
||||||
|
@ -499,124 +499,124 @@ func (c *Client) SetSSL(ssl bool) {
|
||||||
// Port 25 is used when SSL is unset (false).
|
// Port 25 is used when SSL is unset (false).
|
||||||
// When the SSL connection fails and fb is set to true,
|
// When the SSL connection fails and fb is set to true,
|
||||||
// the client will attempt to connect on port 25 using plaintext.
|
// the client will attempt to connect on port 25 using plaintext.
|
||||||
func (c *Client) SetSSLPort(ssl bool, fallback bool) {
|
func (c *Client) SetSSLPort(ssl bool, fb bool) {
|
||||||
c.port = DefaultPort
|
c.port = DefaultPort
|
||||||
if ssl {
|
if ssl {
|
||||||
c.port = DefaultPortSSL
|
c.port = DefaultPortSSL
|
||||||
}
|
}
|
||||||
|
|
||||||
c.fallbackPort = 0
|
c.fallbackPort = 0
|
||||||
if fallback {
|
if fb {
|
||||||
c.fallbackPort = DefaultPort
|
c.fallbackPort = DefaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
c.useSSL = ssl
|
c.ssl = ssl
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDebugLog tells the Client whether debug logging is enabled or not
|
// SetDebugLog tells the Client whether debug logging is enabled or not
|
||||||
func (c *Client) SetDebugLog(val bool) {
|
func (c *Client) SetDebugLog(v bool) {
|
||||||
c.useDebugLog = val
|
c.dl = v
|
||||||
if c.smtpClient != nil {
|
if c.sc != nil {
|
||||||
c.smtpClient.SetDebugLog(val)
|
c.sc.SetDebugLog(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger tells the Client which log.Logger to use
|
// SetLogger tells the Client which log.Logger to use
|
||||||
func (c *Client) SetLogger(logger log.Logger) {
|
func (c *Client) SetLogger(l log.Logger) {
|
||||||
c.logger = logger
|
c.l = l
|
||||||
if c.smtpClient != nil {
|
if c.sc != nil {
|
||||||
c.smtpClient.SetLogger(logger)
|
c.sc.SetLogger(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value
|
// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value
|
||||||
func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error {
|
func (c *Client) SetTLSConfig(co *tls.Config) error {
|
||||||
if tlsconfig == nil {
|
if co == nil {
|
||||||
return ErrInvalidTLSConfig
|
return ErrInvalidTLSConfig
|
||||||
}
|
}
|
||||||
c.tlsconfig = tlsconfig
|
c.tlsconfig = co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUsername overrides the current username string with the given value
|
// SetUsername overrides the current username string with the given value
|
||||||
func (c *Client) SetUsername(username string) {
|
func (c *Client) SetUsername(u string) {
|
||||||
c.user = username
|
c.user = u
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword overrides the current password string with the given value
|
// SetPassword overrides the current password string with the given value
|
||||||
func (c *Client) SetPassword(password string) {
|
func (c *Client) SetPassword(p string) {
|
||||||
c.pass = password
|
c.pass = p
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value
|
// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value
|
||||||
func (c *Client) SetSMTPAuth(authtype SMTPAuthType) {
|
func (c *Client) SetSMTPAuth(a SMTPAuthType) {
|
||||||
c.smtpAuthType = authtype
|
c.satype = a
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth
|
// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth
|
||||||
func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) {
|
func (c *Client) SetSMTPAuthCustom(sa smtp.Auth) {
|
||||||
c.smtpAuth = smtpAuth
|
c.sa = sa
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
|
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
|
||||||
func (c *Client) setDefaultHelo() error {
|
func (c *Client) setDefaultHelo() error {
|
||||||
hostname, err := os.Hostname()
|
hn, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read local hostname: %w", err)
|
return fmt.Errorf("failed cto read local hostname: %w", err)
|
||||||
}
|
}
|
||||||
c.helo = hostname
|
c.helo = hn
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialWithContext establishes a connection to the SMTP server with a given context.Context
|
// DialWithContext establishes a connection cto the SMTP server with a given context.Context
|
||||||
func (c *Client) DialWithContext(dialCtx context.Context) error {
|
func (c *Client) DialWithContext(pc context.Context) error {
|
||||||
ctx, cancel := context.WithDeadline(dialCtx, time.Now().Add(c.connTimeout))
|
ctx, cfn := context.WithDeadline(pc, time.Now().Add(c.cto))
|
||||||
defer cancel()
|
defer cfn()
|
||||||
|
|
||||||
if c.dialContextFunc == nil {
|
if c.dialContextFunc == nil {
|
||||||
netDialer := net.Dialer{}
|
nd := net.Dialer{}
|
||||||
c.dialContextFunc = netDialer.DialContext
|
c.dialContextFunc = nd.DialContext
|
||||||
|
|
||||||
if c.useSSL {
|
if c.ssl {
|
||||||
tlsDialer := tls.Dialer{NetDialer: &netDialer, Config: c.tlsconfig}
|
td := tls.Dialer{NetDialer: &nd, Config: c.tlsconfig}
|
||||||
c.isEncrypted = true
|
c.enc = true
|
||||||
c.dialContextFunc = tlsDialer.DialContext
|
c.dialContextFunc = td.DialContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
c.connection, err = c.dialContextFunc(ctx, "tcp", c.ServerAddr())
|
c.co, err = c.dialContextFunc(ctx, "tcp", c.ServerAddr())
|
||||||
if err != nil && c.fallbackPort != 0 {
|
if err != nil && c.fallbackPort != 0 {
|
||||||
// TODO: should we somehow log or append the previous error?
|
// TODO: should we somehow log or append the previous error?
|
||||||
c.connection, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
|
c.co, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := smtp.NewClient(c.connection, c.host)
|
sc, err := smtp.NewClient(c.co, c.host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if client == nil {
|
if sc == nil {
|
||||||
return fmt.Errorf("SMTP client is nil")
|
return fmt.Errorf("SMTP client is nil")
|
||||||
}
|
}
|
||||||
c.smtpClient = client
|
c.sc = sc
|
||||||
|
|
||||||
if c.logger != nil {
|
if c.l != nil {
|
||||||
c.smtpClient.SetLogger(c.logger)
|
c.sc.SetLogger(c.l)
|
||||||
}
|
}
|
||||||
if c.useDebugLog {
|
if c.dl {
|
||||||
c.smtpClient.SetDebugLog(true)
|
c.sc.SetDebugLog(true)
|
||||||
}
|
}
|
||||||
if err = c.smtpClient.Hello(c.helo); err != nil {
|
if err := c.sc.Hello(c.helo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.tls(); err != nil {
|
if err := c.tls(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.auth(); err != nil {
|
if err := c.auth(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,7 +628,7 @@ func (c *Client) Close() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.smtpClient.Quit(); err != nil {
|
if err := c.sc.Quit(); err != nil {
|
||||||
return fmt.Errorf("failed to close SMTP client: %w", err)
|
return fmt.Errorf("failed to close SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +640,7 @@ func (c *Client) Reset() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.smtpClient.Reset(); err != nil {
|
if err := c.sc.Reset(); err != nil {
|
||||||
return fmt.Errorf("failed to send RSET to SMTP client: %w", err)
|
return fmt.Errorf("failed to send RSET to SMTP client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,18 +649,18 @@ func (c *Client) Reset() error {
|
||||||
|
|
||||||
// DialAndSend establishes a connection to the SMTP server with a
|
// DialAndSend establishes a connection to the SMTP server with a
|
||||||
// default context.Background and sends the mail
|
// default context.Background and sends the mail
|
||||||
func (c *Client) DialAndSend(messages ...*Msg) error {
|
func (c *Client) DialAndSend(ml ...*Msg) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
return c.DialAndSendWithContext(ctx, messages...)
|
return c.DialAndSendWithContext(ctx, ml...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialAndSendWithContext establishes a connection to the SMTP server with a
|
// DialAndSendWithContext establishes a connection to the SMTP server with a
|
||||||
// custom context and sends the mail
|
// custom context and sends the mail
|
||||||
func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) error {
|
func (c *Client) DialAndSendWithContext(ctx context.Context, ml ...*Msg) error {
|
||||||
if err := c.DialWithContext(ctx); err != nil {
|
if err := c.DialWithContext(ctx); err != nil {
|
||||||
return fmt.Errorf("dial failed: %w", err)
|
return fmt.Errorf("dial failed: %w", err)
|
||||||
}
|
}
|
||||||
if err := c.Send(messages...); err != nil {
|
if err := c.Send(ml...); err != nil {
|
||||||
return fmt.Errorf("send failed: %w", err)
|
return fmt.Errorf("send failed: %w", err)
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
|
@ -672,17 +672,17 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e
|
||||||
// checkConn makes sure that a required server connection is available and extends the
|
// checkConn makes sure that a required server connection is available and extends the
|
||||||
// connection deadline
|
// connection deadline
|
||||||
func (c *Client) checkConn() error {
|
func (c *Client) checkConn() error {
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.noNoop {
|
if !c.noNoop {
|
||||||
if err := c.smtpClient.Noop(); err != nil {
|
if err := c.sc.Noop(); err != nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.connection.SetDeadline(time.Now().Add(c.connTimeout)); err != nil {
|
if err := c.co.SetDeadline(time.Now().Add(c.cto)); err != nil {
|
||||||
return ErrDeadlineExtendFailed
|
return ErrDeadlineExtendFailed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -696,30 +696,30 @@ func (c *Client) serverFallbackAddr() string {
|
||||||
|
|
||||||
// tls tries to make sure that the STARTTLS requirements are satisfied
|
// tls tries to make sure that the STARTTLS requirements are satisfied
|
||||||
func (c *Client) tls() error {
|
func (c *Client) tls() error {
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
return ErrNoActiveConnection
|
return ErrNoActiveConnection
|
||||||
}
|
}
|
||||||
if !c.useSSL && c.tlspolicy != NoTLS {
|
if !c.ssl && c.tlspolicy != NoTLS {
|
||||||
hasStartTLS := false
|
est := false
|
||||||
extension, _ := c.smtpClient.Extension("STARTTLS")
|
st, _ := c.sc.Extension("STARTTLS")
|
||||||
if c.tlspolicy == TLSMandatory {
|
if c.tlspolicy == TLSMandatory {
|
||||||
hasStartTLS = true
|
est = true
|
||||||
if !extension {
|
if !st {
|
||||||
return fmt.Errorf("STARTTLS mode set to: %q, but target host does not support STARTTLS",
|
return fmt.Errorf("STARTTLS mode set to: %q, but target host does not support STARTTLS",
|
||||||
c.tlspolicy)
|
c.tlspolicy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.tlspolicy == TLSOpportunistic {
|
if c.tlspolicy == TLSOpportunistic {
|
||||||
if extension {
|
if st {
|
||||||
hasStartTLS = true
|
est = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasStartTLS {
|
if est {
|
||||||
if err := c.smtpClient.StartTLS(c.tlsconfig); err != nil {
|
if err := c.sc.StartTLS(c.tlsconfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, c.isEncrypted = c.smtpClient.TLSConnectionState()
|
_, c.enc = c.sc.TLSConnectionState()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -729,40 +729,40 @@ func (c *Client) auth() error {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
return fmt.Errorf("failed to authenticate: %w", err)
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||||||
}
|
}
|
||||||
if c.smtpAuth == nil && c.smtpAuthType != "" {
|
if c.sa == nil && c.satype != "" {
|
||||||
hasSMTPAuth, smtpAuthType := c.smtpClient.Extension("AUTH")
|
sa, sat := c.sc.Extension("AUTH")
|
||||||
if !hasSMTPAuth {
|
if !sa {
|
||||||
return fmt.Errorf("server does not support SMTP AUTH")
|
return fmt.Errorf("server does not support SMTP AUTH")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch c.smtpAuthType {
|
switch c.satype {
|
||||||
case SMTPAuthPlain:
|
case SMTPAuthPlain:
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthPlain)) {
|
if !strings.Contains(sat, string(SMTPAuthPlain)) {
|
||||||
return ErrPlainAuthNotSupported
|
return ErrPlainAuthNotSupported
|
||||||
}
|
}
|
||||||
c.smtpAuth = smtp.PlainAuth("", c.user, c.pass, c.host)
|
c.sa = smtp.PlainAuth("", c.user, c.pass, c.host)
|
||||||
case SMTPAuthLogin:
|
case SMTPAuthLogin:
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthLogin)) {
|
if !strings.Contains(sat, string(SMTPAuthLogin)) {
|
||||||
return ErrLoginAuthNotSupported
|
return ErrLoginAuthNotSupported
|
||||||
}
|
}
|
||||||
c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host)
|
c.sa = smtp.LoginAuth(c.user, c.pass, c.host)
|
||||||
case SMTPAuthCramMD5:
|
case SMTPAuthCramMD5:
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthCramMD5)) {
|
if !strings.Contains(sat, string(SMTPAuthCramMD5)) {
|
||||||
return ErrCramMD5AuthNotSupported
|
return ErrCramMD5AuthNotSupported
|
||||||
}
|
}
|
||||||
c.smtpAuth = smtp.CRAMMD5Auth(c.user, c.pass)
|
c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
|
||||||
case SMTPAuthXOAUTH2:
|
case SMTPAuthXOAUTH2:
|
||||||
if !strings.Contains(smtpAuthType, string(SMTPAuthXOAUTH2)) {
|
if !strings.Contains(sat, string(SMTPAuthXOAUTH2)) {
|
||||||
return ErrXOauth2AuthNotSupported
|
return ErrXOauth2AuthNotSupported
|
||||||
}
|
}
|
||||||
c.smtpAuth = smtp.XOAuth2Auth(c.user, c.pass)
|
c.sa = smtp.XOAuth2Auth(c.user, c.pass)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported SMTP AUTH type %q", c.smtpAuthType)
|
return fmt.Errorf("unsupported SMTP AUTH type %q", c.satype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.smtpAuth != nil {
|
if c.sa != nil {
|
||||||
if err := c.smtpClient.Auth(c.smtpAuth); err != nil {
|
if err := c.sc.Auth(c.sa); err != nil {
|
||||||
return fmt.Errorf("SMTP AUTH failed: %w", err)
|
return fmt.Errorf("SMTP AUTH failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
128
client_119.go
128
client_119.go
|
@ -10,123 +10,123 @@ package mail
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// Send sends out the mail message
|
// Send sends out the mail message
|
||||||
func (c *Client) Send(messages ...*Msg) error {
|
func (c *Client) Send(ml ...*Msg) error {
|
||||||
if cerr := c.checkConn(); cerr != nil {
|
if cerr := c.checkConn(); cerr != nil {
|
||||||
return &SendError{Reason: ErrConnCheck, errlist: []error{cerr}, isTemp: isTempError(cerr)}
|
return &SendError{Reason: ErrConnCheck, errlist: []error{cerr}, isTemp: isTempError(cerr)}
|
||||||
}
|
}
|
||||||
var errs []*SendError
|
var errs []*SendError
|
||||||
for _, message := range messages {
|
for _, m := range ml {
|
||||||
message.sendError = nil
|
m.sendError = nil
|
||||||
if message.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||||
sendErr := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
se := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
from, err := message.GetSender(false)
|
f, err := m.GetSender(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendErr := &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rcpts, err := message.GetRecipients()
|
rl, err := m.GetRecipients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendErr := &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.dsn {
|
if c.dsn {
|
||||||
if c.dsnmrtype != "" {
|
if c.dsnmrtype != "" {
|
||||||
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = c.smtpClient.Mail(from); err != nil {
|
if err := c.sc.Mail(f); err != nil {
|
||||||
sendErr := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
if resetSendErr := c.smtpClient.Reset(); resetSendErr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
sendErr.errlist = append(sendErr.errlist, resetSendErr)
|
se.errlist = append(se.errlist, reserr)
|
||||||
}
|
}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
failed := false
|
failed := false
|
||||||
rcptSendErr := &SendError{}
|
rse := &SendError{}
|
||||||
rcptSendErr.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rcptSendErr.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
rcptNotifyOpt := strings.Join(c.dsnrntype, ",")
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
c.smtpClient.SetDSNRcptNotifyOption(rcptNotifyOpt)
|
c.sc.SetDSNRcptNotifyOption(rno)
|
||||||
for _, rcpt := range rcpts {
|
for _, r := range rl {
|
||||||
if err = c.smtpClient.Rcpt(rcpt); err != nil {
|
if err := c.sc.Rcpt(r); err != nil {
|
||||||
rcptSendErr.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rcptSendErr.errlist = append(rcptSendErr.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rcptSendErr.rcpt = append(rcptSendErr.rcpt, rcpt)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
rcptSendErr.isTemp = isTempError(err)
|
rse.isTemp = isTempError(err)
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if resetSendErr := c.smtpClient.Reset(); resetSendErr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
rcptSendErr.errlist = append(rcptSendErr.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
}
|
}
|
||||||
message.sendError = rcptSendErr
|
m.sendError = rse
|
||||||
errs = append(errs, rcptSendErr)
|
errs = append(errs, rse)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
writer, err := c.smtpClient.Data()
|
w, err := c.sc.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendErr := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = message.WriteTo(writer)
|
_, err = m.WriteTo(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendErr := &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
message.isDelivered = true
|
m.isDelivered = true
|
||||||
|
|
||||||
if err = writer.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
sendErr := &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
sendErr := &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
sendErr := &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
se := &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
message.sendError = sendErr
|
m.sendError = se
|
||||||
errs = append(errs, sendErr)
|
errs = append(errs, se)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
if len(errs) > 1 {
|
if len(errs) > 1 {
|
||||||
returnErr := &SendError{Reason: ErrAmbiguous}
|
re := &SendError{Reason: ErrAmbiguous}
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
returnErr.errlist = append(returnErr.errlist, errs[i].errlist...)
|
re.errlist = append(re.errlist, errs[i].errlist...)
|
||||||
returnErr.rcpt = append(returnErr.rcpt, errs[i].rcpt...)
|
re.rcpt = append(re.rcpt, errs[i].rcpt...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We assume that the isTemp flag from the last error we received should be the
|
// We assume that the isTemp flag from the last error we received should be the
|
||||||
// indicator for the returned isTemp flag as well
|
// indicator for the returned isTemp flag as well
|
||||||
returnErr.isTemp = errs[len(errs)-1].isTemp
|
re.isTemp = errs[len(errs)-1].isTemp
|
||||||
|
|
||||||
return returnErr
|
return re
|
||||||
}
|
}
|
||||||
return errs[0]
|
return errs[0]
|
||||||
}
|
}
|
||||||
|
|
102
client_120.go
102
client_120.go
|
@ -13,97 +13,97 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send sends out the mail message
|
// Send sends out the mail message
|
||||||
func (c *Client) Send(messages ...*Msg) (returnErr error) {
|
func (c *Client) Send(ml ...*Msg) (rerr error) {
|
||||||
if err := c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
returnErr = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
rerr = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, message := range messages {
|
for _, m := range ml {
|
||||||
message.sendError = nil
|
m.sendError = nil
|
||||||
if message.encoding == NoEncoding {
|
if m.encoding == NoEncoding {
|
||||||
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||||
message.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
from, err := message.GetSender(false)
|
f, err := m.GetSender(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrGetSender, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rcpts, err := message.GetRecipients()
|
rl, err := m.GetRecipients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrGetRcpts, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.dsn {
|
if c.dsn {
|
||||||
if c.dsnmrtype != "" {
|
if c.dsnmrtype != "" {
|
||||||
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = c.smtpClient.Mail(from); err != nil {
|
if err := c.sc.Mail(f); err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
if resetSendErr := c.smtpClient.Reset(); resetSendErr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
returnErr = errors.Join(returnErr, resetSendErr)
|
rerr = errors.Join(rerr, reserr)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
failed := false
|
failed := false
|
||||||
rcptSendErr := &SendError{}
|
rse := &SendError{}
|
||||||
rcptSendErr.errlist = make([]error, 0)
|
rse.errlist = make([]error, 0)
|
||||||
rcptSendErr.rcpt = make([]string, 0)
|
rse.rcpt = make([]string, 0)
|
||||||
rcptNotifyOpt := strings.Join(c.dsnrntype, ",")
|
rno := strings.Join(c.dsnrntype, ",")
|
||||||
c.smtpClient.SetDSNRcptNotifyOption(rcptNotifyOpt)
|
c.sc.SetDSNRcptNotifyOption(rno)
|
||||||
for _, rcpt := range rcpts {
|
for _, r := range rl {
|
||||||
if err = c.smtpClient.Rcpt(rcpt); err != nil {
|
if err := c.sc.Rcpt(r); err != nil {
|
||||||
rcptSendErr.Reason = ErrSMTPRcptTo
|
rse.Reason = ErrSMTPRcptTo
|
||||||
rcptSendErr.errlist = append(rcptSendErr.errlist, err)
|
rse.errlist = append(rse.errlist, err)
|
||||||
rcptSendErr.rcpt = append(rcptSendErr.rcpt, rcpt)
|
rse.rcpt = append(rse.rcpt, r)
|
||||||
rcptSendErr.isTemp = isTempError(err)
|
rse.isTemp = isTempError(err)
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if resetSendErr := c.smtpClient.Reset(); resetSendErr != nil {
|
if reserr := c.sc.Reset(); reserr != nil {
|
||||||
returnErr = errors.Join(returnErr, resetSendErr)
|
rerr = errors.Join(rerr, reserr)
|
||||||
}
|
}
|
||||||
message.sendError = rcptSendErr
|
m.sendError = rse
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
writer, err := c.smtpClient.Data()
|
w, err := c.sc.Data()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = message.WriteTo(writer)
|
_, err = m.WriteTo(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrWriteContent, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
message.isDelivered = true
|
m.isDelivered = true
|
||||||
|
|
||||||
if err = writer.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPDataClose, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrSMTPReset, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = c.checkConn(); err != nil {
|
if err := c.checkConn(); err != nil {
|
||||||
message.sendError = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
m.sendError = &SendError{Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err)}
|
||||||
returnErr = errors.Join(returnErr, message.sendError)
|
rerr = errors.Join(rerr, m.sendError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,9 @@ func TestNewClient(t *testing.T) {
|
||||||
if c.host != tt.host {
|
if c.host != tt.host {
|
||||||
t.Errorf("failed to create new client. Host expected: %s, got: %s", host, c.host)
|
t.Errorf("failed to create new client. Host expected: %s, got: %s", host, c.host)
|
||||||
}
|
}
|
||||||
if c.connTimeout != DefaultTimeout {
|
if c.cto != DefaultTimeout {
|
||||||
t.Errorf("failed to create new client. Timeout expected: %s, got: %s", DefaultTimeout.String(),
|
t.Errorf("failed to create new client. Timeout expected: %s, got: %s", DefaultTimeout.String(),
|
||||||
c.connTimeout.String())
|
c.cto.String())
|
||||||
}
|
}
|
||||||
if c.port != DefaultPort {
|
if c.port != DefaultPort {
|
||||||
t.Errorf("failed to create new client. Port expected: %d, got: %d", DefaultPort, c.port)
|
t.Errorf("failed to create new client. Port expected: %d, got: %d", DefaultPort, c.port)
|
||||||
|
@ -205,8 +205,8 @@ func TestWithTimeout(t *testing.T) {
|
||||||
t.Errorf("failed to create new client: %s", err)
|
t.Errorf("failed to create new client: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.connTimeout != tt.want {
|
if c.cto != tt.want {
|
||||||
t.Errorf("failed to set custom timeout. Want: %d, got: %d", tt.want, c.connTimeout)
|
t.Errorf("failed to set custom timeout. Want: %d, got: %d", tt.want, c.cto)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -345,8 +345,8 @@ func TestSetSSL(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSSL(tt.value)
|
c.SetSSL(tt.value)
|
||||||
if c.useSSL != tt.value {
|
if c.ssl != tt.value {
|
||||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.ssl, tt.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -374,8 +374,8 @@ func TestClient_SetSSLPort(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSSLPort(tt.value, tt.fb)
|
c.SetSSLPort(tt.value, tt.fb)
|
||||||
if c.useSSL != tt.value {
|
if c.ssl != tt.value {
|
||||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.ssl, tt.value)
|
||||||
}
|
}
|
||||||
if c.port != tt.port {
|
if c.port != tt.port {
|
||||||
t.Errorf("failed to set SSLPort, wanted port: %d, got: %d", c.port, tt.port)
|
t.Errorf("failed to set SSLPort, wanted port: %d, got: %d", c.port, tt.port)
|
||||||
|
@ -460,8 +460,8 @@ func TestSetSMTPAuth(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSMTPAuth(tt.value)
|
c.SetSMTPAuth(tt.value)
|
||||||
if string(c.smtpAuthType) != tt.want {
|
if string(c.satype) != tt.want {
|
||||||
t.Errorf("failed to set SMTP auth type. Expected %s, got: %s", tt.want, string(c.smtpAuthType))
|
t.Errorf("failed to set SMTP auth type. Expected %s, got: %s", tt.want, string(c.satype))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -590,10 +590,10 @@ func TestSetSMTPAuthCustom(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSMTPAuthCustom(tt.value)
|
c.SetSMTPAuthCustom(tt.value)
|
||||||
if c.smtpAuth == nil {
|
if c.sa == nil {
|
||||||
t.Errorf("failed to set custom SMTP auth method. SMTP Auth method is empty")
|
t.Errorf("failed to set custom SMTP auth method. SMTP Auth method is empty")
|
||||||
}
|
}
|
||||||
p, _, err := c.smtpAuth.Start(&si)
|
p, _, err := c.sa.Start(&si)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("SMTP Auth Start() method returned error: %s", err)
|
t.Errorf("SMTP Auth Start() method returned error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -615,10 +615,10 @@ func TestClient_DialWithContext(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.smtpClient == nil {
|
if c.sc == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
|
@ -640,10 +640,10 @@ func TestClient_DialWithContext_Fallback(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.smtpClient == nil {
|
if c.sc == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
|
@ -670,10 +670,10 @@ func TestClient_DialWithContext_Debug(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.smtpClient == nil {
|
if c.sc == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
c.SetDebugLog(true)
|
c.SetDebugLog(true)
|
||||||
|
@ -694,10 +694,10 @@ func TestClient_DialWithContext_Debug_custom(t *testing.T) {
|
||||||
t.Errorf("failed to dial with context: %s", err)
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.connection == nil {
|
if c.co == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.smtpClient == nil {
|
if c.sc == nil {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
c.SetDebugLog(true)
|
c.SetDebugLog(true)
|
||||||
|
@ -714,7 +714,7 @@ func TestClient_DialWithContextInvalidHost(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.connection = nil
|
c.co = nil
|
||||||
c.host = "invalid.addr"
|
c.host = "invalid.addr"
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := c.DialWithContext(ctx); err == nil {
|
if err := c.DialWithContext(ctx); err == nil {
|
||||||
|
@ -730,7 +730,7 @@ func TestClient_DialWithContextInvalidHELO(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.connection = nil
|
c.co = nil
|
||||||
c.helo = ""
|
c.helo = ""
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := c.DialWithContext(ctx); err == nil {
|
if err := c.DialWithContext(ctx); err == nil {
|
||||||
|
@ -762,7 +762,7 @@ func TestClient_checkConn(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
}
|
}
|
||||||
c.connection = nil
|
c.co = nil
|
||||||
if err := c.checkConn(); err == nil {
|
if err := c.checkConn(); err == nil {
|
||||||
t.Errorf("connCheck() should fail but succeeded")
|
t.Errorf("connCheck() should fail but succeeded")
|
||||||
}
|
}
|
||||||
|
@ -799,10 +799,10 @@ func TestClient_DialWithContextOptions(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !tt.sf {
|
if !tt.sf {
|
||||||
if c.connection == nil && !tt.sf {
|
if c.co == nil && !tt.sf {
|
||||||
t.Errorf("DialWithContext didn't fail but no connection found.")
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
}
|
}
|
||||||
if c.smtpClient == nil && !tt.sf {
|
if c.sc == nil && !tt.sf {
|
||||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
}
|
}
|
||||||
if err := c.Reset(); err != nil {
|
if err := c.Reset(); err != nil {
|
||||||
|
@ -1002,16 +1002,16 @@ func TestClient_DialSendCloseBroken(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closestart {
|
if tt.closestart {
|
||||||
_ = c.smtpClient.Close()
|
_ = c.sc.Close()
|
||||||
_ = c.connection.Close()
|
_ = c.co.Close()
|
||||||
}
|
}
|
||||||
if err := c.Send(m); err != nil && !tt.sf {
|
if err := c.Send(m); err != nil && !tt.sf {
|
||||||
t.Errorf("Send() failed: %s", err)
|
t.Errorf("Send() failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closeearly {
|
if tt.closeearly {
|
||||||
_ = c.smtpClient.Close()
|
_ = c.sc.Close()
|
||||||
_ = c.connection.Close()
|
_ = c.co.Close()
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil && !tt.sf {
|
if err := c.Close(); err != nil && !tt.sf {
|
||||||
t.Errorf("Close() failed: %s", err)
|
t.Errorf("Close() failed: %s", err)
|
||||||
|
@ -1062,16 +1062,16 @@ func TestClient_DialSendCloseBrokenWithDSN(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closestart {
|
if tt.closestart {
|
||||||
_ = c.smtpClient.Close()
|
_ = c.sc.Close()
|
||||||
_ = c.connection.Close()
|
_ = c.co.Close()
|
||||||
}
|
}
|
||||||
if err := c.Send(m); err != nil && !tt.sf {
|
if err := c.Send(m); err != nil && !tt.sf {
|
||||||
t.Errorf("Send() failed: %s", err)
|
t.Errorf("Send() failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.closeearly {
|
if tt.closeearly {
|
||||||
_ = c.smtpClient.Close()
|
_ = c.sc.Close()
|
||||||
_ = c.connection.Close()
|
_ = c.co.Close()
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil && !tt.sf {
|
if err := c.Close(); err != nil && !tt.sf {
|
||||||
t.Errorf("Close() failed: %s", err)
|
t.Errorf("Close() failed: %s", err)
|
||||||
|
|
26
file.go
26
file.go
|
@ -23,17 +23,17 @@ type File struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFileName sets the filename of the File
|
// WithFileName sets the filename of the File
|
||||||
func WithFileName(name string) FileOption {
|
func WithFileName(n string) FileOption {
|
||||||
return func(f *File) {
|
return func(f *File) {
|
||||||
f.Name = name
|
f.Name = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFileDescription sets an optional file description of the File that will be
|
// WithFileDescription sets an optional file description of the File that will be
|
||||||
// added as Content-Description part
|
// added as Content-Description part
|
||||||
func WithFileDescription(description string) FileOption {
|
func WithFileDescription(d string) FileOption {
|
||||||
return func(f *File) {
|
return func(f *File) {
|
||||||
f.Desc = description
|
f.Desc = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +41,12 @@ func WithFileDescription(description string) FileOption {
|
||||||
// Base64 encoding but there might be exceptions, where this might come handy.
|
// Base64 encoding but there might be exceptions, where this might come handy.
|
||||||
// Please note that quoted-printable should never be used for attachments/embeds. If this
|
// Please note that quoted-printable should never be used for attachments/embeds. If this
|
||||||
// is provided as argument, the function will automatically override back to Base64
|
// is provided as argument, the function will automatically override back to Base64
|
||||||
func WithFileEncoding(encoding Encoding) FileOption {
|
func WithFileEncoding(e Encoding) FileOption {
|
||||||
return func(f *File) {
|
return func(f *File) {
|
||||||
if encoding == EncodingQP {
|
if e == EncodingQP {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.Enc = encoding
|
f.Enc = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,19 +56,19 @@ func WithFileEncoding(encoding Encoding) FileOption {
|
||||||
// could not be guessed. In some cases, however, it might be needed to force
|
// could not be guessed. In some cases, however, it might be needed to force
|
||||||
// this to a specific type. For such situations this override method can
|
// this to a specific type. For such situations this override method can
|
||||||
// be used
|
// be used
|
||||||
func WithFileContentType(contentType ContentType) FileOption {
|
func WithFileContentType(t ContentType) FileOption {
|
||||||
return func(f *File) {
|
return func(f *File) {
|
||||||
f.ContentType = contentType
|
f.ContentType = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setHeader sets header fields to a File
|
// setHeader sets header fields to a File
|
||||||
func (f *File) setHeader(header Header, value string) {
|
func (f *File) setHeader(h Header, v string) {
|
||||||
f.Header.Set(string(header), value)
|
f.Header.Set(string(h), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHeader return header fields of a File
|
// getHeader return header fields of a File
|
||||||
func (f *File) getHeader(header Header) (string, bool) {
|
func (f *File) getHeader(h Header) (string, bool) {
|
||||||
v := f.Header.Get(string(header))
|
v := f.Header.Get(string(h))
|
||||||
return v, v != ""
|
return v, v != ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,68 +15,68 @@ import (
|
||||||
|
|
||||||
// JSONlog is the default structured JSON logger that satisfies the Logger interface
|
// JSONlog is the default structured JSON logger that satisfies the Logger interface
|
||||||
type JSONlog struct {
|
type JSONlog struct {
|
||||||
level Level
|
l Level
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSON returns a new JSONlog type that satisfies the Logger interface
|
// NewJSON returns a new JSONlog type that satisfies the Logger interface
|
||||||
func NewJSON(output io.Writer, level Level) *JSONlog {
|
func NewJSON(o io.Writer, l Level) *JSONlog {
|
||||||
logOpts := slog.HandlerOptions{}
|
lo := slog.HandlerOptions{}
|
||||||
switch level {
|
switch l {
|
||||||
case LevelDebug:
|
case LevelDebug:
|
||||||
logOpts.Level = slog.LevelDebug
|
lo.Level = slog.LevelDebug
|
||||||
case LevelInfo:
|
case LevelInfo:
|
||||||
logOpts.Level = slog.LevelInfo
|
lo.Level = slog.LevelInfo
|
||||||
case LevelWarn:
|
case LevelWarn:
|
||||||
logOpts.Level = slog.LevelWarn
|
lo.Level = slog.LevelWarn
|
||||||
case LevelError:
|
case LevelError:
|
||||||
logOpts.Level = slog.LevelError
|
lo.Level = slog.LevelError
|
||||||
default:
|
default:
|
||||||
logOpts.Level = slog.LevelDebug
|
lo.Level = slog.LevelDebug
|
||||||
}
|
}
|
||||||
logHandler := slog.NewJSONHandler(output, &logOpts)
|
lh := slog.NewJSONHandler(o, &lo)
|
||||||
return &JSONlog{
|
return &JSONlog{
|
||||||
level: level,
|
l: l,
|
||||||
log: slog.New(logHandler),
|
log: slog.New(lh),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf logs a debug message via the structured JSON logger
|
// Debugf logs a debug message via the structured JSON logger
|
||||||
func (l *JSONlog) Debugf(log Log) {
|
func (l *JSONlog) Debugf(lo Log) {
|
||||||
if l.level >= LevelDebug {
|
if l.l >= LevelDebug {
|
||||||
l.log.WithGroup(DirString).With(
|
l.log.WithGroup(DirString).With(
|
||||||
slog.String(DirFromString, log.directionFrom()),
|
slog.String(DirFromString, lo.directionFrom()),
|
||||||
slog.String(DirToString, log.directionTo()),
|
slog.String(DirToString, lo.directionTo()),
|
||||||
).Debug(fmt.Sprintf(log.Format, log.Messages...))
|
).Debug(fmt.Sprintf(lo.Format, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof logs a info message via the structured JSON logger
|
// Infof logs a info message via the structured JSON logger
|
||||||
func (l *JSONlog) Infof(log Log) {
|
func (l *JSONlog) Infof(lo Log) {
|
||||||
if l.level >= LevelInfo {
|
if l.l >= LevelInfo {
|
||||||
l.log.WithGroup(DirString).With(
|
l.log.WithGroup(DirString).With(
|
||||||
slog.String(DirFromString, log.directionFrom()),
|
slog.String(DirFromString, lo.directionFrom()),
|
||||||
slog.String(DirToString, log.directionTo()),
|
slog.String(DirToString, lo.directionTo()),
|
||||||
).Info(fmt.Sprintf(log.Format, log.Messages...))
|
).Info(fmt.Sprintf(lo.Format, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnf logs a warn message via the structured JSON logger
|
// Warnf logs a warn message via the structured JSON logger
|
||||||
func (l *JSONlog) Warnf(log Log) {
|
func (l *JSONlog) Warnf(lo Log) {
|
||||||
if l.level >= LevelWarn {
|
if l.l >= LevelWarn {
|
||||||
l.log.WithGroup(DirString).With(
|
l.log.WithGroup(DirString).With(
|
||||||
slog.String(DirFromString, log.directionFrom()),
|
slog.String(DirFromString, lo.directionFrom()),
|
||||||
slog.String(DirToString, log.directionTo()),
|
slog.String(DirToString, lo.directionTo()),
|
||||||
).Warn(fmt.Sprintf(log.Format, log.Messages...))
|
).Warn(fmt.Sprintf(lo.Format, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs a warn message via the structured JSON logger
|
// Errorf logs a warn message via the structured JSON logger
|
||||||
func (l *JSONlog) Errorf(log Log) {
|
func (l *JSONlog) Errorf(lo Log) {
|
||||||
if l.level >= LevelError {
|
if l.l >= LevelError {
|
||||||
l.log.WithGroup(DirString).With(
|
l.log.WithGroup(DirString).With(
|
||||||
slog.String(DirFromString, log.directionFrom()),
|
slog.String(DirFromString, lo.directionFrom()),
|
||||||
slog.String(DirToString, log.directionTo()),
|
slog.String(DirToString, lo.directionTo()),
|
||||||
).Error(fmt.Sprintf(log.Format, log.Messages...))
|
).Error(fmt.Sprintf(lo.Format, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ type jsonDir struct {
|
||||||
func TestNewJSON(t *testing.T) {
|
func TestNewJSON(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
l := NewJSON(&b, LevelDebug)
|
l := NewJSON(&b, LevelDebug)
|
||||||
if l.level != LevelDebug {
|
if l.l != LevelDebug {
|
||||||
t.Error("Expected level to be LevelDebug, got ", l.level)
|
t.Error("Expected level to be LevelDebug, got ", l.l)
|
||||||
}
|
}
|
||||||
if l.log == nil {
|
if l.log == nil {
|
||||||
t.Error("logger not initialized")
|
t.Error("logger not initialized")
|
||||||
|
@ -81,7 +81,7 @@ func TestJSONDebugf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelInfo
|
l.l = LevelInfo
|
||||||
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Debug message was not expected to be logged")
|
t.Error("Debug message was not expected to be logged")
|
||||||
|
@ -131,7 +131,7 @@ func TestJSONDebugf_WithDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelInfo
|
l.l = LevelInfo
|
||||||
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Debug message was not expected to be logged")
|
t.Error("Debug message was not expected to be logged")
|
||||||
|
@ -181,7 +181,7 @@ func TestJSONInfof(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelWarn
|
l.l = LevelWarn
|
||||||
l.Infof(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Infof(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Info message was not expected to be logged")
|
t.Error("Info message was not expected to be logged")
|
||||||
|
@ -231,7 +231,7 @@ func TestJSONWarnf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelError
|
l.l = LevelError
|
||||||
l.Warnf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Warnf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Warn message was not expected to be logged")
|
t.Error("Warn message was not expected to be logged")
|
||||||
|
@ -281,7 +281,7 @@ func TestJSONErrorf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = -99
|
l.l = -99
|
||||||
l.Errorf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Errorf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Error message was not expected to be logged")
|
t.Error("Error message was not expected to be logged")
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
// Stdlog is the default logger that satisfies the Logger interface
|
// Stdlog is the default logger that satisfies the Logger interface
|
||||||
type Stdlog struct {
|
type Stdlog struct {
|
||||||
level Level
|
l Level
|
||||||
err *log.Logger
|
err *log.Logger
|
||||||
warn *log.Logger
|
warn *log.Logger
|
||||||
info *log.Logger
|
info *log.Logger
|
||||||
|
@ -24,45 +24,45 @@ type Stdlog struct {
|
||||||
const CallDepth = 2
|
const CallDepth = 2
|
||||||
|
|
||||||
// New returns a new Stdlog type that satisfies the Logger interface
|
// New returns a new Stdlog type that satisfies the Logger interface
|
||||||
func New(output io.Writer, level Level) *Stdlog {
|
func New(o io.Writer, l Level) *Stdlog {
|
||||||
lf := log.Lmsgprefix | log.LstdFlags
|
lf := log.Lmsgprefix | log.LstdFlags
|
||||||
return &Stdlog{
|
return &Stdlog{
|
||||||
level: level,
|
l: l,
|
||||||
err: log.New(output, "ERROR: ", lf),
|
err: log.New(o, "ERROR: ", lf),
|
||||||
warn: log.New(output, " WARN: ", lf),
|
warn: log.New(o, " WARN: ", lf),
|
||||||
info: log.New(output, " INFO: ", lf),
|
info: log.New(o, " INFO: ", lf),
|
||||||
debug: log.New(output, "DEBUG: ", lf),
|
debug: log.New(o, "DEBUG: ", lf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf performs a Printf() on the debug logger
|
// Debugf performs a Printf() on the debug logger
|
||||||
func (l *Stdlog) Debugf(log Log) {
|
func (l *Stdlog) Debugf(lo Log) {
|
||||||
if l.level >= LevelDebug {
|
if l.l >= LevelDebug {
|
||||||
format := fmt.Sprintf("%s %s", log.directionPrefix(), log.Format)
|
f := fmt.Sprintf("%s %s", lo.directionPrefix(), lo.Format)
|
||||||
_ = l.debug.Output(CallDepth, fmt.Sprintf(format, log.Messages...))
|
_ = l.debug.Output(CallDepth, fmt.Sprintf(f, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof performs a Printf() on the info logger
|
// Infof performs a Printf() on the info logger
|
||||||
func (l *Stdlog) Infof(log Log) {
|
func (l *Stdlog) Infof(lo Log) {
|
||||||
if l.level >= LevelInfo {
|
if l.l >= LevelInfo {
|
||||||
format := fmt.Sprintf("%s %s", log.directionPrefix(), log.Format)
|
f := fmt.Sprintf("%s %s", lo.directionPrefix(), lo.Format)
|
||||||
_ = l.info.Output(CallDepth, fmt.Sprintf(format, log.Messages...))
|
_ = l.info.Output(CallDepth, fmt.Sprintf(f, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnf performs a Printf() on the warn logger
|
// Warnf performs a Printf() on the warn logger
|
||||||
func (l *Stdlog) Warnf(log Log) {
|
func (l *Stdlog) Warnf(lo Log) {
|
||||||
if l.level >= LevelWarn {
|
if l.l >= LevelWarn {
|
||||||
format := fmt.Sprintf("%s %s", log.directionPrefix(), log.Format)
|
f := fmt.Sprintf("%s %s", lo.directionPrefix(), lo.Format)
|
||||||
_ = l.warn.Output(CallDepth, fmt.Sprintf(format, log.Messages...))
|
_ = l.warn.Output(CallDepth, fmt.Sprintf(f, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf performs a Printf() on the error logger
|
// Errorf performs a Printf() on the error logger
|
||||||
func (l *Stdlog) Errorf(log Log) {
|
func (l *Stdlog) Errorf(lo Log) {
|
||||||
if l.level >= LevelError {
|
if l.l >= LevelError {
|
||||||
format := fmt.Sprintf("%s %s", log.directionPrefix(), log.Format)
|
f := fmt.Sprintf("%s %s", lo.directionPrefix(), lo.Format)
|
||||||
_ = l.err.Output(CallDepth, fmt.Sprintf(format, log.Messages...))
|
_ = l.err.Output(CallDepth, fmt.Sprintf(f, lo.Messages...))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
l := New(&b, LevelDebug)
|
l := New(&b, LevelDebug)
|
||||||
if l.level != LevelDebug {
|
if l.l != LevelDebug {
|
||||||
t.Error("Expected level to be LevelDebug, got ", l.level)
|
t.Error("Expected level to be LevelDebug, got ", l.l)
|
||||||
}
|
}
|
||||||
if l.err == nil || l.warn == nil || l.info == nil || l.debug == nil {
|
if l.err == nil || l.warn == nil || l.info == nil || l.debug == nil {
|
||||||
t.Error("Loggers not initialized")
|
t.Error("Loggers not initialized")
|
||||||
|
@ -37,7 +37,7 @@ func TestDebugf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelInfo
|
l.l = LevelInfo
|
||||||
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Debugf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Debug message was not expected to be logged")
|
t.Error("Debug message was not expected to be logged")
|
||||||
|
@ -60,7 +60,7 @@ func TestInfof(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelWarn
|
l.l = LevelWarn
|
||||||
l.Infof(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Infof(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Info message was not expected to be logged")
|
t.Error("Info message was not expected to be logged")
|
||||||
|
@ -83,7 +83,7 @@ func TestWarnf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelError
|
l.l = LevelError
|
||||||
l.Warnf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Warnf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Warn message was not expected to be logged")
|
t.Error("Warn message was not expected to be logged")
|
||||||
|
@ -106,7 +106,7 @@ func TestErrorf(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Reset()
|
b.Reset()
|
||||||
l.level = LevelError - 1
|
l.l = LevelError - 1
|
||||||
l.Errorf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
l.Errorf(Log{Direction: DirServerToClient, Format: "test %s", Messages: []interface{}{"foo"}})
|
||||||
if b.String() != "" {
|
if b.String() != "" {
|
||||||
t.Error("Error message was not expected to be logged")
|
t.Error("Error message was not expected to be logged")
|
||||||
|
|
14
msg_test.go
14
msg_test.go
|
@ -1251,7 +1251,7 @@ func TestMsg_SetBodyString(t *testing.T) {
|
||||||
}
|
}
|
||||||
part := m.parts[0]
|
part := m.parts[0]
|
||||||
res := bytes.Buffer{}
|
res := bytes.Buffer{}
|
||||||
if _, err := part.writeFunc(&res); err != nil && !tt.sf {
|
if _, err := part.w(&res); err != nil && !tt.sf {
|
||||||
t.Errorf("WriteFunc of part failed: %s", err)
|
t.Errorf("WriteFunc of part failed: %s", err)
|
||||||
}
|
}
|
||||||
if res.String() != tt.want {
|
if res.String() != tt.want {
|
||||||
|
@ -1286,7 +1286,7 @@ func TestMsg_AddAlternativeString(t *testing.T) {
|
||||||
}
|
}
|
||||||
apart := m.parts[1]
|
apart := m.parts[1]
|
||||||
res := bytes.Buffer{}
|
res := bytes.Buffer{}
|
||||||
if _, err := apart.writeFunc(&res); err != nil && !tt.sf {
|
if _, err := apart.w(&res); err != nil && !tt.sf {
|
||||||
t.Errorf("WriteFunc of part failed: %s", err)
|
t.Errorf("WriteFunc of part failed: %s", err)
|
||||||
}
|
}
|
||||||
if res.String() != tt.want {
|
if res.String() != tt.want {
|
||||||
|
@ -3174,7 +3174,7 @@ func TestMsg_checkUserAgent(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "check default user agent",
|
name: "check default user agent",
|
||||||
noDefaultUserAgent: false,
|
noDefaultUserAgent: false,
|
||||||
wantUserAgent: fmt.Sprintf("go-mail v%s // https://github.com/wneessen/go-mail", VERSION),
|
wantUserAgent: "go-mail v0.4.1 // https://github.com/wneessen/go-mail",
|
||||||
sf: false,
|
sf: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -3211,11 +3211,3 @@ func TestMsg_checkUserAgent(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNewMsgWithMIMEVersion tests WithMIMEVersion and Msg.SetMIMEVersion
|
|
||||||
func TestNewMsgWithNoDefaultUserAgent(t *testing.T) {
|
|
||||||
m := NewMsg(WithNoDefaultUserAgent())
|
|
||||||
if m.noDefaultUserAgent != true {
|
|
||||||
t.Errorf("WithNoDefaultUserAgent() failed. Expected: %t, got: %t", true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
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.isDeleted {
|
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.charset
|
pcs := p.cset
|
||||||
if partCharset.String() == "" {
|
if pcs.String() == "" {
|
||||||
partCharset = charset
|
pcs = cs
|
||||||
}
|
}
|
||||||
contentType := fmt.Sprintf("%s; charset=%s", part.contentType, partCharset)
|
ct := fmt.Sprintf("%s; charset=%s", p.ctype, pcs)
|
||||||
contentTransferEnc := part.encoding.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.description != "" {
|
if p.desc != "" {
|
||||||
mimeHeader.Add(string(HeaderContentDescription), part.description)
|
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.writeFunc, part.encoding)
|
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"`) {
|
||||||
|
|
66
part.go
66
part.go
|
@ -14,18 +14,18 @@ type PartOption func(*Part)
|
||||||
|
|
||||||
// Part is a part of the Msg
|
// Part is a part of the Msg
|
||||||
type Part struct {
|
type Part struct {
|
||||||
contentType ContentType
|
ctype ContentType
|
||||||
charset Charset
|
cset Charset
|
||||||
description string
|
desc string
|
||||||
encoding Encoding
|
enc Encoding
|
||||||
isDeleted bool
|
del bool
|
||||||
writeFunc func(io.Writer) (int64, error)
|
w func(io.Writer) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContent executes the WriteFunc of the Part and returns the content as byte slice
|
// GetContent executes the WriteFunc of the Part and returns the content as byte slice
|
||||||
func (p *Part) GetContent() ([]byte, error) {
|
func (p *Part) GetContent() ([]byte, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if _, err := p.writeFunc(&b); err != nil {
|
if _, err := p.w(&b); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return b.Bytes(), nil
|
return b.Bytes(), nil
|
||||||
|
@ -33,83 +33,83 @@ func (p *Part) GetContent() ([]byte, error) {
|
||||||
|
|
||||||
// GetCharset returns the currently set Charset of the Part
|
// GetCharset returns the currently set Charset of the Part
|
||||||
func (p *Part) GetCharset() Charset {
|
func (p *Part) GetCharset() Charset {
|
||||||
return p.charset
|
return p.cset
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContentType returns the currently set ContentType of the Part
|
// GetContentType returns the currently set ContentType of the Part
|
||||||
func (p *Part) GetContentType() ContentType {
|
func (p *Part) GetContentType() ContentType {
|
||||||
return p.contentType
|
return p.ctype
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEncoding returns the currently set Encoding of the Part
|
// GetEncoding returns the currently set Encoding of the Part
|
||||||
func (p *Part) GetEncoding() Encoding {
|
func (p *Part) GetEncoding() Encoding {
|
||||||
return p.encoding
|
return p.enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWriteFunc returns the currently set WriterFunc of the Part
|
// GetWriteFunc returns the currently set WriterFunc of the Part
|
||||||
func (p *Part) GetWriteFunc() func(io.Writer) (int64, error) {
|
func (p *Part) GetWriteFunc() func(io.Writer) (int64, error) {
|
||||||
return p.writeFunc
|
return p.w
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDescription returns the currently set Content-Description of the Part
|
// GetDescription returns the currently set Content-Description of the Part
|
||||||
func (p *Part) GetDescription() string {
|
func (p *Part) GetDescription() string {
|
||||||
return p.description
|
return p.desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContent overrides the content of the Part with the given string
|
// SetContent overrides the content of the Part with the given string
|
||||||
func (p *Part) SetContent(content string) {
|
func (p *Part) SetContent(c string) {
|
||||||
buffer := bytes.NewBufferString(content)
|
buf := bytes.NewBufferString(c)
|
||||||
p.writeFunc = writeFuncFromBuffer(buffer)
|
p.w = writeFuncFromBuffer(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContentType overrides the ContentType of the Part
|
// SetContentType overrides the ContentType of the Part
|
||||||
func (p *Part) SetContentType(contentType ContentType) {
|
func (p *Part) SetContentType(c ContentType) {
|
||||||
p.contentType = contentType
|
p.ctype = c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCharset overrides the Charset of the Part
|
// SetCharset overrides the Charset of the Part
|
||||||
func (p *Part) SetCharset(charset Charset) {
|
func (p *Part) SetCharset(c Charset) {
|
||||||
p.charset = charset
|
p.cset = c
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message
|
// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
func (p *Part) SetEncoding(encoding Encoding) {
|
func (p *Part) SetEncoding(e Encoding) {
|
||||||
p.encoding = encoding
|
p.enc = e
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDescription overrides the Content-Description of the Part
|
// SetDescription overrides the Content-Description of the Part
|
||||||
func (p *Part) SetDescription(description string) {
|
func (p *Part) SetDescription(d string) {
|
||||||
p.description = description
|
p.desc = d
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteFunc overrides the WriteFunc of the Part
|
// SetWriteFunc overrides the WriteFunc of the Part
|
||||||
func (p *Part) SetWriteFunc(writeFunc func(io.Writer) (int64, error)) {
|
func (p *Part) SetWriteFunc(w func(io.Writer) (int64, error)) {
|
||||||
p.writeFunc = writeFunc
|
p.w = w
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the current part from the parts list of the Msg by setting the
|
// Delete removes the current part from the parts list of the Msg by setting the
|
||||||
// isDeleted flag to true. The msgWriter will skip it then
|
// del flag to true. The msgWriter will skip it then
|
||||||
func (p *Part) Delete() {
|
func (p *Part) Delete() {
|
||||||
p.isDeleted = true
|
p.del = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPartCharset overrides the default Part charset
|
// WithPartCharset overrides the default Part charset
|
||||||
func WithPartCharset(charset Charset) PartOption {
|
func WithPartCharset(c Charset) PartOption {
|
||||||
return func(p *Part) {
|
return func(p *Part) {
|
||||||
p.charset = charset
|
p.cset = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPartEncoding overrides the default Part encoding
|
// WithPartEncoding overrides the default Part encoding
|
||||||
func WithPartEncoding(encoding Encoding) PartOption {
|
func WithPartEncoding(e Encoding) PartOption {
|
||||||
return func(p *Part) {
|
return func(p *Part) {
|
||||||
p.encoding = encoding
|
p.enc = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPartContentDescription overrides the default Part Content-Description
|
// WithPartContentDescription overrides the default Part Content-Description
|
||||||
func WithPartContentDescription(description string) PartOption {
|
func WithPartContentDescription(d string) PartOption {
|
||||||
return func(p *Part) {
|
return func(p *Part) {
|
||||||
p.description = description
|
p.desc = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
part_test.go
34
part_test.go
|
@ -31,15 +31,15 @@ func TestPartEncoding(t *testing.T) {
|
||||||
t.Errorf("newPart() WithPartEncoding() failed: no part returned")
|
t.Errorf("newPart() WithPartEncoding() failed: no part returned")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if part.encoding.String() != tt.want {
|
if part.enc.String() != tt.want {
|
||||||
t.Errorf("newPart() WithPartEncoding() failed: expected encoding: %s, got: %s", tt.want,
|
t.Errorf("newPart() WithPartEncoding() failed: expected encoding: %s, got: %s", tt.want,
|
||||||
part.encoding.String())
|
part.enc.String())
|
||||||
}
|
}
|
||||||
part.encoding = ""
|
part.enc = ""
|
||||||
part.SetEncoding(tt.enc)
|
part.SetEncoding(tt.enc)
|
||||||
if part.encoding.String() != tt.want {
|
if part.enc.String() != tt.want {
|
||||||
t.Errorf("newPart() SetEncoding() failed: expected encoding: %s, got: %s", tt.want,
|
t.Errorf("newPart() SetEncoding() failed: expected encoding: %s, got: %s", tt.want,
|
||||||
part.encoding.String())
|
part.enc.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,9 @@ func TestWithPartCharset(t *testing.T) {
|
||||||
t.Errorf("newPart() WithPartCharset() failed: no part returned")
|
t.Errorf("newPart() WithPartCharset() failed: no part returned")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if part.charset.String() != tt.want {
|
if part.cset.String() != tt.want {
|
||||||
t.Errorf("newPart() WithPartCharset() failed: expected charset: %s, got: %s",
|
t.Errorf("newPart() WithPartCharset() failed: expected charset: %s, got: %s",
|
||||||
tt.want, part.charset.String())
|
tt.want, part.cset.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -89,14 +89,14 @@ func TestPart_WithPartContentDescription(t *testing.T) {
|
||||||
t.Errorf("newPart() WithPartContentDescription() failed: no part returned")
|
t.Errorf("newPart() WithPartContentDescription() failed: no part returned")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if part.description != tt.desc {
|
if part.desc != tt.desc {
|
||||||
t.Errorf("newPart() WithPartContentDescription() failed: expected: %s, got: %s", tt.desc,
|
t.Errorf("newPart() WithPartContentDescription() failed: expected: %s, got: %s", tt.desc,
|
||||||
part.description)
|
part.desc)
|
||||||
}
|
}
|
||||||
part.description = ""
|
part.desc = ""
|
||||||
part.SetDescription(tt.desc)
|
part.SetDescription(tt.desc)
|
||||||
if part.description != tt.desc {
|
if part.desc != tt.desc {
|
||||||
t.Errorf("newPart() SetDescription() failed: expected: %s, got: %s", tt.desc, part.description)
|
t.Errorf("newPart() SetDescription() failed: expected: %s, got: %s", tt.desc, part.desc)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,7 @@ func TestPart_GetContentBroken(t *testing.T) {
|
||||||
t.Errorf("failed: %s", err)
|
t.Errorf("failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pl[0].writeFunc = func(io.Writer) (int64, error) {
|
pl[0].w = func(io.Writer) (int64, error) {
|
||||||
return 0, fmt.Errorf("broken")
|
return 0, fmt.Errorf("broken")
|
||||||
}
|
}
|
||||||
_, err = pl[0].GetContent()
|
_, err = pl[0].GetContent()
|
||||||
|
@ -314,8 +314,8 @@ func TestPart_SetDescription(t *testing.T) {
|
||||||
t.Errorf("Part.GetDescription failed. Expected empty description but got: %s", pd)
|
t.Errorf("Part.GetDescription failed. Expected empty description but got: %s", pd)
|
||||||
}
|
}
|
||||||
pl[0].SetDescription(d)
|
pl[0].SetDescription(d)
|
||||||
if pl[0].description != d {
|
if pl[0].desc != d {
|
||||||
t.Errorf("Part.SetDescription failed. Expected description to be: %s, got: %s", d, pd)
|
t.Errorf("Part.SetDescription failed. Expected desc to be: %s, got: %s", d, pd)
|
||||||
}
|
}
|
||||||
pd = pl[0].GetDescription()
|
pd = pl[0].GetDescription()
|
||||||
if pd != d {
|
if pd != d {
|
||||||
|
@ -334,8 +334,8 @@ func TestPart_Delete(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pl[0].Delete()
|
pl[0].Delete()
|
||||||
if !pl[0].isDeleted {
|
if !pl[0].del {
|
||||||
t.Errorf("Delete failed. Expected: %t, got: %t", true, pl[0].isDeleted)
|
t.Errorf("Delete failed. Expected: %t, got: %t", true, pl[0].del)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
63
random.go
63
random.go
|
@ -22,53 +22,54 @@ const (
|
||||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||||
)
|
)
|
||||||
|
|
||||||
// randomStringSecure returns a random, string of length characters. This method uses the
|
// randomStringSecure returns a random, n long string of characters. The character set is based
|
||||||
|
// on the s (special chars) and h (human readable) boolean arguments. This method uses the
|
||||||
// crypto/random package and therfore is cryptographically secure
|
// crypto/random package and therfore is cryptographically secure
|
||||||
func randomStringSecure(length int) (string, error) {
|
func randomStringSecure(n int) (string, error) {
|
||||||
randString := strings.Builder{}
|
rs := strings.Builder{}
|
||||||
randString.Grow(length)
|
rs.Grow(n)
|
||||||
charRangeLength := len(cr)
|
crl := len(cr)
|
||||||
|
|
||||||
randPool := make([]byte, 8)
|
rp := make([]byte, 8)
|
||||||
_, err := rand.Read(randPool)
|
_, err := rand.Read(rp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return randString.String(), err
|
return rs.String(), err
|
||||||
}
|
}
|
||||||
for idx, char, rest := length-1, binary.BigEndian.Uint64(randPool), letterIdxMax; idx >= 0; {
|
for i, c, r := n-1, binary.BigEndian.Uint64(rp), letterIdxMax; i >= 0; {
|
||||||
if rest == 0 {
|
if r == 0 {
|
||||||
_, err = rand.Read(randPool)
|
_, err := rand.Read(rp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return randString.String(), err
|
return rs.String(), err
|
||||||
}
|
}
|
||||||
char, rest = binary.BigEndian.Uint64(randPool), letterIdxMax
|
c, r = binary.BigEndian.Uint64(rp), letterIdxMax
|
||||||
}
|
}
|
||||||
if i := int(char & letterIdxMask); i < charRangeLength {
|
if idx := int(c & letterIdxMask); idx < crl {
|
||||||
randString.WriteByte(cr[i])
|
rs.WriteByte(cr[idx])
|
||||||
idx--
|
i--
|
||||||
}
|
}
|
||||||
char >>= letterIdxBits
|
c >>= letterIdxBits
|
||||||
rest--
|
r--
|
||||||
}
|
}
|
||||||
|
|
||||||
return randString.String(), nil
|
return rs.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// randNum returns a random number with a maximum value of length
|
// randNum returns a random number with a maximum value of n
|
||||||
func randNum(length int) (int, error) {
|
func randNum(n int) (int, error) {
|
||||||
if length <= 0 {
|
if n <= 0 {
|
||||||
return 0, fmt.Errorf("provided number is <= 0: %d", length)
|
return 0, fmt.Errorf("provided number is <= 0: %d", n)
|
||||||
}
|
}
|
||||||
length64 := big.NewInt(int64(length))
|
mbi := big.NewInt(int64(n))
|
||||||
if !length64.IsUint64() {
|
if !mbi.IsUint64() {
|
||||||
return 0, fmt.Errorf("big.NewInt() generation returned negative value: %d", length64)
|
return 0, fmt.Errorf("big.NewInt() generation returned negative value: %d", mbi)
|
||||||
}
|
}
|
||||||
randNum64, err := rand.Int(rand.Reader, length64)
|
rn64, err := rand.Int(rand.Reader, mbi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
randomNum := int(randNum64.Int64())
|
rn := int(rn64.Int64())
|
||||||
if randomNum < 0 {
|
if rn < 0 {
|
||||||
return 0, fmt.Errorf("generated random number does not fit as int64: %d", randNum64)
|
return 0, fmt.Errorf("generated random number does not fit as int64: %d", rn64)
|
||||||
}
|
}
|
||||||
return randomNum, nil
|
return rn, nil
|
||||||
}
|
}
|
||||||
|
|
22
reader.go
22
reader.go
|
@ -10,9 +10,9 @@ import (
|
||||||
|
|
||||||
// Reader is a type that implements the io.Reader interface for a Msg
|
// Reader is a type that implements the io.Reader interface for a Msg
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
buffer []byte // contents are the bytes buffer[offset : len(buffer)]
|
buf []byte // contents are the bytes buf[off : len(buf)]
|
||||||
offset int // read at &buffer[offset], write at &buffer[len(buffer)]
|
off int // read at &buf[off], write at &buf[len(buf)]
|
||||||
err error // initialization error
|
err error // initialization error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns an error if the Reader err field is not nil
|
// Error returns an error if the Reader err field is not nil
|
||||||
|
@ -21,28 +21,28 @@ func (r *Reader) Error() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads the length of p of the Msg buffer to satisfy the io.Reader interface
|
// Read reads the length of p of the Msg buffer to satisfy the io.Reader interface
|
||||||
func (r *Reader) Read(payload []byte) (n int, err error) {
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
if r.empty() || r.buffer == nil {
|
if r.empty() || r.buf == nil {
|
||||||
r.Reset()
|
r.Reset()
|
||||||
if len(payload) == 0 {
|
if len(p) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
n = copy(payload, r.buffer[r.offset:])
|
n = copy(p, r.buf[r.off:])
|
||||||
r.offset += n
|
r.off += n
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets the Reader buffer to be empty, but it retains the underlying storage
|
// Reset resets the Reader buffer to be empty, but it retains the underlying storage
|
||||||
// for use by future writes.
|
// for use by future writes.
|
||||||
func (r *Reader) Reset() {
|
func (r *Reader) Reset() {
|
||||||
r.buffer = r.buffer[:0]
|
r.buf = r.buf[:0]
|
||||||
r.offset = 0
|
r.off = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty reports whether the unread portion of the Reader buffer is empty.
|
// empty reports whether the unread portion of the Reader buffer is empty.
|
||||||
func (r *Reader) empty() bool { return len(r.buffer) <= r.offset }
|
func (r *Reader) empty() bool { return len(r.buf) <= r.off }
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestReader_Read_error(t *testing.T) {
|
||||||
|
|
||||||
// TestReader_Read_empty tests the Reader.Read method with an empty buffer
|
// TestReader_Read_empty tests the Reader.Read method with an empty buffer
|
||||||
func TestReader_Read_empty(t *testing.T) {
|
func TestReader_Read_empty(t *testing.T) {
|
||||||
r := Reader{buffer: []byte{}}
|
r := Reader{buf: []byte{}}
|
||||||
p := make([]byte, 1)
|
p := make([]byte, 1)
|
||||||
p[0] = 'a'
|
p[0] = 'a'
|
||||||
_, err := r.Read(p)
|
_, err := r.Read(p)
|
||||||
|
@ -76,7 +76,7 @@ func TestReader_Read_empty(t *testing.T) {
|
||||||
|
|
||||||
// TestReader_Read_nil tests the Reader.Read method with a nil buffer
|
// TestReader_Read_nil tests the Reader.Read method with a nil buffer
|
||||||
func TestReader_Read_nil(t *testing.T) {
|
func TestReader_Read_nil(t *testing.T) {
|
||||||
r := Reader{buffer: nil, offset: -10}
|
r := Reader{buf: nil, off: -10}
|
||||||
p := make([]byte, 0)
|
p := make([]byte, 0)
|
||||||
_, err := r.Read(p)
|
_, err := r.Read(p)
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
|
28
senderror.go
28
senderror.go
|
@ -71,34 +71,34 @@ func (e *SendError) Error() string {
|
||||||
return "unknown reason"
|
return "unknown reason"
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMessage strings.Builder
|
var em strings.Builder
|
||||||
errMessage.WriteString(e.Reason.String())
|
em.WriteString(e.Reason.String())
|
||||||
if len(e.errlist) > 0 {
|
if len(e.errlist) > 0 {
|
||||||
errMessage.WriteRune(':')
|
em.WriteRune(':')
|
||||||
for i := range e.errlist {
|
for i := range e.errlist {
|
||||||
errMessage.WriteRune(' ')
|
em.WriteRune(' ')
|
||||||
errMessage.WriteString(e.errlist[i].Error())
|
em.WriteString(e.errlist[i].Error())
|
||||||
if i != len(e.errlist)-1 {
|
if i != len(e.errlist)-1 {
|
||||||
errMessage.WriteString(", ")
|
em.WriteString(", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(e.rcpt) > 0 {
|
if len(e.rcpt) > 0 {
|
||||||
errMessage.WriteString(", affected recipient(s): ")
|
em.WriteString(", affected recipient(s): ")
|
||||||
for i := range e.rcpt {
|
for i := range e.rcpt {
|
||||||
errMessage.WriteString(e.rcpt[i])
|
em.WriteString(e.rcpt[i])
|
||||||
if i != len(e.rcpt)-1 {
|
if i != len(e.rcpt)-1 {
|
||||||
errMessage.WriteString(", ")
|
em.WriteString(", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errMessage.String()
|
return em.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is implements the errors.Is functionality and compares the SendErrReason
|
// Is implements the errors.Is functionality and compares the SendErrReason
|
||||||
func (e *SendError) Is(errType error) bool {
|
func (e *SendError) Is(et error) bool {
|
||||||
var t *SendError
|
var t *SendError
|
||||||
if errors.As(errType, &t) && t != nil {
|
if errors.As(et, &t) && t != nil {
|
||||||
return e.Reason == t.Reason && e.isTemp == t.isTemp
|
return e.Reason == t.Reason && e.isTemp == t.isTemp
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -143,6 +143,6 @@ func (r SendErrReason) String() string {
|
||||||
|
|
||||||
// isTempError checks the given SMTP error and returns true if the given error is of temporary nature
|
// isTempError checks the given SMTP error and returns true if the given error is of temporary nature
|
||||||
// and should be retried
|
// and should be retried
|
||||||
func isTempError(err error) bool {
|
func isTempError(e error) bool {
|
||||||
return err.Error()[0] == '4'
|
return e.Error()[0] == '4'
|
||||||
}
|
}
|
||||||
|
|
8
tls.go
8
tls.go
|
@ -8,17 +8,17 @@ package mail
|
||||||
type TLSPolicy int
|
type TLSPolicy int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TLSMandatory requires that the connection to the server is
|
// TLSMandatory requires that the connection cto the server is
|
||||||
// encrypting using STARTTLS. If the server does not support STARTTLS
|
// encrypting using STARTTLS. If the server does not support STARTTLS
|
||||||
// the connection will be terminated with an error
|
// the connection will be terminated with an error
|
||||||
TLSMandatory TLSPolicy = iota
|
TLSMandatory TLSPolicy = iota
|
||||||
|
|
||||||
// TLSOpportunistic tries to establish an encrypted connection via the
|
// TLSOpportunistic tries cto establish an encrypted connection via the
|
||||||
// STARTTLS protocol. If the server does not support this, it will fall
|
// STARTTLS protocol. If the server does not support this, it will fall
|
||||||
// back to non-encrypted plaintext transmission
|
// back cto non-encrypted plaintext transmission
|
||||||
TLSOpportunistic
|
TLSOpportunistic
|
||||||
|
|
||||||
// NoTLS forces the transaction to be not encrypted
|
// NoTLS forces the transaction cto be not encrypted
|
||||||
NoTLS
|
NoTLS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue