mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
Compare commits
No commits in common. "e1098091c347f9a814adf250f83910b3d70edd88" and "4e880ab31c0895c6d05256af69247fe32f9b8392" have entirely different histories.
e1098091c3
...
4e880ab31c
7 changed files with 403 additions and 417 deletions
|
@ -20,39 +20,39 @@ type Base64LineBreaker struct {
|
|||
out io.Writer
|
||||
}
|
||||
|
||||
var newlineBytes = []byte(SingleNewLine)
|
||||
var nl = []byte(SingleNewLine)
|
||||
|
||||
// Write writes the data stream and inserts a SingleNewLine when the maximum
|
||||
// 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 {
|
||||
err = fmt.Errorf(ErrNoOutWriter)
|
||||
return
|
||||
}
|
||||
if l.used+len(data) < MaxBodyLength {
|
||||
copy(l.line[l.used:], data)
|
||||
l.used += len(data)
|
||||
return len(data), nil
|
||||
if l.used+len(b) < MaxBodyLength {
|
||||
copy(l.line[l.used:], b)
|
||||
l.used += len(b)
|
||||
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 {
|
||||
return
|
||||
}
|
||||
excess := MaxBodyLength - l.used
|
||||
l.used = 0
|
||||
|
||||
numBytes, err = l.out.Write(data[0:excess])
|
||||
n, err = l.out.Write(b[0:excess])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
numBytes, err = l.out.Write(newlineBytes)
|
||||
n, err = l.out.Write(nl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return l.Write(data[excess:])
|
||||
return l.Write(b[excess:])
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
_, err = l.out.Write(newlineBytes)
|
||||
_, err = l.out.Write(nl)
|
||||
}
|
||||
|
||||
return
|
||||
|
|
280
client.go
280
client.go
|
@ -87,11 +87,11 @@ type DialContextFunc func(ctx context.Context, network, address string) (net.Con
|
|||
|
||||
// Client is the SMTP client struct
|
||||
type Client struct {
|
||||
// connection is the net.Conn that the smtp.Client is based on
|
||||
connection net.Conn
|
||||
// co is the net.Conn that the smtp.Client is based on
|
||||
co net.Conn
|
||||
|
||||
// Timeout for the SMTP server connection
|
||||
connTimeout time.Duration
|
||||
cto time.Duration
|
||||
|
||||
// dsn indicates that we want to use DSN for the Client
|
||||
dsn bool
|
||||
|
@ -102,8 +102,8 @@ type Client struct {
|
|||
// dsnrntype defines the DSNRcptNotifyOption in case DSN is enabled
|
||||
dsnrntype []string
|
||||
|
||||
// isEncrypted indicates if a Client connection is encrypted or not
|
||||
isEncrypted bool
|
||||
// enc indicates if a Client connection is encrypted or not
|
||||
enc bool
|
||||
|
||||
// noNoop indicates the Noop is to be skipped
|
||||
noNoop bool
|
||||
|
@ -121,17 +121,17 @@ type Client struct {
|
|||
port int
|
||||
fallbackPort int
|
||||
|
||||
// smtpAuth is a pointer to smtp.Auth
|
||||
smtpAuth smtp.Auth
|
||||
// sa is a pointer to smtp.Auth
|
||||
sa smtp.Auth
|
||||
|
||||
// smtpAuthType represents the authentication type for SMTP AUTH
|
||||
smtpAuthType SMTPAuthType
|
||||
// satype represents the authentication type for SMTP AUTH
|
||||
satype SMTPAuthType
|
||||
|
||||
// smtpClient is the smtp.Client that is set up when using the Dial*() methods
|
||||
smtpClient *smtp.Client
|
||||
// sc is the smtp.Client that is set up when using the Dial*() methods
|
||||
sc *smtp.Client
|
||||
|
||||
// Use SSL for the connection
|
||||
useSSL bool
|
||||
ssl bool
|
||||
|
||||
// tlspolicy sets the client to use the provided TLSPolicy for the STARTTLS protocol
|
||||
tlspolicy TLSPolicy
|
||||
|
@ -142,11 +142,11 @@ type Client struct {
|
|||
// user is the SMTP AUTH username
|
||||
user string
|
||||
|
||||
// useDebugLog enables the debug logging on the SMTP client
|
||||
useDebugLog bool
|
||||
// dl enables the debug logging on the SMTP client
|
||||
dl bool
|
||||
|
||||
// logger is a logger that implements the log.Logger interface
|
||||
logger log.Logger
|
||||
// l is a logger that implements the log.Logger interface
|
||||
l log.Logger
|
||||
|
||||
// dialContextFunc is a custom DialContext function to dial target SMTP server
|
||||
dialContextFunc DialContextFunc
|
||||
|
@ -198,12 +198,12 @@ var (
|
|||
)
|
||||
|
||||
// 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{
|
||||
connTimeout: DefaultTimeout,
|
||||
host: host,
|
||||
cto: DefaultTimeout,
|
||||
host: h,
|
||||
port: DefaultPort,
|
||||
tlsconfig: &tls.Config{ServerName: host, MinVersion: DefaultTLSMinVersion},
|
||||
tlsconfig: &tls.Config{ServerName: h, MinVersion: DefaultTLSMinVersion},
|
||||
tlspolicy: DefaultTLSPolicy,
|
||||
}
|
||||
|
||||
|
@ -213,11 +213,11 @@ func NewClient(host string, opts ...Option) (*Client, error) {
|
|||
}
|
||||
|
||||
// Override defaults with optionally provided Option functions
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
for _, co := range o {
|
||||
if co == nil {
|
||||
continue
|
||||
}
|
||||
if err := opt(c); err != nil {
|
||||
if err := co(c); err != nil {
|
||||
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
|
||||
func WithPort(port int) Option {
|
||||
func WithPort(p int) Option {
|
||||
return func(c *Client) error {
|
||||
if port < 1 || port > 65535 {
|
||||
if p < 1 || p > 65535 {
|
||||
return ErrInvalidPort
|
||||
}
|
||||
c.port = port
|
||||
c.port = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout overrides the default connection timeout
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
func WithTimeout(t time.Duration) Option {
|
||||
return func(c *Client) error {
|
||||
if timeout <= 0 {
|
||||
if t <= 0 {
|
||||
return ErrInvalidTimeout
|
||||
}
|
||||
c.connTimeout = timeout
|
||||
c.cto = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ func WithTimeout(timeout time.Duration) Option {
|
|||
// Deprecated: use WithSSLPort instead.
|
||||
func WithSSL() Option {
|
||||
return func(c *Client) error {
|
||||
c.useSSL = true
|
||||
c.ssl = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -267,9 +267,9 @@ func WithSSL() Option {
|
|||
//
|
||||
// When the SSL connection fails and fallback is set to true,
|
||||
// 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 {
|
||||
c.SetSSLPort(true, fallback)
|
||||
c.SetSSLPort(true, fb)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -278,26 +278,26 @@ func WithSSLPort(fallback bool) Option {
|
|||
// to StdErr
|
||||
func WithDebugLog() Option {
|
||||
return func(c *Client) error {
|
||||
c.useDebugLog = true
|
||||
c.dl = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.logger = logger
|
||||
c.l = l
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if helo == "" {
|
||||
if h == "" {
|
||||
return ErrInvalidHELO
|
||||
}
|
||||
c.helo = helo
|
||||
c.helo = h
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -305,9 +305,9 @@ func WithHELO(helo string) Option {
|
|||
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
||||
//
|
||||
// Deprecated: use WithTLSPortPolicy instead.
|
||||
func WithTLSPolicy(policy TLSPolicy) Option {
|
||||
func WithTLSPolicy(p TLSPolicy) Option {
|
||||
return func(c *Client) error {
|
||||
c.tlspolicy = policy
|
||||
c.tlspolicy = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -319,52 +319,52 @@ func WithTLSPolicy(policy TLSPolicy) Option {
|
|||
// If the connection fails with TLSOpportunistic,
|
||||
// a plaintext connection is attempted on port 25 as a fallback.
|
||||
// NoTLS will allways use port 25.
|
||||
func WithTLSPortPolicy(policy TLSPolicy) Option {
|
||||
func WithTLSPortPolicy(p TLSPolicy) Option {
|
||||
return func(c *Client) error {
|
||||
c.SetTLSPortPolicy(policy)
|
||||
c.SetTLSPortPolicy(p)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if tlsconfig == nil {
|
||||
if co == nil {
|
||||
return ErrInvalidTLSConfig
|
||||
}
|
||||
c.tlsconfig = tlsconfig
|
||||
c.tlsconfig = co
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.smtpAuthType = authtype
|
||||
c.satype = t
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.smtpAuth = smtpAuth
|
||||
c.sa = a
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.user = username
|
||||
c.user = u
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.pass = password
|
||||
c.pass = p
|
||||
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
|
||||
// given DSNMailReturnOption
|
||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||
func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
||||
func WithDSNMailReturnType(mro DSNMailReturnOption) Option {
|
||||
return func(c *Client) error {
|
||||
switch option {
|
||||
switch mro {
|
||||
case DSNMailReturnHeadersOnly:
|
||||
case DSNMailReturnFull:
|
||||
default:
|
||||
|
@ -396,7 +396,7 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
|||
}
|
||||
|
||||
c.dsn = true
|
||||
c.dsnmrtype = option
|
||||
c.dsnmrtype = mro
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -404,13 +404,13 @@ func WithDSNMailReturnType(option DSNMailReturnOption) Option {
|
|||
// 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
|
||||
// See: https://www.rfc-editor.org/rfc/rfc1891
|
||||
func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
||||
func WithDSNRcptNotifyType(rno ...DSNRcptNotifyOption) Option {
|
||||
return func(c *Client) error {
|
||||
var rcptOpts []string
|
||||
var rnol []string
|
||||
var ns, nns bool
|
||||
if len(opts) > 0 {
|
||||
for _, opt := range opts {
|
||||
switch opt {
|
||||
if len(rno) > 0 {
|
||||
for _, crno := range rno {
|
||||
switch crno {
|
||||
case DSNRcptNotifyNever:
|
||||
ns = true
|
||||
case DSNRcptNotifySuccess:
|
||||
|
@ -422,7 +422,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
|||
default:
|
||||
return ErrInvalidDSNRcptNotifyOption
|
||||
}
|
||||
rcptOpts = append(rcptOpts, string(opt))
|
||||
rnol = append(rnol, string(crno))
|
||||
}
|
||||
}
|
||||
if ns && nns {
|
||||
|
@ -430,7 +430,7 @@ func WithDSNRcptNotifyType(opts ...DSNRcptNotifyOption) Option {
|
|||
}
|
||||
|
||||
c.dsn = true
|
||||
c.dsnrntype = rcptOpts
|
||||
c.dsnrntype = rnol
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -445,9 +445,9 @@ func WithoutNoop() Option {
|
|||
}
|
||||
|
||||
// WithDialContextFunc overrides the default DialContext for connecting SMTP server
|
||||
func WithDialContextFunc(dialCtxFunc DialContextFunc) Option {
|
||||
func WithDialContextFunc(f DialContextFunc) Option {
|
||||
return func(c *Client) error {
|
||||
c.dialContextFunc = dialCtxFunc
|
||||
c.dialContextFunc = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -463,8 +463,8 @@ func (c *Client) ServerAddr() string {
|
|||
}
|
||||
|
||||
// SetTLSPolicy overrides the current TLSPolicy with the given TLSPolicy value
|
||||
func (c *Client) SetTLSPolicy(policy TLSPolicy) {
|
||||
c.tlspolicy = policy
|
||||
func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
||||
c.tlspolicy = p
|
||||
}
|
||||
|
||||
// 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
|
||||
// attempted on port 25 as a fallback.
|
||||
// NoTLS will allways use port 25.
|
||||
func (c *Client) SetTLSPortPolicy(policy TLSPolicy) {
|
||||
func (c *Client) SetTLSPortPolicy(p TLSPolicy) {
|
||||
c.port = DefaultPortTLS
|
||||
|
||||
if policy == TLSOpportunistic {
|
||||
if p == TLSOpportunistic {
|
||||
c.fallbackPort = DefaultPort
|
||||
}
|
||||
if policy == NoTLS {
|
||||
if p == NoTLS {
|
||||
c.port = DefaultPort
|
||||
}
|
||||
|
||||
c.tlspolicy = policy
|
||||
c.tlspolicy = p
|
||||
}
|
||||
|
||||
// SetSSL tells the Client wether to use SSL or not
|
||||
func (c *Client) SetSSL(ssl bool) {
|
||||
c.useSSL = ssl
|
||||
func (c *Client) SetSSL(s bool) {
|
||||
c.ssl = s
|
||||
}
|
||||
|
||||
// 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).
|
||||
// When the SSL connection fails and fb is set to true,
|
||||
// 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
|
||||
if ssl {
|
||||
c.port = DefaultPortSSL
|
||||
}
|
||||
|
||||
c.fallbackPort = 0
|
||||
if fallback {
|
||||
if fb {
|
||||
c.fallbackPort = DefaultPort
|
||||
}
|
||||
|
||||
c.useSSL = ssl
|
||||
c.ssl = ssl
|
||||
}
|
||||
|
||||
// SetDebugLog tells the Client whether debug logging is enabled or not
|
||||
func (c *Client) SetDebugLog(val bool) {
|
||||
c.useDebugLog = val
|
||||
if c.smtpClient != nil {
|
||||
c.smtpClient.SetDebugLog(val)
|
||||
func (c *Client) SetDebugLog(v bool) {
|
||||
c.dl = v
|
||||
if c.sc != nil {
|
||||
c.sc.SetDebugLog(v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogger tells the Client which log.Logger to use
|
||||
func (c *Client) SetLogger(logger log.Logger) {
|
||||
c.logger = logger
|
||||
if c.smtpClient != nil {
|
||||
c.smtpClient.SetLogger(logger)
|
||||
func (c *Client) SetLogger(l log.Logger) {
|
||||
c.l = l
|
||||
if c.sc != nil {
|
||||
c.sc.SetLogger(l)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTLSConfig overrides the current *tls.Config with the given *tls.Config value
|
||||
func (c *Client) SetTLSConfig(tlsconfig *tls.Config) error {
|
||||
if tlsconfig == nil {
|
||||
func (c *Client) SetTLSConfig(co *tls.Config) error {
|
||||
if co == nil {
|
||||
return ErrInvalidTLSConfig
|
||||
}
|
||||
c.tlsconfig = tlsconfig
|
||||
c.tlsconfig = co
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUsername overrides the current username string with the given value
|
||||
func (c *Client) SetUsername(username string) {
|
||||
c.user = username
|
||||
func (c *Client) SetUsername(u string) {
|
||||
c.user = u
|
||||
}
|
||||
|
||||
// SetPassword overrides the current password string with the given value
|
||||
func (c *Client) SetPassword(password string) {
|
||||
c.pass = password
|
||||
func (c *Client) SetPassword(p string) {
|
||||
c.pass = p
|
||||
}
|
||||
|
||||
// SetSMTPAuth overrides the current SMTP AUTH type setting with the given value
|
||||
func (c *Client) SetSMTPAuth(authtype SMTPAuthType) {
|
||||
c.smtpAuthType = authtype
|
||||
func (c *Client) SetSMTPAuth(a SMTPAuthType) {
|
||||
c.satype = a
|
||||
}
|
||||
|
||||
// SetSMTPAuthCustom overrides the current SMTP AUTH setting with the given custom smtp.Auth
|
||||
func (c *Client) SetSMTPAuthCustom(smtpAuth smtp.Auth) {
|
||||
c.smtpAuth = smtpAuth
|
||||
func (c *Client) SetSMTPAuthCustom(sa smtp.Auth) {
|
||||
c.sa = sa
|
||||
}
|
||||
|
||||
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
|
||||
func (c *Client) setDefaultHelo() error {
|
||||
hostname, err := os.Hostname()
|
||||
hn, err := os.Hostname()
|
||||
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
|
||||
}
|
||||
|
||||
// DialWithContext establishes a connection to the SMTP server with a given context.Context
|
||||
func (c *Client) DialWithContext(dialCtx context.Context) error {
|
||||
ctx, cancel := context.WithDeadline(dialCtx, time.Now().Add(c.connTimeout))
|
||||
defer cancel()
|
||||
// DialWithContext establishes a connection cto the SMTP server with a given context.Context
|
||||
func (c *Client) DialWithContext(pc context.Context) error {
|
||||
ctx, cfn := context.WithDeadline(pc, time.Now().Add(c.cto))
|
||||
defer cfn()
|
||||
|
||||
if c.dialContextFunc == nil {
|
||||
netDialer := net.Dialer{}
|
||||
c.dialContextFunc = netDialer.DialContext
|
||||
nd := net.Dialer{}
|
||||
c.dialContextFunc = nd.DialContext
|
||||
|
||||
if c.useSSL {
|
||||
tlsDialer := tls.Dialer{NetDialer: &netDialer, Config: c.tlsconfig}
|
||||
c.isEncrypted = true
|
||||
c.dialContextFunc = tlsDialer.DialContext
|
||||
if c.ssl {
|
||||
td := tls.Dialer{NetDialer: &nd, Config: c.tlsconfig}
|
||||
c.enc = true
|
||||
c.dialContextFunc = td.DialContext
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := smtp.NewClient(c.connection, c.host)
|
||||
sc, err := smtp.NewClient(c.co, c.host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if client == nil {
|
||||
if sc == nil {
|
||||
return fmt.Errorf("SMTP client is nil")
|
||||
}
|
||||
c.smtpClient = client
|
||||
c.sc = sc
|
||||
|
||||
if c.logger != nil {
|
||||
c.smtpClient.SetLogger(c.logger)
|
||||
if c.l != nil {
|
||||
c.sc.SetLogger(c.l)
|
||||
}
|
||||
if c.useDebugLog {
|
||||
c.smtpClient.SetDebugLog(true)
|
||||
if c.dl {
|
||||
c.sc.SetDebugLog(true)
|
||||
}
|
||||
if err = c.smtpClient.Hello(c.helo); err != nil {
|
||||
if err := c.sc.Hello(c.helo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.tls(); err != nil {
|
||||
if err := c.tls(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.auth(); err != nil {
|
||||
if err := c.auth(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -628,7 +628,7 @@ func (c *Client) Close() error {
|
|||
if err := c.checkConn(); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -640,7 +640,7 @@ func (c *Client) Reset() error {
|
|||
if err := c.checkConn(); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -672,17 +672,17 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, ml ...*Msg) error {
|
|||
// checkConn makes sure that a required server connection is available and extends the
|
||||
// connection deadline
|
||||
func (c *Client) checkConn() error {
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
return ErrNoActiveConnection
|
||||
}
|
||||
|
||||
if !c.noNoop {
|
||||
if err := c.smtpClient.Noop(); err != nil {
|
||||
if err := c.sc.Noop(); err != nil {
|
||||
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 nil
|
||||
|
@ -696,12 +696,12 @@ func (c *Client) serverFallbackAddr() string {
|
|||
|
||||
// tls tries to make sure that the STARTTLS requirements are satisfied
|
||||
func (c *Client) tls() error {
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
return ErrNoActiveConnection
|
||||
}
|
||||
if !c.useSSL && c.tlspolicy != NoTLS {
|
||||
if !c.ssl && c.tlspolicy != NoTLS {
|
||||
est := false
|
||||
st, _ := c.smtpClient.Extension("STARTTLS")
|
||||
st, _ := c.sc.Extension("STARTTLS")
|
||||
if c.tlspolicy == TLSMandatory {
|
||||
est = true
|
||||
if !st {
|
||||
|
@ -715,11 +715,11 @@ func (c *Client) tls() error {
|
|||
}
|
||||
}
|
||||
if est {
|
||||
if err := c.smtpClient.StartTLS(c.tlsconfig); err != nil {
|
||||
if err := c.sc.StartTLS(c.tlsconfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, c.isEncrypted = c.smtpClient.TLSConnectionState()
|
||||
_, c.enc = c.sc.TLSConnectionState()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -729,40 +729,40 @@ func (c *Client) auth() error {
|
|||
if err := c.checkConn(); err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
if c.smtpAuth == nil && c.smtpAuthType != "" {
|
||||
sa, sat := c.smtpClient.Extension("AUTH")
|
||||
if c.sa == nil && c.satype != "" {
|
||||
sa, sat := c.sc.Extension("AUTH")
|
||||
if !sa {
|
||||
return fmt.Errorf("server does not support SMTP AUTH")
|
||||
}
|
||||
|
||||
switch c.smtpAuthType {
|
||||
switch c.satype {
|
||||
case SMTPAuthPlain:
|
||||
if !strings.Contains(sat, string(SMTPAuthPlain)) {
|
||||
return ErrPlainAuthNotSupported
|
||||
}
|
||||
c.smtpAuth = smtp.PlainAuth("", c.user, c.pass, c.host)
|
||||
c.sa = smtp.PlainAuth("", c.user, c.pass, c.host)
|
||||
case SMTPAuthLogin:
|
||||
if !strings.Contains(sat, string(SMTPAuthLogin)) {
|
||||
return ErrLoginAuthNotSupported
|
||||
}
|
||||
c.smtpAuth = smtp.LoginAuth(c.user, c.pass, c.host)
|
||||
c.sa = smtp.LoginAuth(c.user, c.pass, c.host)
|
||||
case SMTPAuthCramMD5:
|
||||
if !strings.Contains(sat, string(SMTPAuthCramMD5)) {
|
||||
return ErrCramMD5AuthNotSupported
|
||||
}
|
||||
c.smtpAuth = smtp.CRAMMD5Auth(c.user, c.pass)
|
||||
c.sa = smtp.CRAMMD5Auth(c.user, c.pass)
|
||||
case SMTPAuthXOAUTH2:
|
||||
if !strings.Contains(sat, string(SMTPAuthXOAUTH2)) {
|
||||
return ErrXOauth2AuthNotSupported
|
||||
}
|
||||
c.smtpAuth = smtp.XOAuth2Auth(c.user, c.pass)
|
||||
c.sa = smtp.XOAuth2Auth(c.user, c.pass)
|
||||
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 err := c.smtpClient.Auth(c.smtpAuth); err != nil {
|
||||
if c.sa != nil {
|
||||
if err := c.sc.Auth(c.sa); err != nil {
|
||||
return fmt.Errorf("SMTP AUTH failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func (c *Client) Send(ml ...*Msg) error {
|
|||
for _, m := range ml {
|
||||
m.sendError = nil
|
||||
if m.encoding == NoEncoding {
|
||||
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||
se := &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||
m.sendError = se
|
||||
errs = append(errs, se)
|
||||
|
@ -42,12 +42,12 @@ func (c *Client) Send(ml ...*Msg) error {
|
|||
|
||||
if c.dsn {
|
||||
if c.dsnmrtype != "" {
|
||||
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||
}
|
||||
}
|
||||
if err := c.smtpClient.Mail(f); err != nil {
|
||||
if err := c.sc.Mail(f); err != nil {
|
||||
se := &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
se.errlist = append(se.errlist, reserr)
|
||||
}
|
||||
m.sendError = se
|
||||
|
@ -59,9 +59,9 @@ func (c *Client) Send(ml ...*Msg) error {
|
|||
rse.errlist = make([]error, 0)
|
||||
rse.rcpt = make([]string, 0)
|
||||
rno := strings.Join(c.dsnrntype, ",")
|
||||
c.smtpClient.SetDSNRcptNotifyOption(rno)
|
||||
c.sc.SetDSNRcptNotifyOption(rno)
|
||||
for _, r := range rl {
|
||||
if err := c.smtpClient.Rcpt(r); err != nil {
|
||||
if err := c.sc.Rcpt(r); err != nil {
|
||||
rse.Reason = ErrSMTPRcptTo
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
rse.rcpt = append(rse.rcpt, r)
|
||||
|
@ -70,14 +70,14 @@ func (c *Client) Send(ml ...*Msg) error {
|
|||
}
|
||||
}
|
||||
if failed {
|
||||
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
}
|
||||
m.sendError = rse
|
||||
errs = append(errs, rse)
|
||||
continue
|
||||
}
|
||||
w, err := c.smtpClient.Data()
|
||||
w, err := c.sc.Data()
|
||||
if err != nil {
|
||||
se := &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
m.sendError = se
|
||||
|
|
|
@ -21,7 +21,7 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
|||
for _, m := range ml {
|
||||
m.sendError = nil
|
||||
if m.encoding == NoEncoding {
|
||||
if ok, _ := c.smtpClient.Extension("8BITMIME"); !ok {
|
||||
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||
m.sendError = &SendError{Reason: ErrNoUnencoded, isTemp: false}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
|
@ -42,13 +42,13 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
|||
|
||||
if c.dsn {
|
||||
if c.dsnmrtype != "" {
|
||||
c.smtpClient.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||
c.sc.SetDSNMailReturnOption(string(c.dsnmrtype))
|
||||
}
|
||||
}
|
||||
if err := c.smtpClient.Mail(f); err != nil {
|
||||
if err := c.sc.Mail(f); err != nil {
|
||||
m.sendError = &SendError{Reason: ErrSMTPMailFrom, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
rerr = errors.Join(rerr, reserr)
|
||||
}
|
||||
continue
|
||||
|
@ -58,9 +58,9 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
|||
rse.errlist = make([]error, 0)
|
||||
rse.rcpt = make([]string, 0)
|
||||
rno := strings.Join(c.dsnrntype, ",")
|
||||
c.smtpClient.SetDSNRcptNotifyOption(rno)
|
||||
c.sc.SetDSNRcptNotifyOption(rno)
|
||||
for _, r := range rl {
|
||||
if err := c.smtpClient.Rcpt(r); err != nil {
|
||||
if err := c.sc.Rcpt(r); err != nil {
|
||||
rse.Reason = ErrSMTPRcptTo
|
||||
rse.errlist = append(rse.errlist, err)
|
||||
rse.rcpt = append(rse.rcpt, r)
|
||||
|
@ -69,14 +69,14 @@ func (c *Client) Send(ml ...*Msg) (rerr error) {
|
|||
}
|
||||
}
|
||||
if failed {
|
||||
if reserr := c.smtpClient.Reset(); reserr != nil {
|
||||
if reserr := c.sc.Reset(); reserr != nil {
|
||||
rerr = errors.Join(rerr, reserr)
|
||||
}
|
||||
m.sendError = rse
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
continue
|
||||
}
|
||||
w, err := c.smtpClient.Data()
|
||||
w, err := c.sc.Data()
|
||||
if err != nil {
|
||||
m.sendError = &SendError{Reason: ErrSMTPData, errlist: []error{err}, isTemp: isTempError(err)}
|
||||
rerr = errors.Join(rerr, m.sendError)
|
||||
|
|
|
@ -49,9 +49,9 @@ func TestNewClient(t *testing.T) {
|
|||
if c.host != tt.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(),
|
||||
c.connTimeout.String())
|
||||
c.cto.String())
|
||||
}
|
||||
if c.port != DefaultPort {
|
||||
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)
|
||||
return
|
||||
}
|
||||
if c.connTimeout != tt.want {
|
||||
t.Errorf("failed to set custom timeout. Want: %d, got: %d", tt.want, c.connTimeout)
|
||||
if c.cto != tt.want {
|
||||
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
|
||||
}
|
||||
c.SetSSL(tt.value)
|
||||
if c.useSSL != tt.value {
|
||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
||||
if c.ssl != 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
|
||||
}
|
||||
c.SetSSLPort(tt.value, tt.fb)
|
||||
if c.useSSL != tt.value {
|
||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.useSSL, tt.value)
|
||||
if c.ssl != tt.value {
|
||||
t.Errorf("failed to set SSL setting. Got: %t, want: %t", c.ssl, tt.value)
|
||||
}
|
||||
if 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
|
||||
}
|
||||
c.SetSMTPAuth(tt.value)
|
||||
if string(c.smtpAuthType) != tt.want {
|
||||
t.Errorf("failed to set SMTP auth type. Expected %s, got: %s", tt.want, string(c.smtpAuthType))
|
||||
if string(c.satype) != tt.want {
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
p, _, err := c.smtpAuth.Start(&si)
|
||||
p, _, err := c.sa.Start(&si)
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
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.")
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
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.")
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
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.")
|
||||
}
|
||||
c.SetDebugLog(true)
|
||||
|
@ -694,10 +694,10 @@ func TestClient_DialWithContext_Debug_custom(t *testing.T) {
|
|||
t.Errorf("failed to dial with context: %s", err)
|
||||
return
|
||||
}
|
||||
if c.connection == nil {
|
||||
if c.co == nil {
|
||||
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.")
|
||||
}
|
||||
c.SetDebugLog(true)
|
||||
|
@ -714,7 +714,7 @@ func TestClient_DialWithContextInvalidHost(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
c.connection = nil
|
||||
c.co = nil
|
||||
c.host = "invalid.addr"
|
||||
ctx := context.Background()
|
||||
if err := c.DialWithContext(ctx); err == nil {
|
||||
|
@ -730,7 +730,7 @@ func TestClient_DialWithContextInvalidHELO(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
c.connection = nil
|
||||
c.co = nil
|
||||
c.helo = ""
|
||||
ctx := context.Background()
|
||||
if err := c.DialWithContext(ctx); err == nil {
|
||||
|
@ -762,7 +762,7 @@ func TestClient_checkConn(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
c.connection = nil
|
||||
c.co = nil
|
||||
if err := c.checkConn(); err == nil {
|
||||
t.Errorf("connCheck() should fail but succeeded")
|
||||
}
|
||||
|
@ -799,10 +799,10 @@ func TestClient_DialWithContextOptions(t *testing.T) {
|
|||
return
|
||||
}
|
||||
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.")
|
||||
}
|
||||
if c.smtpClient == nil && !tt.sf {
|
||||
if c.sc == nil && !tt.sf {
|
||||
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||
}
|
||||
if err := c.Reset(); err != nil {
|
||||
|
@ -1002,16 +1002,16 @@ func TestClient_DialSendCloseBroken(t *testing.T) {
|
|||
return
|
||||
}
|
||||
if tt.closestart {
|
||||
_ = c.smtpClient.Close()
|
||||
_ = c.connection.Close()
|
||||
_ = c.sc.Close()
|
||||
_ = c.co.Close()
|
||||
}
|
||||
if err := c.Send(m); err != nil && !tt.sf {
|
||||
t.Errorf("Send() failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tt.closeearly {
|
||||
_ = c.smtpClient.Close()
|
||||
_ = c.connection.Close()
|
||||
_ = c.sc.Close()
|
||||
_ = c.co.Close()
|
||||
}
|
||||
if err := c.Close(); err != nil && !tt.sf {
|
||||
t.Errorf("Close() failed: %s", err)
|
||||
|
@ -1062,16 +1062,16 @@ func TestClient_DialSendCloseBrokenWithDSN(t *testing.T) {
|
|||
return
|
||||
}
|
||||
if tt.closestart {
|
||||
_ = c.smtpClient.Close()
|
||||
_ = c.connection.Close()
|
||||
_ = c.sc.Close()
|
||||
_ = c.co.Close()
|
||||
}
|
||||
if err := c.Send(m); err != nil && !tt.sf {
|
||||
t.Errorf("Send() failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tt.closeearly {
|
||||
_ = c.smtpClient.Close()
|
||||
_ = c.connection.Close()
|
||||
_ = c.sc.Close()
|
||||
_ = c.co.Close()
|
||||
}
|
||||
if err := c.Close(); err != nil && !tt.sf {
|
||||
t.Errorf("Close() failed: %s", err)
|
||||
|
|
408
msg.go
408
msg.go
|
@ -708,10 +708,7 @@ func (m *Msg) SetBodyString(contentType ContentType, content string, opts ...Par
|
|||
}
|
||||
|
||||
// SetBodyWriter sets the body of the message.
|
||||
func (m *Msg) SetBodyWriter(
|
||||
contentType ContentType, writeFunc func(io.Writer) (int64, error),
|
||||
opts ...PartOption,
|
||||
) {
|
||||
func (m *Msg) SetBodyWriter(contentType ContentType, writeFunc func(io.Writer) (int64, error), opts ...PartOption) {
|
||||
p := m.newPart(contentType, opts...)
|
||||
p.w = writeFunc
|
||||
m.parts = []*Part{p}
|
||||
|
@ -719,88 +716,85 @@ func (m *Msg) SetBodyWriter(
|
|||
|
||||
// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer
|
||||
// The content type will be set to text/html automatically
|
||||
func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
func (m *Msg) SetBodyHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
buf := bytes.Buffer{}
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
writeFunc := writeFuncFromBuffer(&buffer)
|
||||
m.SetBodyWriter(TypeTextHTML, writeFunc, opts...)
|
||||
w := writeFuncFromBuffer(&buf)
|
||||
m.SetBodyWriter(TypeTextHTML, w, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer
|
||||
// The content type will be set to text/plain automatically
|
||||
func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
func (m *Msg) SetBodyTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buf, data); err != nil {
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
writeFunc := writeFuncFromBuffer(&buf)
|
||||
m.SetBodyWriter(TypeTextPlain, writeFunc, opts...)
|
||||
w := writeFuncFromBuffer(&buf)
|
||||
m.SetBodyWriter(TypeTextPlain, w, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAlternativeString sets the alternative body of the message.
|
||||
func (m *Msg) AddAlternativeString(contentType ContentType, content string, opts ...PartOption) {
|
||||
buffer := bytes.NewBufferString(content)
|
||||
writeFunc := writeFuncFromBuffer(buffer)
|
||||
m.AddAlternativeWriter(contentType, writeFunc, opts...)
|
||||
func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) {
|
||||
buf := bytes.NewBufferString(b)
|
||||
w := writeFuncFromBuffer(buf)
|
||||
m.AddAlternativeWriter(ct, w, o...)
|
||||
}
|
||||
|
||||
// AddAlternativeWriter sets the body of the message.
|
||||
func (m *Msg) AddAlternativeWriter(
|
||||
contentType ContentType, writeFunc func(io.Writer) (int64, error),
|
||||
opts ...PartOption,
|
||||
) {
|
||||
part := m.newPart(contentType, opts...)
|
||||
part.w = writeFunc
|
||||
m.parts = append(m.parts, part)
|
||||
func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) (int64, error), o ...PartOption) {
|
||||
p := m.newPart(ct, o...)
|
||||
p.w = w
|
||||
m.parts = append(m.parts, p)
|
||||
}
|
||||
|
||||
// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output
|
||||
// The content type will be set to text/html automatically
|
||||
func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
func (m *Msg) AddAlternativeHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
buf := bytes.Buffer{}
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
writeFunc := writeFuncFromBuffer(&buffer)
|
||||
m.AddAlternativeWriter(TypeTextHTML, writeFunc, opts...)
|
||||
w := writeFuncFromBuffer(&buf)
|
||||
m.AddAlternativeWriter(TypeTextHTML, w, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output
|
||||
// The content type will be set to text/plain automatically
|
||||
func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
func (m *Msg) AddAlternativeTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
buf := bytes.Buffer{}
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
writeFunc := writeFuncFromBuffer(&buffer)
|
||||
m.AddAlternativeWriter(TypeTextPlain, writeFunc, opts...)
|
||||
w := writeFuncFromBuffer(&buf)
|
||||
m.AddAlternativeWriter(TypeTextPlain, w, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachFile adds an attachment File to the Msg
|
||||
func (m *Msg) AttachFile(name string, opts ...FileOption) {
|
||||
file := fileFromFS(name)
|
||||
if file == nil {
|
||||
func (m *Msg) AttachFile(n string, o ...FileOption) {
|
||||
f := fileFromFS(n)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
||||
}
|
||||
|
||||
// AttachReader adds an attachment File via io.Reader to the Msg
|
||||
|
@ -809,65 +803,61 @@ func (m *Msg) AttachFile(name string, opts ...FileOption) {
|
|||
// into memory first, so it can seek through it. Using larger amounts of
|
||||
// data on the io.Reader should be avoided. For such, it is recommended to
|
||||
// either use AttachFile or AttachReadSeeker instead
|
||||
func (m *Msg) AttachReader(name string, reader io.Reader, opts ...FileOption) error {
|
||||
file, err := fileFromReader(name, reader)
|
||||
func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) error {
|
||||
f, err := fileFromReader(n, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
|
||||
func (m *Msg) AttachReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) {
|
||||
file := fileFromReadSeeker(name, reader)
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
func (m *Msg) AttachReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
|
||||
f := fileFromReadSeeker(n, r)
|
||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
||||
}
|
||||
|
||||
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
|
||||
func (m *Msg) AttachHTMLTemplate(
|
||||
name string, tpl *ht.Template, data interface{}, opts ...FileOption,
|
||||
) error {
|
||||
file, err := fileFromHTMLTemplate(name, tpl, data)
|
||||
func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
|
||||
f, err := fileFromHTMLTemplate(n, t, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to attach template: %w", err)
|
||||
}
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg
|
||||
func (m *Msg) AttachTextTemplate(
|
||||
name string, tpl *tt.Template, data interface{}, opts ...FileOption,
|
||||
) error {
|
||||
file, err := fileFromTextTemplate(name, tpl, data)
|
||||
func (m *Msg) AttachTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error {
|
||||
f, err := fileFromTextTemplate(n, t, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to attach template: %w", err)
|
||||
}
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
m.attachments = m.appendFile(m.attachments, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachFromEmbedFS adds an attachment File from an embed.FS to the Msg
|
||||
func (m *Msg) AttachFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error {
|
||||
if fs == nil {
|
||||
func (m *Msg) AttachFromEmbedFS(n string, f *embed.FS, o ...FileOption) error {
|
||||
if f == nil {
|
||||
return fmt.Errorf("embed.FS must not be nil")
|
||||
}
|
||||
file, err := fileFromEmbedFS(name, fs)
|
||||
ef, err := fileFromEmbedFS(n, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.attachments = m.appendFile(m.attachments, file, opts...)
|
||||
m.attachments = m.appendFile(m.attachments, ef, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmbedFile adds an embedded File to the Msg
|
||||
func (m *Msg) EmbedFile(name string, opts ...FileOption) {
|
||||
file := fileFromFS(name)
|
||||
if file == nil {
|
||||
func (m *Msg) EmbedFile(n string, o ...FileOption) {
|
||||
f := fileFromFS(n)
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||
}
|
||||
|
||||
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
||||
|
@ -876,55 +866,51 @@ func (m *Msg) EmbedFile(name string, opts ...FileOption) {
|
|||
// into memory first, so it can seek through it. Using larger amounts of
|
||||
// data on the io.Reader should be avoided. For such, it is recommended to
|
||||
// either use EmbedFile or EmbedReadSeeker instead
|
||||
func (m *Msg) EmbedReader(name string, reader io.Reader, opts ...FileOption) error {
|
||||
file, err := fileFromReader(name, reader)
|
||||
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) error {
|
||||
f, err := fileFromReader(n, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
|
||||
func (m *Msg) EmbedReadSeeker(name string, reader io.ReadSeeker, opts ...FileOption) {
|
||||
file := fileFromReadSeeker(name, reader)
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
func (m *Msg) EmbedReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
|
||||
f := fileFromReadSeeker(n, r)
|
||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||
}
|
||||
|
||||
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
|
||||
func (m *Msg) EmbedHTMLTemplate(
|
||||
name string, tpl *ht.Template, data interface{}, opts ...FileOption,
|
||||
) error {
|
||||
file, err := fileFromHTMLTemplate(name, tpl, data)
|
||||
func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
|
||||
f, err := fileFromHTMLTemplate(n, t, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to embed template: %w", err)
|
||||
}
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg
|
||||
func (m *Msg) EmbedTextTemplate(
|
||||
name string, tpl *tt.Template, data interface{}, opts ...FileOption,
|
||||
) error {
|
||||
file, err := fileFromTextTemplate(name, tpl, data)
|
||||
func (m *Msg) EmbedTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error {
|
||||
f, err := fileFromTextTemplate(n, t, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to embed template: %w", err)
|
||||
}
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmbedFromEmbedFS adds an embedded File from an embed.FS to the Msg
|
||||
func (m *Msg) EmbedFromEmbedFS(name string, fs *embed.FS, opts ...FileOption) error {
|
||||
if fs == nil {
|
||||
func (m *Msg) EmbedFromEmbedFS(n string, f *embed.FS, o ...FileOption) error {
|
||||
if f == nil {
|
||||
return fmt.Errorf("embed.FS must not be nil")
|
||||
}
|
||||
file, err := fileFromEmbedFS(name, fs)
|
||||
ef, err := fileFromEmbedFS(n, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.embeds = m.appendFile(m.embeds, file, opts...)
|
||||
m.embeds = m.appendFile(m.embeds, ef, o...)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -939,73 +925,73 @@ func (m *Msg) Reset() {
|
|||
}
|
||||
|
||||
// ApplyMiddlewares apply the list of middlewares to a Msg
|
||||
func (m *Msg) applyMiddlewares(msg *Msg) *Msg {
|
||||
for _, middleware := range m.middlewares {
|
||||
msg = middleware.Handle(msg)
|
||||
func (m *Msg) applyMiddlewares(ms *Msg) *Msg {
|
||||
for _, mw := range m.middlewares {
|
||||
ms = mw.Handle(ms)
|
||||
}
|
||||
return msg
|
||||
return ms
|
||||
}
|
||||
|
||||
// WriteTo writes the formated Msg into a give io.Writer and satisfies the io.WriteTo interface
|
||||
func (m *Msg) WriteTo(writer io.Writer) (int64, error) {
|
||||
mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder}
|
||||
func (m *Msg) WriteTo(w io.Writer) (int64, error) {
|
||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
||||
mw.writeMsg(m.applyMiddlewares(m))
|
||||
return mw.bytesWritten, mw.err
|
||||
}
|
||||
|
||||
// WriteToSkipMiddleware writes the formated Msg into a give io.Writer and satisfies
|
||||
// the io.WriteTo interface but will skip the given Middleware
|
||||
func (m *Msg) WriteToSkipMiddleware(writer io.Writer, middleWareType MiddlewareType) (int64, error) {
|
||||
var origMiddlewares, middlewares []Middleware
|
||||
origMiddlewares = m.middlewares
|
||||
func (m *Msg) WriteToSkipMiddleware(w io.Writer, mt MiddlewareType) (int64, error) {
|
||||
var omwl, mwl []Middleware
|
||||
omwl = m.middlewares
|
||||
for i := range m.middlewares {
|
||||
if m.middlewares[i].Type() == middleWareType {
|
||||
if m.middlewares[i].Type() == mt {
|
||||
continue
|
||||
}
|
||||
middlewares = append(middlewares, m.middlewares[i])
|
||||
mwl = append(mwl, m.middlewares[i])
|
||||
}
|
||||
m.middlewares = middlewares
|
||||
mw := &msgWriter{writer: writer, charset: m.charset, encoder: m.encoder}
|
||||
m.middlewares = mwl
|
||||
mw := &msgWriter{writer: w, charset: m.charset, encoder: m.encoder}
|
||||
mw.writeMsg(m.applyMiddlewares(m))
|
||||
m.middlewares = origMiddlewares
|
||||
m.middlewares = omwl
|
||||
return mw.bytesWritten, mw.err
|
||||
}
|
||||
|
||||
// Write is an alias method to WriteTo due to compatibility reasons
|
||||
func (m *Msg) Write(writer io.Writer) (int64, error) {
|
||||
return m.WriteTo(writer)
|
||||
func (m *Msg) Write(w io.Writer) (int64, error) {
|
||||
return m.WriteTo(w)
|
||||
}
|
||||
|
||||
// appendFile adds a File to the Msg (as attachment or embed)
|
||||
func (m *Msg) appendFile(files []*File, file *File, opts ...FileOption) []*File {
|
||||
func (m *Msg) appendFile(c []*File, f *File, o ...FileOption) []*File {
|
||||
// Override defaults with optionally provided FileOption functions
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
for _, co := range o {
|
||||
if co == nil {
|
||||
continue
|
||||
}
|
||||
opt(file)
|
||||
co(f)
|
||||
}
|
||||
|
||||
if files == nil {
|
||||
return []*File{file}
|
||||
if c == nil {
|
||||
return []*File{f}
|
||||
}
|
||||
|
||||
return append(files, file)
|
||||
return append(c, f)
|
||||
}
|
||||
|
||||
// WriteToFile stores the Msg as file on disk. It will try to create the given filename
|
||||
// Already existing files will be overwritten
|
||||
func (m *Msg) WriteToFile(name string) error {
|
||||
file, err := os.Create(name)
|
||||
func (m *Msg) WriteToFile(n string) error {
|
||||
f, err := os.Create(n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
_, err = m.WriteTo(file)
|
||||
defer func() { _ = f.Close() }()
|
||||
_, err = m.WriteTo(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to output file: %w", err)
|
||||
}
|
||||
return file.Close()
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// WriteToSendmail returns WriteToSendmailWithCommand with a default sendmail path
|
||||
|
@ -1015,38 +1001,38 @@ func (m *Msg) WriteToSendmail() error {
|
|||
|
||||
// WriteToSendmailWithCommand returns WriteToSendmailWithContext with a default timeout
|
||||
// of 5 seconds and a given sendmail path
|
||||
func (m *Msg) WriteToSendmailWithCommand(sendmailPath string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
return m.WriteToSendmailWithContext(ctx, sendmailPath)
|
||||
func (m *Msg) WriteToSendmailWithCommand(sp string) error {
|
||||
tctx, tcfn := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer tcfn()
|
||||
return m.WriteToSendmailWithContext(tctx, sp)
|
||||
}
|
||||
|
||||
// WriteToSendmailWithContext opens an pipe to the local sendmail binary and tries to send the
|
||||
// mail though that. It takes a context.Context, the path to the sendmail binary and additional
|
||||
// arguments for the sendmail binary as parameters
|
||||
func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath string, args ...string) error {
|
||||
cmdCtx := exec.CommandContext(ctx, sendmailPath)
|
||||
cmdCtx.Args = append(cmdCtx.Args, "-oi", "-t")
|
||||
cmdCtx.Args = append(cmdCtx.Args, args...)
|
||||
func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...string) error {
|
||||
ec := exec.CommandContext(ctx, sp)
|
||||
ec.Args = append(ec.Args, "-oi", "-t")
|
||||
ec.Args = append(ec.Args, a...)
|
||||
|
||||
stdErr, err := cmdCtx.StderrPipe()
|
||||
se, err := ec.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set STDERR pipe: %w", err)
|
||||
}
|
||||
|
||||
stdIn, err := cmdCtx.StdinPipe()
|
||||
si, err := ec.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set STDIN pipe: %w", err)
|
||||
}
|
||||
if stdErr == nil || stdIn == nil {
|
||||
if se == nil || si == nil {
|
||||
return fmt.Errorf("received nil for STDERR or STDIN pipe")
|
||||
}
|
||||
|
||||
// Start the execution and write to STDIN
|
||||
if err = cmdCtx.Start(); err != nil {
|
||||
if err = ec.Start(); err != nil {
|
||||
return fmt.Errorf("could not start sendmail execution: %w", err)
|
||||
}
|
||||
_, err = m.WriteTo(stdIn)
|
||||
_, err = m.WriteTo(si)
|
||||
if err != nil {
|
||||
if !errors.Is(err, syscall.EPIPE) {
|
||||
return fmt.Errorf("failed to write mail to buffer: %w", err)
|
||||
|
@ -1054,20 +1040,20 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath strin
|
|||
}
|
||||
|
||||
// Close STDIN and wait for completion or cancellation of the sendmail executable
|
||||
if err = stdIn.Close(); err != nil {
|
||||
if err = si.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close STDIN pipe: %w", err)
|
||||
}
|
||||
|
||||
// Read the stderr pipe for possible errors
|
||||
sendmailErr, err := io.ReadAll(stdErr)
|
||||
serr, err := io.ReadAll(se)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read STDERR pipe: %w", err)
|
||||
}
|
||||
if len(sendmailErr) > 0 {
|
||||
return fmt.Errorf("sendmail command failed: %s", string(sendmailErr))
|
||||
if len(serr) > 0 {
|
||||
return fmt.Errorf("sendmail command failed: %s", string(serr))
|
||||
}
|
||||
|
||||
if err = cmdCtx.Wait(); err != nil {
|
||||
if err = ec.Wait(); err != nil {
|
||||
return fmt.Errorf("sendmail command execution failed: %w", err)
|
||||
}
|
||||
|
||||
|
@ -1081,24 +1067,24 @@ func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sendmailPath strin
|
|||
// changes will not be reflected in the Reader. You will have to use Msg.UpdateReader
|
||||
// first to update the Reader's buffer with the current Msg content
|
||||
func (m *Msg) NewReader() *Reader {
|
||||
reader := &Reader{}
|
||||
buffer := bytes.Buffer{}
|
||||
_, err := m.Write(&buffer)
|
||||
r := &Reader{}
|
||||
wbuf := bytes.Buffer{}
|
||||
_, err := m.Write(&wbuf)
|
||||
if err != nil {
|
||||
reader.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
|
||||
r.err = fmt.Errorf("failed to write Msg to Reader buffer: %w", err)
|
||||
}
|
||||
reader.buf = buffer.Bytes()
|
||||
return reader
|
||||
r.buf = wbuf.Bytes()
|
||||
return r
|
||||
}
|
||||
|
||||
// UpdateReader will update a Reader with the content of the given Msg and reset the
|
||||
// Reader position to the start
|
||||
func (m *Msg) UpdateReader(reader *Reader) {
|
||||
buffer := bytes.Buffer{}
|
||||
_, err := m.Write(&buffer)
|
||||
reader.Reset()
|
||||
reader.buf = buffer.Bytes()
|
||||
reader.err = err
|
||||
func (m *Msg) UpdateReader(r *Reader) {
|
||||
wbuf := bytes.Buffer{}
|
||||
_, err := m.Write(&wbuf)
|
||||
r.Reset()
|
||||
r.buf = wbuf.Bytes()
|
||||
r.err = err
|
||||
}
|
||||
|
||||
// HasSendError returns true if the Msg experienced an error during the message delivery and the
|
||||
|
@ -1110,9 +1096,9 @@ func (m *Msg) HasSendError() bool {
|
|||
// SendErrorIsTemp returns true if the Msg experienced an error during the message delivery and the
|
||||
// corresponding error was of temporary nature and should be retried later
|
||||
func (m *Msg) SendErrorIsTemp() bool {
|
||||
var err *SendError
|
||||
if errors.As(m.sendError, &err) && err != nil {
|
||||
return err.isTemp
|
||||
var e *SendError
|
||||
if errors.As(m.sendError, &e) && e != nil {
|
||||
return e.isTemp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1124,19 +1110,19 @@ func (m *Msg) SendError() error {
|
|||
|
||||
// encodeString encodes a string based on the configured message encoder and the corresponding
|
||||
// charset for the Msg
|
||||
func (m *Msg) encodeString(str string) string {
|
||||
return m.encoder.Encode(string(m.charset), str)
|
||||
func (m *Msg) encodeString(s string) string {
|
||||
return m.encoder.Encode(string(m.charset), s)
|
||||
}
|
||||
|
||||
// hasAlt returns true if the Msg has more than one part
|
||||
func (m *Msg) hasAlt() bool {
|
||||
count := 0
|
||||
for _, part := range m.parts {
|
||||
if !part.del {
|
||||
count++
|
||||
c := 0
|
||||
for _, p := range m.parts {
|
||||
if !p.del {
|
||||
c++
|
||||
}
|
||||
}
|
||||
return count > 1 && m.pgptype == 0
|
||||
return c > 1 && m.pgptype == 0
|
||||
}
|
||||
|
||||
// hasMixed returns true if the Msg has mixed parts
|
||||
|
@ -1155,19 +1141,19 @@ func (m *Msg) hasPGPType() bool {
|
|||
}
|
||||
|
||||
// newPart returns a new Part for the Msg
|
||||
func (m *Msg) newPart(contentType ContentType, opts ...PartOption) *Part {
|
||||
func (m *Msg) newPart(ct ContentType, o ...PartOption) *Part {
|
||||
p := &Part{
|
||||
ctype: contentType,
|
||||
ctype: ct,
|
||||
cset: m.charset,
|
||||
enc: m.encoding,
|
||||
}
|
||||
|
||||
// Override defaults with optionally provided MsgOption functions
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
for _, co := range o {
|
||||
if co == nil {
|
||||
continue
|
||||
}
|
||||
opt(p)
|
||||
co(p)
|
||||
}
|
||||
|
||||
return p
|
||||
|
@ -1201,118 +1187,118 @@ func (m *Msg) addDefaultHeader() {
|
|||
}
|
||||
|
||||
// fileFromEmbedFS returns a File pointer from a given file in the provided embed.FS
|
||||
func fileFromEmbedFS(name string, fs *embed.FS) (*File, error) {
|
||||
_, err := fs.Open(name)
|
||||
func fileFromEmbedFS(n string, f *embed.FS) (*File, error) {
|
||||
_, err := f.Open(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file from embed.FS: %w", err)
|
||||
}
|
||||
return &File{
|
||||
Name: filepath.Base(name),
|
||||
Name: filepath.Base(n),
|
||||
Header: make(map[string][]string),
|
||||
Writer: func(writer io.Writer) (int64, error) {
|
||||
file, err := fs.Open(name)
|
||||
Writer: func(w io.Writer) (int64, error) {
|
||||
h, err := f.Open(n)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numBytes, err := io.Copy(writer, file)
|
||||
nb, err := io.Copy(w, h)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return numBytes, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||
_ = h.Close()
|
||||
return nb, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||
}
|
||||
return numBytes, file.Close()
|
||||
return nb, h.Close()
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fileFromFS returns a File pointer from a given file in the system's file system
|
||||
func fileFromFS(name string) *File {
|
||||
_, err := os.Stat(name)
|
||||
func fileFromFS(n string) *File {
|
||||
_, err := os.Stat(n)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &File{
|
||||
Name: filepath.Base(name),
|
||||
Name: filepath.Base(n),
|
||||
Header: make(map[string][]string),
|
||||
Writer: func(writer io.Writer) (int64, error) {
|
||||
file, err := os.Open(name)
|
||||
Writer: func(w io.Writer) (int64, error) {
|
||||
h, err := os.Open(n)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numBytes, err := io.Copy(writer, file)
|
||||
nb, err := io.Copy(w, h)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return numBytes, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||
_ = h.Close()
|
||||
return nb, fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||
}
|
||||
return numBytes, file.Close()
|
||||
return nb, h.Close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// fileFromReader returns a File pointer from a given io.Reader
|
||||
func fileFromReader(name string, reader io.Reader) (*File, error) {
|
||||
d, err := io.ReadAll(reader)
|
||||
func fileFromReader(n string, r io.Reader) (*File, error) {
|
||||
d, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return &File{}, err
|
||||
}
|
||||
byteReader := bytes.NewReader(d)
|
||||
br := bytes.NewReader(d)
|
||||
return &File{
|
||||
Name: name,
|
||||
Name: n,
|
||||
Header: make(map[string][]string),
|
||||
Writer: func(writer io.Writer) (int64, error) {
|
||||
readBytes, copyErr := io.Copy(writer, byteReader)
|
||||
if copyErr != nil {
|
||||
return readBytes, copyErr
|
||||
Writer: func(w io.Writer) (int64, error) {
|
||||
rb, cerr := io.Copy(w, br)
|
||||
if cerr != nil {
|
||||
return rb, cerr
|
||||
}
|
||||
_, copyErr = byteReader.Seek(0, io.SeekStart)
|
||||
return readBytes, copyErr
|
||||
_, cerr = br.Seek(0, io.SeekStart)
|
||||
return rb, cerr
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
|
||||
func fileFromReadSeeker(name string, reader io.ReadSeeker) *File {
|
||||
func fileFromReadSeeker(n string, r io.ReadSeeker) *File {
|
||||
return &File{
|
||||
Name: name,
|
||||
Name: n,
|
||||
Header: make(map[string][]string),
|
||||
Writer: func(writer io.Writer) (int64, error) {
|
||||
readBytes, err := io.Copy(writer, reader)
|
||||
Writer: func(w io.Writer) (int64, error) {
|
||||
rb, err := io.Copy(w, r)
|
||||
if err != nil {
|
||||
return readBytes, err
|
||||
return rb, err
|
||||
}
|
||||
_, err = reader.Seek(0, io.SeekStart)
|
||||
return readBytes, err
|
||||
_, err = r.Seek(0, io.SeekStart)
|
||||
return rb, err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
|
||||
func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) {
|
||||
if tpl == nil {
|
||||
func fileFromHTMLTemplate(n string, t *ht.Template, d interface{}) (*File, error) {
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
buf := bytes.Buffer{}
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
return fileFromReader(name, &buffer)
|
||||
return fileFromReader(n, &buf)
|
||||
}
|
||||
|
||||
// fileFromTextTemplate returns a File pointer form a given text/template.Template
|
||||
func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) {
|
||||
if tpl == nil {
|
||||
func fileFromTextTemplate(n string, t *tt.Template, d interface{}) (*File, error) {
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
buf := bytes.Buffer{}
|
||||
if err := t.Execute(&buf, d); err != nil {
|
||||
return nil, fmt.Errorf(errTplExecuteFailed, err)
|
||||
}
|
||||
return fileFromReader(name, &buffer)
|
||||
return fileFromReader(n, &buf)
|
||||
}
|
||||
|
||||
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||
func getEncoder(enc Encoding) mime.WordEncoder {
|
||||
switch enc {
|
||||
func getEncoder(e Encoding) mime.WordEncoder {
|
||||
switch e {
|
||||
case EncodingQP:
|
||||
return mime.QEncoding
|
||||
case EncodingB64:
|
||||
|
@ -1324,10 +1310,10 @@ func getEncoder(enc Encoding) mime.WordEncoder {
|
|||
|
||||
// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as
|
||||
// often required by this library
|
||||
func writeFuncFromBuffer(buffer *bytes.Buffer) func(io.Writer) (int64, error) {
|
||||
writeFunc := func(w io.Writer) (int64, error) {
|
||||
numBytes, err := w.Write(buffer.Bytes())
|
||||
return int64(numBytes), err
|
||||
func writeFuncFromBuffer(buf *bytes.Buffer) func(io.Writer) (int64, error) {
|
||||
w := func(w io.Writer) (int64, error) {
|
||||
nb, err := w.Write(buf.Bytes())
|
||||
return int64(nb), err
|
||||
}
|
||||
return writeFunc
|
||||
return w
|
||||
}
|
||||
|
|
8
tls.go
8
tls.go
|
@ -8,17 +8,17 @@ package mail
|
|||
type TLSPolicy int
|
||||
|
||||
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
|
||||
// the connection will be terminated with an error
|
||||
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
|
||||
// back to non-encrypted plaintext transmission
|
||||
// back cto non-encrypted plaintext transmission
|
||||
TLSOpportunistic
|
||||
|
||||
// NoTLS forces the transaction to be not encrypted
|
||||
// NoTLS forces the transaction cto be not encrypted
|
||||
NoTLS
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue