mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-25 15:10:48 +01:00
Merge branch 'main' into feature/145_add-eml-parser-to-generate-msg-from-eml-files
This commit is contained in:
commit
8da2abcf01
3 changed files with 200 additions and 5 deletions
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
|
@ -50,6 +50,6 @@ jobs:
|
||||||
go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
|
go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: success() && matrix.go == '1.21' && matrix.os == 'ubuntu-latest'
|
if: success() && matrix.go == '1.21' && matrix.os == 'ubuntu-latest'
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||||
|
|
96
client.go
96
client.go
|
@ -20,9 +20,15 @@ import (
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
const (
|
const (
|
||||||
// DefaultPort is the default connection port cto the SMTP server
|
// DefaultPort is the default connection port to the SMTP server
|
||||||
DefaultPort = 25
|
DefaultPort = 25
|
||||||
|
|
||||||
|
// DefaultPortSSL is the default connection port for SSL/TLS to the SMTP server
|
||||||
|
DefaultPortSSL = 465
|
||||||
|
|
||||||
|
// DefaultPortTLS is the default connection port for STARTTLS to the SMTP server
|
||||||
|
DefaultPortTLS = 587
|
||||||
|
|
||||||
// DefaultTimeout is the default connection timeout
|
// DefaultTimeout is the default connection timeout
|
||||||
DefaultTimeout = time.Second * 15
|
DefaultTimeout = time.Second * 15
|
||||||
|
|
||||||
|
@ -105,14 +111,15 @@ type Client struct {
|
||||||
// HELO/EHLO string for the greeting the target SMTP server
|
// HELO/EHLO string for the greeting the target SMTP server
|
||||||
helo string
|
helo string
|
||||||
|
|
||||||
// Hostname of the target SMTP server cto connect cto
|
// Hostname of the target SMTP server to connect to
|
||||||
host string
|
host string
|
||||||
|
|
||||||
// pass is the corresponding SMTP AUTH password
|
// pass is the corresponding SMTP AUTH password
|
||||||
pass string
|
pass string
|
||||||
|
|
||||||
// Port of the SMTP server cto connect cto
|
// Port of the SMTP server to connect to
|
||||||
port int
|
port int
|
||||||
|
fallbackPort int
|
||||||
|
|
||||||
// sa is a pointer to smtp.Auth
|
// sa is a pointer to smtp.Auth
|
||||||
sa smtp.Auth
|
sa smtp.Auth
|
||||||
|
@ -246,6 +253,8 @@ func WithTimeout(t time.Duration) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSSL tells the client to use a SSL/TLS connection
|
// WithSSL tells the client to use a SSL/TLS connection
|
||||||
|
//
|
||||||
|
// Deprecated: use WithSSLPort instead.
|
||||||
func WithSSL() Option {
|
func WithSSL() Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.ssl = true
|
c.ssl = true
|
||||||
|
@ -253,6 +262,18 @@ func WithSSL() Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSSLPort tells the client to use a SSL/TLS connection.
|
||||||
|
// It automatically sets the port to 465.
|
||||||
|
//
|
||||||
|
// When the SSL connection fails and fallback is set to true,
|
||||||
|
// the client will attempt to connect on port 25 using plaintext.
|
||||||
|
func WithSSLPort(fb bool) Option {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.SetSSLPort(true, fb)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithDebugLog tells the client to log incoming and outgoing messages of the SMTP client
|
// WithDebugLog tells the client to log incoming and outgoing messages of the SMTP client
|
||||||
// to StdErr
|
// to StdErr
|
||||||
func WithDebugLog() Option {
|
func WithDebugLog() Option {
|
||||||
|
@ -282,6 +303,8 @@ func WithHELO(h string) Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
// WithTLSPolicy tells the client to use the provided TLSPolicy
|
||||||
|
//
|
||||||
|
// Deprecated: use WithTLSPortPolicy instead.
|
||||||
func WithTLSPolicy(p TLSPolicy) Option {
|
func WithTLSPolicy(p TLSPolicy) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
c.tlspolicy = p
|
c.tlspolicy = p
|
||||||
|
@ -289,6 +312,20 @@ func WithTLSPolicy(p TLSPolicy) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTLSPortPolicy tells the client to use the provided TLSPolicy,
|
||||||
|
// The correct port is automatically set.
|
||||||
|
//
|
||||||
|
// Port 587 is used for TLSMandatory and TLSOpportunistic.
|
||||||
|
// 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(p TLSPolicy) Option {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.SetTLSPortPolicy(p)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithTLSConfig tells the client to use the provided *tls.Config
|
// WithTLSConfig tells the client to use the provided *tls.Config
|
||||||
func WithTLSConfig(co *tls.Config) Option {
|
func WithTLSConfig(co *tls.Config) Option {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
|
@ -430,11 +467,52 @@ func (c *Client) SetTLSPolicy(p TLSPolicy) {
|
||||||
c.tlspolicy = p
|
c.tlspolicy = p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
|
||||||
|
// value. The correct port is automatically set.
|
||||||
|
//
|
||||||
|
// Port 587 is used for TLSMandatory and TLSOpportunistic.
|
||||||
|
// 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(p TLSPolicy) {
|
||||||
|
c.port = DefaultPortTLS
|
||||||
|
|
||||||
|
if p == TLSOpportunistic {
|
||||||
|
c.fallbackPort = DefaultPort
|
||||||
|
}
|
||||||
|
if p == NoTLS {
|
||||||
|
c.port = DefaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
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(s bool) {
|
func (c *Client) SetSSL(s bool) {
|
||||||
c.ssl = s
|
c.ssl = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSSLPort tells the Client wether or not to use SSL and fallback.
|
||||||
|
// The correct port is automatically set.
|
||||||
|
//
|
||||||
|
// Port 465 is used when SSL set (true).
|
||||||
|
// 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, fb bool) {
|
||||||
|
c.port = DefaultPort
|
||||||
|
if ssl {
|
||||||
|
c.port = DefaultPortSSL
|
||||||
|
}
|
||||||
|
|
||||||
|
c.fallbackPort = 0
|
||||||
|
if fb {
|
||||||
|
c.fallbackPort = DefaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
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(v bool) {
|
func (c *Client) SetDebugLog(v bool) {
|
||||||
c.dl = v
|
c.dl = v
|
||||||
|
@ -507,6 +585,10 @@ func (c *Client) DialWithContext(pc context.Context) error {
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
c.co, 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.co, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -606,6 +688,12 @@ func (c *Client) checkConn() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serverFallbackAddr returns the currently set combination of hostname
|
||||||
|
// and fallback port.
|
||||||
|
func (c *Client) serverFallbackAddr() string {
|
||||||
|
return fmt.Sprintf("%s:%d", c.host, c.fallbackPort)
|
||||||
|
}
|
||||||
|
|
||||||
// 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.co == nil {
|
if c.co == nil {
|
||||||
|
|
107
client_test.go
107
client_test.go
|
@ -89,9 +89,12 @@ func TestNewClientWithOptions(t *testing.T) {
|
||||||
{"WithTimeout()", WithTimeout(time.Second * 5), false},
|
{"WithTimeout()", WithTimeout(time.Second * 5), false},
|
||||||
{"WithTimeout()", WithTimeout(-10), true},
|
{"WithTimeout()", WithTimeout(-10), true},
|
||||||
{"WithSSL()", WithSSL(), false},
|
{"WithSSL()", WithSSL(), false},
|
||||||
|
{"WithSSLPort(false)", WithSSLPort(false), false},
|
||||||
|
{"WithSSLPort(true)", WithSSLPort(true), false},
|
||||||
{"WithHELO()", WithHELO(host), false},
|
{"WithHELO()", WithHELO(host), false},
|
||||||
{"WithHELO(); helo is empty", WithHELO(""), true},
|
{"WithHELO(); helo is empty", WithHELO(""), true},
|
||||||
{"WithTLSPolicy()", WithTLSPolicy(TLSOpportunistic), false},
|
{"WithTLSPolicy()", WithTLSPolicy(TLSOpportunistic), false},
|
||||||
|
{"WithTLSPortPolicy()", WithTLSPortPolicy(TLSOpportunistic), false},
|
||||||
{"WithTLSConfig()", WithTLSConfig(&tls.Config{}), false},
|
{"WithTLSConfig()", WithTLSConfig(&tls.Config{}), false},
|
||||||
{"WithTLSConfig(); config is nil", WithTLSConfig(nil), true},
|
{"WithTLSConfig(); config is nil", WithTLSConfig(nil), true},
|
||||||
{"WithSMTPAuth()", WithSMTPAuth(SMTPAuthLogin), false},
|
{"WithSMTPAuth()", WithSMTPAuth(SMTPAuthLogin), false},
|
||||||
|
@ -235,6 +238,42 @@ func TestWithTLSPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWithTLSPortPolicy tests the WithTLSPortPolicy() option for the NewClient() method
|
||||||
|
func TestWithTLSPortPolicy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value TLSPolicy
|
||||||
|
want string
|
||||||
|
wantPort int
|
||||||
|
fbPort int
|
||||||
|
sf bool
|
||||||
|
}{
|
||||||
|
{"Policy: TLSMandatory", TLSMandatory, TLSMandatory.String(), 587, 0, false},
|
||||||
|
{"Policy: TLSOpportunistic", TLSOpportunistic, TLSOpportunistic.String(), 587, 25, false},
|
||||||
|
{"Policy: NoTLS", NoTLS, NoTLS.String(), 25, 0, false},
|
||||||
|
{"Policy: Invalid", -1, "UnknownPolicy", 587, 0, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, err := NewClient(DefaultHost, WithTLSPortPolicy(tt.value))
|
||||||
|
if err != nil && !tt.sf {
|
||||||
|
t.Errorf("failed to create new client: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.tlspolicy.String() != tt.want {
|
||||||
|
t.Errorf("failed to set TLSPortPolicy. Want: %s, got: %s", tt.want, c.tlspolicy)
|
||||||
|
}
|
||||||
|
if c.port != tt.wantPort {
|
||||||
|
t.Errorf("failed to set TLSPortPolicy, wanted port: %d, got: %d", tt.wantPort, c.port)
|
||||||
|
}
|
||||||
|
if c.fallbackPort != tt.fbPort {
|
||||||
|
t.Errorf("failed to set TLSPortPolicy, wanted fallbakc port: %d, got: %d", tt.fbPort,
|
||||||
|
c.fallbackPort)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSetTLSPolicy tests the SetTLSPolicy() method for the Client object
|
// TestSetTLSPolicy tests the SetTLSPolicy() method for the Client object
|
||||||
func TestSetTLSPolicy(t *testing.T) {
|
func TestSetTLSPolicy(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -312,6 +351,42 @@ func TestSetSSL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetSSLPort tests the Client.SetSSLPort method
|
||||||
|
func TestClient_SetSSLPort(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value bool
|
||||||
|
fb bool
|
||||||
|
port int
|
||||||
|
fbPort int
|
||||||
|
}{
|
||||||
|
{"SSL: on, fb: off", true, false, 465, 0},
|
||||||
|
{"SSL: on, fb: on", true, true, 465, 25},
|
||||||
|
{"SSL: off, fb: off", false, false, 25, 0},
|
||||||
|
{"SSL: off, fb: on", false, true, 25, 25},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, err := NewClient(DefaultHost)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create new client: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetSSLPort(tt.value, tt.fb)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if c.fallbackPort != tt.fbPort {
|
||||||
|
t.Errorf("failed to set SSLPort, wanted fallback port: %d, got: %d", c.fallbackPort,
|
||||||
|
tt.fbPort)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSetUsername tests the SetUsername method for the Client object
|
// TestSetUsername tests the SetUsername method for the Client object
|
||||||
func TestSetUsername(t *testing.T) {
|
func TestSetUsername(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -550,6 +625,38 @@ func TestClient_DialWithContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestClient_DialWithContext_Fallback tests the Client.DialWithContext method with the fallback
|
||||||
|
// port functionality
|
||||||
|
func TestClient_DialWithContext_Fallback(t *testing.T) {
|
||||||
|
c, err := getTestConnection(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||||
|
}
|
||||||
|
c.SetTLSPortPolicy(TLSOpportunistic)
|
||||||
|
c.port = 999
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := c.DialWithContext(ctx); err != nil {
|
||||||
|
t.Errorf("failed to dial with context: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.co == nil {
|
||||||
|
t.Errorf("DialWithContext didn't fail but no connection found.")
|
||||||
|
}
|
||||||
|
if c.sc == nil {
|
||||||
|
t.Errorf("DialWithContext didn't fail but no SMTP client found.")
|
||||||
|
}
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
t.Errorf("failed to close connection: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.port = 999
|
||||||
|
c.fallbackPort = 999
|
||||||
|
if err = c.DialWithContext(ctx); err == nil {
|
||||||
|
t.Error("dial with context was supposed to fail, but didn't")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestClient_DialWithContext_Debug tests the DialWithContext method for the Client object with debug
|
// TestClient_DialWithContext_Debug tests the DialWithContext method for the Client object with debug
|
||||||
// logging enabled on the SMTP client
|
// logging enabled on the SMTP client
|
||||||
func TestClient_DialWithContext_Debug(t *testing.T) {
|
func TestClient_DialWithContext_Debug(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue