mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-09 15:32:54 +01:00
Merge branch 'main' into feature/107_provide-more-ways-for-middleware-to-interact-with-mail-parts
This commit is contained in:
commit
f7e1345f3d
12 changed files with 297 additions and 11 deletions
6
.github/workflows/codecov.yml
vendored
6
.github/workflows/codecov.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: [1.16, 1.17, 1.18, 1.19]
|
||||
go: [1.16, 1.17, 1.18, 1.19, '1.20']
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@master
|
||||
|
@ -42,14 +42,14 @@ jobs:
|
|||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Install sendmail
|
||||
if: matrix.go == 1.19 && matrix.os == 'ubuntu-latest'
|
||||
if: matrix.go == '1.20' && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get -y install sendmail; which sendmail
|
||||
- name: Run Tests
|
||||
run: |
|
||||
go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
|
||||
- name: Upload coverage to Codecov
|
||||
if: success() && matrix.go == 1.19 && matrix.os == 'ubuntu-latest'
|
||||
if: success() && matrix.go == '1.20' && matrix.os == 'ubuntu-latest'
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||
|
|
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: '1.20'
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
|
2
.github/workflows/sonarqube.yml
vendored
2
.github/workflows/sonarqube.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
|||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
go-version: '1.20.x'
|
||||
|
||||
- name: Run unit Tests
|
||||
run: |
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
## SPDX-License-Identifier: MIT
|
||||
|
||||
[run]
|
||||
go = "1.16"
|
||||
go = "1.20"
|
||||
tests = true
|
||||
|
||||
[linters]
|
||||
|
|
|
@ -61,6 +61,15 @@ standard in a MUA.
|
|||
We aim for good GoDoc documenation in our library which gives you a full API reference. We also provide a more in-depth documentation website at
|
||||
[go-mail.dev](https://go-mail.dev)
|
||||
|
||||
## Compatibility
|
||||
|
||||
Go is growing fast and providing great features with every new release. While we'd love to adopt the latest Go features
|
||||
into our code, we realize that not everybody using this package can run the latest Go versions. Therefore we try to
|
||||
implement alternative solutions for Go versions that do not support these features. Yet, the work needed to maintain
|
||||
the separate versions is not to be underestimated. For that reason, we might retire that code at some point.
|
||||
We guarantee that go-mail will always support the last four releases of Go. With two Go releases per year, this gives
|
||||
the user a timeframe of two years to update to the next or even the latest version of Go.
|
||||
|
||||
## Support
|
||||
We have a support and general discussion channel on Discord. Find us at: [#go-mail](https://discord.gg/dbfQyC4s)
|
||||
|
||||
|
|
23
client.go
23
client.go
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wneessen/go-mail/log"
|
||||
"github.com/wneessen/go-mail/smtp"
|
||||
)
|
||||
|
||||
|
@ -133,6 +134,9 @@ type Client struct {
|
|||
|
||||
// dl enables the debug logging on the SMTP client
|
||||
dl bool
|
||||
|
||||
// l is a logger that implements the log.Logger interface
|
||||
l log.Logger
|
||||
}
|
||||
|
||||
// Option returns a function that can be used for grouping Client options
|
||||
|
@ -252,6 +256,14 @@ func WithDebugLog() Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithLogger overrides the default log.Logger that is used for debug logging
|
||||
func WithLogger(l log.Logger) Option {
|
||||
return func(c *Client) error {
|
||||
c.l = l
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHELO tells the client to use the provided string as HELO/EHLO greeting host
|
||||
func WithHELO(h string) Option {
|
||||
return func(c *Client) error {
|
||||
|
@ -417,6 +429,14 @@ func (c *Client) SetDebugLog(v bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetLogger tells the Client which log.Logger to use
|
||||
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(co *tls.Config) error {
|
||||
if co == nil {
|
||||
|
@ -481,6 +501,9 @@ func (c *Client) DialWithContext(pc context.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.l != nil {
|
||||
c.sc.SetLogger(c.l)
|
||||
}
|
||||
if c.dl {
|
||||
c.sc.SetDebugLog(true)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wneessen/go-mail/log"
|
||||
"github.com/wneessen/go-mail/smtp"
|
||||
)
|
||||
|
||||
|
@ -106,6 +107,7 @@ func TestNewClientWithOptions(t *testing.T) {
|
|||
{"WithDSNRcptNotifyType() wrong option", WithDSNRcptNotifyType("FAIL"), true},
|
||||
{"WithoutNoop()", WithoutNoop(), false},
|
||||
{"WithDebugLog()", WithDebugLog(), false},
|
||||
{"WithLogger()", WithLogger(log.New(os.Stderr, log.LevelDebug)), false},
|
||||
|
||||
{
|
||||
"WithDSNRcptNotifyType() NEVER combination",
|
||||
|
@ -567,6 +569,31 @@ func TestClient_DialWithContext_Debug(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestClient_DialWithContext_Debug_custom tests the DialWithContext method for the Client
|
||||
// object with debug logging enabled and a custom logger on the SMTP client
|
||||
func TestClient_DialWithContext_Debug_custom(t *testing.T) {
|
||||
c, err := getTestClient(true)
|
||||
if err != nil {
|
||||
t.Skipf("failed to create test client: %s. Skipping tests", err)
|
||||
}
|
||||
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.")
|
||||
}
|
||||
c.SetDebugLog(true)
|
||||
c.SetLogger(log.New(os.Stderr, log.LevelDebug))
|
||||
if err := c.Close(); err != nil {
|
||||
t.Errorf("failed to close connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClient_DialWithContextInvalidHost tests the DialWithContext method with intentional breaking
|
||||
// for the Client object
|
||||
func TestClient_DialWithContextInvalidHost(t *testing.T) {
|
||||
|
|
14
log/log.go
Normal file
14
log/log.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: Copyright (c) 2022-2023 The go-mail Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package log implements a logger interface that can be used within the go-mail package
|
||||
package log
|
||||
|
||||
// Logger is the log interface for go-mail
|
||||
type Logger interface {
|
||||
Errorf(format string, v ...interface{})
|
||||
Warnf(format string, v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
}
|
74
log/stdlog.go
Normal file
74
log/stdlog.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// SPDX-FileCopyrightText: Copyright (c) 2023 The go-mail Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Level is a type wrapper for an int
|
||||
type Level int
|
||||
|
||||
// Stdlog is the default logger that satisfies the Logger interface
|
||||
type Stdlog struct {
|
||||
l Level
|
||||
err *log.Logger
|
||||
warn *log.Logger
|
||||
info *log.Logger
|
||||
debug *log.Logger
|
||||
}
|
||||
|
||||
const (
|
||||
// LevelError is the Level for only ERROR log messages
|
||||
LevelError Level = iota
|
||||
// LevelWarn is the Level for WARN and higher log messages
|
||||
LevelWarn
|
||||
// LevelInfo is the Level for INFO and higher log messages
|
||||
LevelInfo
|
||||
// LevelDebug is the Level for DEBUG and higher log messages
|
||||
LevelDebug
|
||||
)
|
||||
|
||||
// New returns a new Stdlog type that satisfies the Logger interface
|
||||
func New(o io.Writer, l Level) *Stdlog {
|
||||
lf := log.Lmsgprefix | log.LstdFlags
|
||||
return &Stdlog{
|
||||
l: l,
|
||||
err: log.New(o, "ERROR: ", lf),
|
||||
warn: log.New(o, " WARN: ", lf),
|
||||
info: log.New(o, " INFO: ", lf),
|
||||
debug: log.New(o, "DEBUG: ", lf),
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf performs a Printf() on the debug logger
|
||||
func (l *Stdlog) Debugf(f string, v ...interface{}) {
|
||||
if l.l >= LevelDebug {
|
||||
_ = l.debug.Output(2, fmt.Sprintf(f, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Infof performs a Printf() on the info logger
|
||||
func (l *Stdlog) Infof(f string, v ...interface{}) {
|
||||
if l.l >= LevelInfo {
|
||||
_ = l.info.Output(2, fmt.Sprintf(f, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Warnf performs a Printf() on the warn logger
|
||||
func (l *Stdlog) Warnf(f string, v ...interface{}) {
|
||||
if l.l >= LevelWarn {
|
||||
_ = l.warn.Output(2, fmt.Sprintf(f, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf performs a Printf() on the error logger
|
||||
func (l *Stdlog) Errorf(f string, v ...interface{}) {
|
||||
if l.l >= LevelError {
|
||||
_ = l.err.Output(2, fmt.Sprintf(f, v...))
|
||||
}
|
||||
}
|
93
log/stdlog_test.go
Normal file
93
log/stdlog_test.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// SPDX-FileCopyrightText: Copyright (c) 2023 The go-mail Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
l := New(&b, LevelDebug)
|
||||
if l.l != LevelDebug {
|
||||
t.Error("Expected level to be LevelDebug, got ", l.l)
|
||||
}
|
||||
if l.err == nil || l.warn == nil || l.info == nil || l.debug == nil {
|
||||
t.Error("Loggers not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugf(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
l := New(&b, LevelDebug)
|
||||
|
||||
l.Debugf("test %s", "foo")
|
||||
expected := "DEBUG: test foo\n"
|
||||
if !strings.HasSuffix(b.String(), expected) {
|
||||
t.Errorf("Expected %q, got %q", expected, b.String())
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
l.l = LevelInfo
|
||||
l.Debugf("test %s", "foo")
|
||||
if b.String() != "" {
|
||||
t.Error("Debug message was not expected to be logged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfof(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
l := New(&b, LevelInfo)
|
||||
|
||||
l.Infof("test %s", "foo")
|
||||
expected := " INFO: test foo\n"
|
||||
if !strings.HasSuffix(b.String(), expected) {
|
||||
t.Errorf("Expected %q, got %q", expected, b.String())
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
l.l = LevelWarn
|
||||
l.Infof("test %s", "foo")
|
||||
if b.String() != "" {
|
||||
t.Error("Info message was not expected to be logged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarnf(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
l := New(&b, LevelWarn)
|
||||
|
||||
l.Warnf("test %s", "foo")
|
||||
expected := " WARN: test foo\n"
|
||||
if !strings.HasSuffix(b.String(), expected) {
|
||||
t.Errorf("Expected %q, got %q", expected, b.String())
|
||||
}
|
||||
|
||||
b.Reset()
|
||||
l.l = LevelError
|
||||
l.Warnf("test %s", "foo")
|
||||
if b.String() != "" {
|
||||
t.Error("Warn message was not expected to be logged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
l := New(&b, LevelError)
|
||||
|
||||
l.Errorf("test %s", "foo")
|
||||
expected := "ERROR: test foo\n"
|
||||
if !strings.HasSuffix(b.String(), expected) {
|
||||
t.Errorf("Expected %q, got %q", expected, b.String())
|
||||
}
|
||||
b.Reset()
|
||||
l.l = LevelError - 1
|
||||
l.Warnf("test %s", "foo")
|
||||
if b.String() != "" {
|
||||
t.Error("Error message was not expected to be logged")
|
||||
}
|
||||
}
|
20
smtp/smtp.go
20
smtp/smtp.go
|
@ -26,11 +26,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/wneessen/go-mail/log"
|
||||
)
|
||||
|
||||
// A Client represents a client connection to an SMTP server.
|
||||
|
@ -53,7 +54,7 @@ type Client struct {
|
|||
helloError error // the error from the hello
|
||||
// debug logging
|
||||
debug bool // debug logging is enabled
|
||||
logger *log.Logger // logger will be used for debug logging
|
||||
logger log.Logger // logger will be used for debug logging
|
||||
// DSN support
|
||||
dsnmrtype string // dsnmrtype defines the mail return option in case DSN is enabled
|
||||
dsnrntype string // dsnrntype defines the recipient notify option in case DSN is enabled
|
||||
|
@ -441,12 +442,23 @@ func (c *Client) Quit() error {
|
|||
func (c *Client) SetDebugLog(v bool) {
|
||||
c.debug = v
|
||||
if v {
|
||||
c.logger = log.New(os.Stderr, "[DEBUG] ", log.LstdFlags|log.Lmsgprefix)
|
||||
if c.logger == nil {
|
||||
c.logger = log.New(os.Stderr, log.LevelDebug)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.logger = nil
|
||||
}
|
||||
|
||||
// SetLogger overrides the default log.Stdlog for the debug logging with a logger that
|
||||
// satisfies the log.Logger interface
|
||||
func (c *Client) SetLogger(l log.Logger) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
c.logger = l
|
||||
}
|
||||
|
||||
// SetDSNMailReturnOption sets the DSN mail return option for the Mail method
|
||||
func (c *Client) SetDSNMailReturnOption(d string) {
|
||||
c.dsnmrtype = d
|
||||
|
@ -465,7 +477,7 @@ func (c *Client) debugLog(d logDirection, f string, a ...interface{}) {
|
|||
p = "C --> S:"
|
||||
}
|
||||
fs := fmt.Sprintf("%s %s", p, f)
|
||||
c.logger.Printf(fs, a...)
|
||||
c.logger.Debugf(fs, a...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,13 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wneessen/go-mail/log"
|
||||
)
|
||||
|
||||
type authTest struct {
|
||||
|
@ -661,6 +664,37 @@ func TestClient_SetDebugLog(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestClient_SetLogger tests the Client method with the Client.SetLogger method
|
||||
// to provide a custom logger
|
||||
func TestClient_SetLogger(t *testing.T) {
|
||||
server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n")
|
||||
|
||||
var cmdbuf strings.Builder
|
||||
bcmdbuf := bufio.NewWriter(&cmdbuf)
|
||||
out := func() string {
|
||||
if err := bcmdbuf.Flush(); err != nil {
|
||||
t.Errorf("failed to flush: %s", err)
|
||||
}
|
||||
return cmdbuf.String()
|
||||
}
|
||||
var fake faker
|
||||
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
|
||||
c, err := NewClient(fake, "fake.host")
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient: %v\n(after %v)", err, out())
|
||||
}
|
||||
defer func() {
|
||||
_ = c.Close()
|
||||
}()
|
||||
c.SetLogger(log.New(os.Stderr, log.LevelDebug))
|
||||
if c.logger == nil {
|
||||
t.Errorf("Expected Logger to be set but received nil")
|
||||
}
|
||||
c.logger.Debugf("test")
|
||||
c.SetLogger(nil)
|
||||
c.logger.Debugf("test")
|
||||
}
|
||||
|
||||
var newClientServer = `220 hello world
|
||||
250-mx.google.com at your service
|
||||
250-SIZE 35651584
|
||||
|
|
Loading…
Reference in a new issue