1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-08-13 20:04:49 +02:00

Chore: Refactor error handling and resource management across multiple files (golangci-lint)

- Updated error handling to use the error return value for resource closures in tests and functions, ensuring proper error reporting.
- Replaced direct calls to `Close()` with deferred functions that handle errors gracefully.
- Improved readability by using `strings.ReplaceAll` instead of `strings.Replace` for string manipulation.
- Enhanced network connection handling by adding default cases for unsupported network types.
- Updated HTTP response handling to use the appropriate status codes and error messages.
- Removed unused variables and commented-out code to clean up the codebase.
This commit is contained in:
Ralph Slooten
2025-06-22 10:32:03 +12:00
parent 429d2e2b3a
commit f99d9ecf69
35 changed files with 250 additions and 232 deletions

View File

@@ -55,7 +55,7 @@ The --recent flag will only consider files with a modification date within the l
logger.Log().Errorf("%s: %s", path, err.Error()) logger.Log().Errorf("%s: %s", path, err.Error())
return nil return nil
} }
defer f.Close() // #nosec defer func() { _ = f.Close() }()
body, err := io.ReadAll(f) body, err := io.ReadAll(f)
if err != nil { if err != nil {

View File

@@ -13,7 +13,7 @@ import (
// IsFile returns whether a file exists and is readable // IsFile returns whether a file exists and is readable
func isFile(path string) bool { func isFile(path string) bool {
f, err := os.Open(filepath.Clean(path)) f, err := os.Open(filepath.Clean(path))
defer f.Close() defer func() { _ = f.Close() }()
return err == nil return err == nil
} }

View File

@@ -39,14 +39,14 @@ func Sync(d string) error {
if URL != "" { if URL != "" {
if !linkRe.MatchString(URL) { if !linkRe.MatchString(URL) {
return errors.New("Invalid URL") return errors.New("invalid URL")
} }
base = strings.TrimRight(URL, "/") + "/" base = strings.TrimRight(URL, "/") + "/"
} }
if base == "" && config.Database == "" { if base == "" && config.Database == "" {
return errors.New("No database or API URL specified") return errors.New("no database or API URL specified")
} }
if !tools.IsDir(outDir) { if !tools.IsDir(outDir) {
@@ -109,7 +109,7 @@ func loadIDs() error {
} }
if len(summary) == 0 { if len(summary) == 0 {
return errors.New("No messages found") return errors.New("no messages found")
} }
return nil return nil

View File

@@ -193,10 +193,10 @@ func downloadToBytes(url string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
err := fmt.Errorf("Error downloading %s", url) err := fmt.Errorf("error downloading %s", url)
return nil, err return nil, err
} }

View File

@@ -152,13 +152,14 @@ func (c CanIEmail) getTest(k string) (Warning, error) {
s.Platform = platform s.Platform = platform
s.Version = version s.Version = version
if support == "y" { switch support {
case "y":
y++ y++
s.Support = "yes" s.Support = "yes"
} else if support == "n" { case "n":
n++ n++
s.Support = "no" s.Support = "no"
} else { default:
p++ p++
s.Support = "partial" s.Support = "partial"

View File

@@ -18,7 +18,7 @@ func authUser(username, password string) bool {
// Send a response with debug logging // Send a response with debug logging
func sendResponse(c net.Conn, m string) { func sendResponse(c net.Conn, m string) {
fmt.Fprintf(c, "%s\r\n", m) _, _ = fmt.Fprintf(c, "%s\r\n", m)
logger.Log().Debugf("[pop3] response: %s", m) logger.Log().Debugf("[pop3] response: %s", m)
if strings.HasPrefix(m, "-ERR ") { if strings.HasPrefix(m, "-ERR ") {
@@ -29,7 +29,7 @@ func sendResponse(c net.Conn, m string) {
// Send a response without debug logging (for data) // Send a response without debug logging (for data)
func sendData(c net.Conn, m string) { func sendData(c net.Conn, m string) {
fmt.Fprintf(c, "%s\r\n", m) _, _ = fmt.Fprintf(c, "%s\r\n", m)
} }
// Get the latest 100 messages // Get the latest 100 messages

View File

@@ -29,22 +29,21 @@ func TestPOP3(t *testing.T) {
// connect with bad password // connect with bad password
t.Log("Testing invalid login") t.Log("Testing invalid login")
c, err := connectBadAuth() if _, err := connectBadAuth(); err == nil {
if err == nil {
t.Error("invalid login gained access") t.Error("invalid login gained access")
return return
} }
t.Log("Testing valid login") t.Log("Testing valid login")
c, err = connectAuth() c, err := connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
count, size, err := c.Stat() count, size, err := c.Stat()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -53,7 +52,7 @@ func TestPOP3(t *testing.T) {
// quit else we get old data // quit else we get old data
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -63,13 +62,13 @@ func TestPOP3(t *testing.T) {
c, err = connectAuth() c, err = connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
count, _, err = c.Stat() count, _, err = c.Stat()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -80,7 +79,7 @@ func TestPOP3(t *testing.T) {
for i := 1; i <= 20; i++ { for i := 1; i <= 20; i++ {
_, err := c.Retr(i) _, err := c.Retr(i)
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
} }
@@ -89,14 +88,14 @@ func TestPOP3(t *testing.T) {
for i := 1; i <= 25; i++ { for i := 1; i <= 25; i++ {
if err := c.Dele(i); err != nil { if err := c.Dele(i); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
} }
// messages get deleted after a QUIT // messages get deleted after a QUIT
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -105,7 +104,7 @@ func TestPOP3(t *testing.T) {
c, err = connectAuth() c, err = connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -113,7 +112,7 @@ func TestPOP3(t *testing.T) {
count, _, err = c.Stat() count, _, err = c.Stat()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -121,13 +120,13 @@ func TestPOP3(t *testing.T) {
// messages get deleted after a QUIT // messages get deleted after a QUIT
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
c, err = connectAuth() c, err = connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -135,7 +134,7 @@ func TestPOP3(t *testing.T) {
for i := 1; i <= 25; i++ { for i := 1; i <= 25; i++ {
if err := c.Dele(i); err != nil { if err := c.Dele(i); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
} }
@@ -143,31 +142,31 @@ func TestPOP3(t *testing.T) {
t.Log("Undeleting messages") t.Log("Undeleting messages")
if err := c.Rset(); err != nil { if err := c.Rset(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
c, err = connectAuth() c, err = connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
count, _, err = c.Stat() count, _, err = c.Stat()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
assertEqual(t, count, 25, "incorrect message count") assertEqual(t, count, 25, "incorrect message count")
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
} }
@@ -190,7 +189,7 @@ func TestAuthentication(t *testing.T) {
// non-authenticated connection // non-authenticated connection
c, err := connect() c, err := connect()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -207,7 +206,7 @@ func TestAuthentication(t *testing.T) {
} }
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -216,7 +215,7 @@ func TestAuthentication(t *testing.T) {
// authenticated connection // authenticated connection
c, err = connectAuth() c, err = connectAuth()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
@@ -233,13 +232,15 @@ func TestAuthentication(t *testing.T) {
} }
if err := c.Quit(); err != nil { if err := c.Quit(); err != nil {
t.Errorf(err.Error()) t.Error(err.Error())
return return
} }
} }
func setup() { func setup() {
auth.SetPOP3Auth("username:password") if err := auth.SetPOP3Auth("username:password"); err != nil {
panic(err)
}
logger.NoLogging = true logger.NoLogging = true
config.MaxMessages = 0 config.MaxMessages = 0
config.Database = os.Getenv("MP_DATABASE") config.Database = os.Getenv("MP_DATABASE")

View File

@@ -271,7 +271,7 @@ func handleTransactionCommand(conn net.Conn, cmd string, args []string, messages
// begins with the termination octet, the line is "byte-stuffed" by // begins with the termination octet, the line is "byte-stuffed" by
// pre-pending the termination octet to that line of the response. // pre-pending the termination octet to that line of the response.
// @see: https://www.ietf.org/rfc/rfc1939.txt // @see: https://www.ietf.org/rfc/rfc1939.txt
sendData(conn, strings.Replace(string(raw), "\n.", "\n..", -1)) sendData(conn, strings.ReplaceAll(string(raw), "\n.", "\n.."))
sendResponse(conn, ".") sendResponse(conn, ".")
case "TOP": case "TOP":
arg, err := getSafeArg(args, 0) arg, err := getSafeArg(args, 0)

View File

@@ -422,7 +422,7 @@ func (c *Conn) Noop() error {
// Message deletions (DELE command) are only executed by the server on a graceful // Message deletions (DELE command) are only executed by the server on a graceful
// quit and close. // quit and close.
func (c *Conn) Quit() error { func (c *Conn) Quit() error {
defer c.conn.Close() defer func() { _ = c.conn.Close() }()
if _, err := c.Cmd("QUIT", false); err != nil { if _, err := c.Cmd("QUIT", false); err != nil {
return err return err

View File

@@ -179,10 +179,10 @@ func StartSeparateServer() {
func GetMode() string { func GetMode() string {
mode := strings.ToLower(strings.TrimSpace(config.PrometheusListen)) mode := strings.ToLower(strings.TrimSpace(config.PrometheusListen))
switch { switch mode {
case mode == "false", mode == "": case "false", "":
return "disabled" return "disabled"
case mode == "true": case "true":
return "integrated" return "integrated"
default: default:
return "separate" return "separate"

View File

@@ -37,7 +37,7 @@ func createForwardingSMTPClient(config config.SMTPForwardConfigStruct, addr stri
client, err := smtp.NewClient(conn, tlsConf.ServerName) client, err := smtp.NewClient(conn, tlsConf.ServerName)
if err != nil { if err != nil {
conn.Close() _ = conn.Close()
return nil, fmt.Errorf("SMTP client error: %v", err) return nil, fmt.Errorf("SMTP client error: %v", err)
} }
@@ -55,7 +55,7 @@ func createForwardingSMTPClient(config config.SMTPForwardConfigStruct, addr stri
tlsConf.InsecureSkipVerify = config.AllowInsecure tlsConf.InsecureSkipVerify = config.AllowInsecure
if err = client.StartTLS(tlsConf); err != nil { if err = client.StartTLS(tlsConf); err != nil {
client.Close() _ = client.Close()
return nil, fmt.Errorf("error creating StartTLS config: %v", err) return nil, fmt.Errorf("error creating StartTLS config: %v", err)
} }
} }
@@ -72,7 +72,7 @@ func forward(from string, msg []byte) error {
if err != nil { if err != nil {
return err return err
} }
defer c.Close() defer func() { _ = c.Close() }()
auth := forwardAuthFromConfig() auth := forwardAuthFromConfig()

View File

@@ -71,7 +71,7 @@ func createRelaySMTPClient(config config.SMTPRelayConfigStruct, addr string) (*s
client, err := smtp.NewClient(conn, tlsConf.ServerName) client, err := smtp.NewClient(conn, tlsConf.ServerName)
if err != nil { if err != nil {
conn.Close() _ = conn.Close()
return nil, fmt.Errorf("SMTP client error: %v", err) return nil, fmt.Errorf("SMTP client error: %v", err)
} }
@@ -89,7 +89,7 @@ func createRelaySMTPClient(config config.SMTPRelayConfigStruct, addr string) (*s
tlsConf.InsecureSkipVerify = config.AllowInsecure tlsConf.InsecureSkipVerify = config.AllowInsecure
if err = client.StartTLS(tlsConf); err != nil { if err = client.StartTLS(tlsConf); err != nil {
client.Close() _ = client.Close()
return nil, fmt.Errorf("error creating StartTLS config: %v", err) return nil, fmt.Errorf("error creating StartTLS config: %v", err)
} }
} }
@@ -106,7 +106,7 @@ func Relay(from string, to []string, msg []byte) error {
if err != nil { if err != nil {
return err return err
} }
defer c.Close() defer func() { _ = c.Close() }()
auth := relayAuthFromConfig() auth := relayAuthFromConfig()
@@ -193,7 +193,7 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
case "Password:": case "Password:":
return []byte(a.password), nil return []byte(a.password), nil
default: default:
return nil, errors.New("Unknown fromServer") return nil, errors.New("unknown fromServer")
} }
} }

View File

@@ -217,7 +217,7 @@ func (srv *Server) Serve(ln net.Listener) error {
return ErrServerClosed return ErrServerClosed
} }
defer ln.Close() defer func() { _ = ln.Close() }()
for { for {
// if we are shutting down, don't accept new connections // if we are shutting down, don't accept new connections
@@ -229,7 +229,7 @@ func (srv *Server) Serve(ln net.Listener) error {
conn, err := ln.Accept() conn, err := ln.Accept()
if err != nil { if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Temporary() { if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue continue
} }
return err return err
@@ -356,7 +356,9 @@ func (srv *Server) Shutdown(ctx context.Context) error {
// Function called to handle connection requests. // Function called to handle connection requests.
func (s *session) serve() { func (s *session) serve() {
defer atomic.AddInt32(&s.srv.openSessions, -1) defer atomic.AddInt32(&s.srv.openSessions, -1)
defer s.conn.Close() // pass the connection into the defer function to ensure it is closed,
// otherwise results in a 5s timeout for each connection
defer func(c net.Conn) { _ = c.Close() }(s.conn)
var from string var from string
var gotFrom bool var gotFrom bool
@@ -517,9 +519,9 @@ loop:
// On other errors, allow the client to try again. // On other errors, allow the client to try again.
data, err := s.readData() data, err := s.readData()
if err != nil { if err != nil {
switch err.(type) { switch err := err.(type) {
case net.Error: case net.Error:
if err.(net.Error).Timeout() { if err.Timeout() {
s.writef("421 4.4.2 %s %s ESMTP Service closing transmission channel after timeout exceeded", s.srv.Hostname, s.srv.AppName) s.writef("421 4.4.2 %s %s ESMTP Service closing transmission channel after timeout exceeded", s.srv.Hostname, s.srv.AppName)
} }
break loop break loop
@@ -749,7 +751,7 @@ func (s *session) writef(format string, args ...interface{}) {
} }
line := fmt.Sprintf(format, args...) line := fmt.Sprintf(format, args...)
fmt.Fprintf(s.bw, "%s\r\n", line) _, _ = fmt.Fprintf(s.bw, "%s\r\n", line)
_ = s.bw.Flush() _ = s.bw.Flush()
if Debug { if Debug {

View File

@@ -12,7 +12,6 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"os"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
@@ -40,7 +39,7 @@ func newConn(t *testing.T, server *Server) net.Conn {
// Send a command and verify the 3 digit code from the response. // Send a command and verify the 3 digit code from the response.
func cmdCode(t *testing.T, conn net.Conn, cmd string, code string) string { func cmdCode(t *testing.T, conn net.Conn, cmd string, code string) string {
fmt.Fprintf(conn, "%s\r\n", cmd) _, _ = fmt.Fprintf(conn, "%s\r\n", cmd)
resp, err := bufio.NewReader(conn).ReadString('\n') resp, err := bufio.NewReader(conn).ReadString('\n')
if err != nil { if err != nil {
t.Fatalf("Failed to read response from test server: %v", err) t.Fatalf("Failed to read response from test server: %v", err)
@@ -72,7 +71,9 @@ func TestSimpleCommands(t *testing.T) {
conn := newConn(t, &Server{}) conn := newConn(t, &Server{})
cmdCode(t, conn, tt.cmd, tt.code) cmdCode(t, conn, tt.cmd, tt.code)
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() if err := conn.Close(); err != nil {
t.Errorf("Failed to close connection after command %s: %v", tt.cmd, err)
}
} }
} }
@@ -90,7 +91,7 @@ func TestCmdHELO(t *testing.T) {
cmdCode(t, conn, "DATA", "503") cmdCode(t, conn, "DATA", "503")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdEHLO(t *testing.T) { func TestCmdEHLO(t *testing.T) {
@@ -107,7 +108,7 @@ func TestCmdEHLO(t *testing.T) {
cmdCode(t, conn, "DATA", "503") cmdCode(t, conn, "DATA", "503")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdRSET(t *testing.T) { func TestCmdRSET(t *testing.T) {
@@ -121,7 +122,7 @@ func TestCmdRSET(t *testing.T) {
cmdCode(t, conn, "DATA", "503") cmdCode(t, conn, "DATA", "503")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdMAIL(t *testing.T) { func TestCmdMAIL(t *testing.T) {
@@ -162,7 +163,7 @@ func TestCmdMAIL(t *testing.T) {
// TODO: MAIL with invalid AUTH parameter must return 501 syntax error // TODO: MAIL with invalid AUTH parameter must return 501 syntax error
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdMAILMaxSize(t *testing.T) { func TestCmdMAILMaxSize(t *testing.T) {
@@ -192,7 +193,7 @@ func TestCmdMAILMaxSize(t *testing.T) {
// Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2). // Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2).
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdRCPT(t *testing.T) { func TestCmdRCPT(t *testing.T) {
@@ -239,7 +240,7 @@ func TestCmdRCPT(t *testing.T) {
cmdCode(t, conn, "RCPT TO: <recipient@example.com>", "501") cmdCode(t, conn, "RCPT TO: <recipient@example.com>", "501")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdMaxRecipients(t *testing.T) { func TestCmdMaxRecipients(t *testing.T) {
@@ -256,7 +257,7 @@ func TestCmdMaxRecipients(t *testing.T) {
cmdCode(t, conn, "RCPT TO: <recipient5@example.com>", "452") cmdCode(t, conn, "RCPT TO: <recipient5@example.com>", "452")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdDATA(t *testing.T) { func TestCmdDATA(t *testing.T) {
@@ -286,7 +287,7 @@ func TestCmdDATA(t *testing.T) {
cmdCode(t, conn, "Test message.\r\n.", "250") cmdCode(t, conn, "Test message.\r\n.", "250")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdDATAWithMaxSize(t *testing.T) { func TestCmdDATAWithMaxSize(t *testing.T) {
@@ -323,7 +324,7 @@ func TestCmdDATAWithMaxSize(t *testing.T) {
// Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2). // Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2).
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
type mockHandler struct { type mockHandler struct {
@@ -347,7 +348,7 @@ func TestCmdDATAWithHandler(t *testing.T) {
cmdCode(t, conn, "DATA", "354") cmdCode(t, conn, "DATA", "354")
cmdCode(t, conn, "Test message.\r\n.", "250") cmdCode(t, conn, "Test message.\r\n.", "250")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
if m.handlerCalled != 1 { if m.handlerCalled != 1 {
t.Errorf("MailHandler called %d times, want one call", m.handlerCalled) t.Errorf("MailHandler called %d times, want one call", m.handlerCalled)
@@ -364,7 +365,7 @@ func TestCmdDATAWithHandlerError(t *testing.T) {
cmdCode(t, conn, "DATA", "354") cmdCode(t, conn, "DATA", "354")
cmdCode(t, conn, "Test message.\r\n.", "451") cmdCode(t, conn, "Test message.\r\n.", "451")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
if m.handlerCalled != 1 { if m.handlerCalled != 1 {
t.Errorf("MailHandler called %d times, want one call", m.handlerCalled) t.Errorf("MailHandler called %d times, want one call", m.handlerCalled)
@@ -382,7 +383,7 @@ func TestCmdSTARTTLS(t *testing.T) {
cmdCode(t, conn, "STARTTLS FOO", "501") cmdCode(t, conn, "STARTTLS FOO", "501")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdSTARTTLSFailure(t *testing.T) { func TestCmdSTARTTLSFailure(t *testing.T) {
@@ -411,7 +412,7 @@ func TestCmdSTARTTLSFailure(t *testing.T) {
} }
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
// Utility function to make a valid TLS certificate for use by the server. // Utility function to make a valid TLS certificate for use by the server.
@@ -497,7 +498,7 @@ func TestCmdSTARTTLSSuccess(t *testing.T) {
cmdCode(t, tlsConn, "STARTTLS", "503") cmdCode(t, tlsConn, "STARTTLS", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdSTARTTLSRequired(t *testing.T) { func TestCmdSTARTTLSRequired(t *testing.T) {
@@ -548,7 +549,7 @@ func TestCmdSTARTTLSRequired(t *testing.T) {
} }
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestMakeHeaders(t *testing.T) { func TestMakeHeaders(t *testing.T) {
@@ -798,8 +799,8 @@ func TestMakeEHLOResponse(t *testing.T) {
t.Errorf("AUTH does not appear in the extension list when an AuthHandler is specified") t.Errorf("AUTH does not appear in the extension list when an AuthHandler is specified")
} }
reLogin := regexp.MustCompile("\\bLOGIN\\b") reLogin := regexp.MustCompile(`\bLOGIN\b`)
rePlain := regexp.MustCompile("\\bPLAIN\\b") rePlain := regexp.MustCompile(`\bPLAIN\b`)
// RFC 4954 specifies that, without TLS in use, plaintext authentication mechanisms must not be advertised. // RFC 4954 specifies that, without TLS in use, plaintext authentication mechanisms must not be advertised.
s.tls = false s.tls = false
@@ -822,86 +823,86 @@ func TestMakeEHLOResponse(t *testing.T) {
} }
} }
func createTmpFile(content string) (file *os.File, err error) { // func createTmpFile(content string) (file *os.File, err error) {
file, err = os.CreateTemp("", "") // file, err = os.CreateTemp("", "")
if err != nil { // if err != nil {
return // return
} // }
_, err = file.Write([]byte(content)) // _, err = file.Write([]byte(content))
if err != nil { // if err != nil {
return // return
} // }
err = file.Close() // err = file.Close()
return // return
} // }
func createTLSFiles() ( // func createTLSFiles() (
certFile *os.File, // certFile *os.File,
keyFile *os.File, // keyFile *os.File,
passphrase string, // passphrase string,
err error, // err error,
) { // ) {
const certPEM = `-----BEGIN CERTIFICATE----- // const certPEM = `-----BEGIN CERTIFICATE-----
MIIDRzCCAi+gAwIBAgIJAKtg4oViVwv4MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV // MIIDRzCCAi+gAwIBAgIJAKtg4oViVwv4MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAgFw0xODA0MjAxMzMxNTBaGA8yMDg2MDUwODEzMzE1MFow // BAMMCWxvY2FsaG9zdDAgFw0xODA0MjAxMzMxNTBaGA8yMDg2MDUwODEzMzE1MFow
FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB // FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA8h7vl0gUquis5jRtcnETyD+8WITZO0s53aIzp0Y+9HXiHW6FGJjbOZjM // CgKCAQEA8h7vl0gUquis5jRtcnETyD+8WITZO0s53aIzp0Y+9HXiHW6FGJjbOZjM
IvozNVni+83QWKumRTgeSzIIW2j4V8iFMSNrvWmhmCKloesXS1aY6H979e01Ve8J // IvozNVni+83QWKumRTgeSzIIW2j4V8iFMSNrvWmhmCKloesXS1aY6H979e01Ve8J
WAJFRe6vZJd6gC6Z/P+ELU3ie4Vtr1GYfkV7nZ6VFp5/V/5nxGFag5TUlpP5hcoS // WAJFRe6vZJd6gC6Z/P+ELU3ie4Vtr1GYfkV7nZ6VFp5/V/5nxGFag5TUlpP5hcoS
9r2kvXofosVwe3x3udT8SEbv5eBD4bKeVyJs/RLbxSuiU1358Y1cDdVuHjcvfm3c // 9r2kvXofosVwe3x3udT8SEbv5eBD4bKeVyJs/RLbxSuiU1358Y1cDdVuHjcvfm3c
ajhheQ4vX9WXsk7LGGhnf1SrrPN/y+IDTXfvoHn+nJh4vMAB4yzQdE1V1N1AB8RA // ajhheQ4vX9WXsk7LGGhnf1SrrPN/y+IDTXfvoHn+nJh4vMAB4yzQdE1V1N1AB8RA
0yBVJ6dwxRrSg4BFrNWhj3gfsvrA7wIDAQABo4GZMIGWMB0GA1UdDgQWBBQ4/ncp // 0yBVJ6dwxRrSg4BFrNWhj3gfsvrA7wIDAQABo4GZMIGWMB0GA1UdDgQWBBQ4/ncp
befFuKH1hoYkPqLwuRrPRjAfBgNVHSMEGDAWgBQ4/ncpbefFuKH1hoYkPqLwuRrP // befFuKH1hoYkPqLwuRrPRjAfBgNVHSMEGDAWgBQ4/ncpbefFuKH1hoYkPqLwuRrP
RjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDALBgNVHQ8EBAMCBaAwEwYD // RjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDALBgNVHQ8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3 // VR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3
DQEBCwUAA4IBAQBJBetEXiEIzKAEpXGX87j6aUON51Fdf6BiLMCghuGKyhnaOG32 // DQEBCwUAA4IBAQBJBetEXiEIzKAEpXGX87j6aUON51Fdf6BiLMCghuGKyhnaOG32
4KJhtvVoS3ZUKPylh9c2VdItYlhWp76zd7YKk+3xUOixWeTMQHIvCvRGTyFibOPT // 4KJhtvVoS3ZUKPylh9c2VdItYlhWp76zd7YKk+3xUOixWeTMQHIvCvRGTyFibOPT
mApwp2pEnJCe4vjUrBaRhiyI+xnB70cWVF2qeernlLUeJA1mfYyQLz+v06ebDWOL // mApwp2pEnJCe4vjUrBaRhiyI+xnB70cWVF2qeernlLUeJA1mfYyQLz+v06ebDWOL
c/hPVQFB94lEdiyjGO7RZfIe8KwcK48g7iv0LQU4+c9MoWM2ZsVM1AL2tHzokSeA // c/hPVQFB94lEdiyjGO7RZfIe8KwcK48g7iv0LQU4+c9MoWM2ZsVM1AL2tHzokSeA
u64gDTW4K0Tzx1ab7KmOFXYUjbz/xWuReMt33EwDXAErKCjbVt2T55Qx8UoKzSh1 // u64gDTW4K0Tzx1ab7KmOFXYUjbz/xWuReMt33EwDXAErKCjbVt2T55Qx8UoKzSh1
tY0KDHdnYOzgsm2HIj2xcJqbeylYQvckNnoC // tY0KDHdnYOzgsm2HIj2xcJqbeylYQvckNnoC
-----END CERTIFICATE-----` // -----END CERTIFICATE-----`
const keyPEM = `-----BEGIN RSA PRIVATE KEY----- // const keyPEM = `-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED // Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,C16BF8745B2CDB53AC2B1D7609893AA0 // DEK-Info: AES-256-CBC,C16BF8745B2CDB53AC2B1D7609893AA0
O13z7Yq7butaJmMfg9wRis9YnIDPsp4coYI6Ud+JGcP7iXoy95QMhovKWx25o1ol // O13z7Yq7butaJmMfg9wRis9YnIDPsp4coYI6Ud+JGcP7iXoy95QMhovKWx25o1ol
tvUTsrsG27fHGf9qG02KizApIVtO9c1e0swCWzFrKRQX0JDiZDmilb9xosBNNst1 // tvUTsrsG27fHGf9qG02KizApIVtO9c1e0swCWzFrKRQX0JDiZDmilb9xosBNNst1
BOzOTRZEwFGSOCKZRBfSXyqC93TvLJ3DO9IUnKIeGt7upipvg29b/Dur/fyCy2WV // BOzOTRZEwFGSOCKZRBfSXyqC93TvLJ3DO9IUnKIeGt7upipvg29b/Dur/fyCy2WV
bLHXwUTDBm7j49yfoEyGkDjoB2QO9wgcgbacbnQJQ25fTFUwZpZJEJv6o1tRhoYM // bLHXwUTDBm7j49yfoEyGkDjoB2QO9wgcgbacbnQJQ25fTFUwZpZJEJv6o1tRhoYM
ZMOhC9x1URmdHKN1+z2y5BrB6oNpParfeAMEvs/9FE6jJwYUR28Ql6Mhphfvr9W2 // ZMOhC9x1URmdHKN1+z2y5BrB6oNpParfeAMEvs/9FE6jJwYUR28Ql6Mhphfvr9W2
5Gxd3J65Ao9Vi2I5j5X6aBuNjyhXN3ScLjPG4lVZm9RU/uTPEt81pig/d5nSAjvF // 5Gxd3J65Ao9Vi2I5j5X6aBuNjyhXN3ScLjPG4lVZm9RU/uTPEt81pig/d5nSAjvF
Nfc08NuG3cnMyJSE/xScJ4D+GtX8U969wO4oKPCR4E/NFyXPR730ppupDFG6hzPD // Nfc08NuG3cnMyJSE/xScJ4D+GtX8U969wO4oKPCR4E/NFyXPR730ppupDFG6hzPD
PDmiszDtU438JAZ8AuFa1LkbyFnEW6KVD4h7VRr8YDjirCqnkgjNSI6dFY0NQ8H7 // PDmiszDtU438JAZ8AuFa1LkbyFnEW6KVD4h7VRr8YDjirCqnkgjNSI6dFY0NQ8H7
SyexB0lrceX6HZc+oNdAtkX3tYdzY3ExzUM5lSF1dkldnRbApLbqc4uuNIVXhXFM // SyexB0lrceX6HZc+oNdAtkX3tYdzY3ExzUM5lSF1dkldnRbApLbqc4uuNIVXhXFM
dJnoPdKAzM6i+2EeVUxWNdafKDxnjVSHIHzHfIFJLQ4GS5rnz9keRFdyDjQL07tT // dJnoPdKAzM6i+2EeVUxWNdafKDxnjVSHIHzHfIFJLQ4GS5rnz9keRFdyDjQL07tT
Lu9pPOmsadDXp7oSa81RgoCUfNZeR4jKpCk2BOft0L6ZSqwYFLcQHLIfJaGfn902 // Lu9pPOmsadDXp7oSa81RgoCUfNZeR4jKpCk2BOft0L6ZSqwYFLcQHLIfJaGfn902
TUOTxHt0KzEUYeYSrXC2a6cyvXAd1YI7lOgy60qG89VHyCc2v5Bs4c4FNUDC/+Dj // TUOTxHt0KzEUYeYSrXC2a6cyvXAd1YI7lOgy60qG89VHyCc2v5Bs4c4FNUDC/+Dj
4ZwogaAbSNkLaE0q3sYQRPdxSqLftyX0KitAgE7oGtdzBfe1cdBoozw3U67NEMMT // 4ZwogaAbSNkLaE0q3sYQRPdxSqLftyX0KitAgE7oGtdzBfe1cdBoozw3U67NEMMT
6qvk5j7RepPRSrapHtK5pMMdg5XpKFWcOXZ26VHVrDCj4JKdjVb4iyiQi94VveV0 // 6qvk5j7RepPRSrapHtK5pMMdg5XpKFWcOXZ26VHVrDCj4JKdjVb4iyiQi94VveV0
w9+KcOtyrM7/jbQlCWnXpsIkP8VA/RIgh7CBn/h4oF1sO8ywP25OGQ7VWAVq1R9D // w9+KcOtyrM7/jbQlCWnXpsIkP8VA/RIgh7CBn/h4oF1sO8ywP25OGQ7VWAVq1R9D
8bl8GzIdR9PZpFyOxuIac4rPa8tkDeoXKs4cxoao7H/OZO9o9aTB7CJMTL9yv0Kb // 8bl8GzIdR9PZpFyOxuIac4rPa8tkDeoXKs4cxoao7H/OZO9o9aTB7CJMTL9yv0Kb
ntWuYxQchE6syoGsOgdGyZhaw4JeFkasDUP5beyNY+278NkzgGTOIMMTXIX46woP // ntWuYxQchE6syoGsOgdGyZhaw4JeFkasDUP5beyNY+278NkzgGTOIMMTXIX46woP
ehzHKGHXVGf7ZiSFF+zAHMXZRSwNVMkOYwlIoRg1IbvIRbAXqAR6xXQTCVzNG0SU // ehzHKGHXVGf7ZiSFF+zAHMXZRSwNVMkOYwlIoRg1IbvIRbAXqAR6xXQTCVzNG0SU
cskojycBca1Cz3hDVIKYZd9beDhprVdr2a4K2nft2g2xRNjKPopsaqXx+VPibFUx // cskojycBca1Cz3hDVIKYZd9beDhprVdr2a4K2nft2g2xRNjKPopsaqXx+VPibFUx
X7542eQ3eAlhkWUuXvt0q5a9WJdjJp9ODA0/d0akF6JQlEHIAyLfoUKB1HYwgUGG // X7542eQ3eAlhkWUuXvt0q5a9WJdjJp9ODA0/d0akF6JQlEHIAyLfoUKB1HYwgUGG
6uRm651FDAab9U4cVC5PY1hfv/QwzpkNDkzgJAZ5SMOfZhq7IdBcqGd3lzPmq2FP // 6uRm651FDAab9U4cVC5PY1hfv/QwzpkNDkzgJAZ5SMOfZhq7IdBcqGd3lzPmq2FP
Vy1LVZIl3eM+9uJx5TLsBHH6NhMwtNhFCNa/5ksodQYlTvR8IrrgWlYg4EL69vjS // Vy1LVZIl3eM+9uJx5TLsBHH6NhMwtNhFCNa/5ksodQYlTvR8IrrgWlYg4EL69vjS
yt6HhhEN3lFCWvrQXQMp93UklbTlpVt6qcDXiC7HYbs3+EINargRd5Z+xL5i5vkN // yt6HhhEN3lFCWvrQXQMp93UklbTlpVt6qcDXiC7HYbs3+EINargRd5Z+xL5i5vkN
f9k7s0xqhloWNPZcyOXMrox8L81WOY+sP4mVlGcfDRLdEJ8X2ofJpOAcwYCnjsKd // f9k7s0xqhloWNPZcyOXMrox8L81WOY+sP4mVlGcfDRLdEJ8X2ofJpOAcwYCnjsKd
uEGsi+l2fTj/F+eZLE6sYoMprgJrbfeqtRWFguUgTn7s5hfU0tZ46al5d0vz8fWK // uEGsi+l2fTj/F+eZLE6sYoMprgJrbfeqtRWFguUgTn7s5hfU0tZ46al5d0vz8fWK
-----END RSA PRIVATE KEY-----` // -----END RSA PRIVATE KEY-----`
passphrase = "test" // passphrase = "test"
certFile, err = createTmpFile(certPEM) // certFile, err = createTmpFile(certPEM)
if err != nil { // if err != nil {
return // return
} // }
keyFile, err = createTmpFile(keyPEM) // keyFile, err = createTmpFile(keyPEM)
return // return
} // }
func TestAuthMechs(t *testing.T) { func TestAuthMechs(t *testing.T) {
s := session{} s := session{}
@@ -966,7 +967,7 @@ func TestCmdAUTH(t *testing.T) {
cmdCode(t, conn, "AUTH", "502") cmdCode(t, conn, "AUTH", "502")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdAUTHOptional(t *testing.T) { func TestCmdAUTHOptional(t *testing.T) {
@@ -1007,7 +1008,7 @@ func TestCmdAUTHOptional(t *testing.T) {
cmdCode(t, conn, "*", "501") cmdCode(t, conn, "*", "501")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdAUTHRequired(t *testing.T) { func TestCmdAUTHRequired(t *testing.T) {
@@ -1056,7 +1057,7 @@ func TestCmdAUTHRequired(t *testing.T) {
cmdCode(t, conn, "AUTH PLAIN", "504") cmdCode(t, conn, "AUTH PLAIN", "504")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdAUTHLOGIN(t *testing.T) { func TestCmdAUTHLOGIN(t *testing.T) {
@@ -1113,7 +1114,7 @@ func TestCmdAUTHLOGIN(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdAUTHLOGINFast(t *testing.T) { func TestCmdAUTHLOGINFast(t *testing.T) {
@@ -1165,7 +1166,7 @@ func TestCmdAUTHLOGINFast(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdAUTHPLAIN(t *testing.T) { func TestCmdAUTHPLAIN(t *testing.T) {
@@ -1224,7 +1225,7 @@ func TestCmdAUTHPLAIN(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdAUTHPLAINEmpty(t *testing.T) { func TestCmdAUTHPLAINEmpty(t *testing.T) {
@@ -1283,7 +1284,7 @@ func TestCmdAUTHPLAINEmpty(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdAUTHPLAINFast(t *testing.T) { func TestCmdAUTHPLAINFast(t *testing.T) {
@@ -1335,7 +1336,7 @@ func TestCmdAUTHPLAINFast(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) { func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) {
@@ -1387,7 +1388,7 @@ func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
// makeCRAMMD5Response is a helper function to create the CRAM-MD5 hash. // makeCRAMMD5Response is a helper function to create the CRAM-MD5 hash.
@@ -1457,7 +1458,7 @@ func TestCmdAUTHCRAMMD5(t *testing.T) {
cmdCode(t, conn, "AUTH CRAM-MD5", "503") cmdCode(t, conn, "AUTH CRAM-MD5", "503")
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
conn.Close() _ = conn.Close()
} }
func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) { func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) {
@@ -1523,7 +1524,7 @@ func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) {
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503") cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
cmdCode(t, tlsConn, "QUIT", "221") cmdCode(t, tlsConn, "QUIT", "221")
tlsConn.Close() _ = tlsConn.Close()
} }
// Benchmark the mail handling without the network stack introducing latency. // Benchmark the mail handling without the network stack introducing latency.
@@ -1540,17 +1541,17 @@ func BenchmarkReceive(b *testing.B) {
// Benchmark a full mail transaction. // Benchmark a full mail transaction.
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
fmt.Fprintf(clientConn, "%s\r\n", "HELO host.example.com") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "HELO host.example.com")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
fmt.Fprintf(clientConn, "%s\r\n", "MAIL FROM:<sender@example.com>") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "MAIL FROM:<sender@example.com>")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
fmt.Fprintf(clientConn, "%s\r\n", "RCPT TO:<recipient@example.com>") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "RCPT TO:<recipient@example.com>")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
fmt.Fprintf(clientConn, "%s\r\n", "DATA") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "DATA")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
fmt.Fprintf(clientConn, "%s\r\n", "Test message.\r\n.") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "Test message.\r\n.")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
fmt.Fprintf(clientConn, "%s\r\n", "QUIT") _, _ = fmt.Fprintf(clientConn, "%s\r\n", "QUIT")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
} }
} }
@@ -1589,11 +1590,11 @@ func TestCmdShutdown(t *testing.T) {
cmdCode(t, conn, "QUIT", "221") cmdCode(t, conn, "QUIT", "221")
// connection should now be closed // connection should now be closed
fmt.Fprintf(conn, "%s\r\n", "HELO host.example.com") _, _ = fmt.Fprintf(conn, "%s\r\n", "HELO host.example.com")
_, err := bufio.NewReader(conn).ReadString('\n') _, err := bufio.NewReader(conn).ReadString('\n')
if err != io.EOF { if err != io.EOF {
t.Errorf("Expected connection to be closed\n") t.Errorf("Expected connection to be closed\n")
} }
conn.Close() _ = conn.Close()
} }

View File

@@ -59,7 +59,7 @@ func Check(email []byte, timeout int) (Response, error) {
return r, err return r, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
err = json.NewDecoder(resp.Body).Decode(&r) err = json.NewDecoder(resp.Body).Decode(&r)

View File

@@ -70,21 +70,22 @@ type Result struct {
// dial connects to spamd through TCP or a Unix socket. // dial connects to spamd through TCP or a Unix socket.
func (c *Client) dial() (connection, error) { func (c *Client) dial() (connection, error) {
if c.net == "tcp" { switch c.net {
case "tcp":
tcpAddr, err := net.ResolveTCPAddr("tcp", c.addr) tcpAddr, err := net.ResolveTCPAddr("tcp", c.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return net.DialTCP("tcp", nil, tcpAddr) return net.DialTCP("tcp", nil, tcpAddr)
} else if c.net == "unix" { case "unix":
unixAddr, err := net.ResolveUnixAddr("unix", c.addr) unixAddr, err := net.ResolveUnixAddr("unix", c.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return net.DialUnix("unix", nil, unixAddr) return net.DialUnix("unix", nil, unixAddr)
default:
return nil, fmt.Errorf("unsupported network type: %s", c.net)
} }
panic("Client.net must be either \"tcp\" or \"unix\"")
} }
// Report checks if message is spam or not, and returns score plus report // Report checks if message is spam or not, and returns score plus report
@@ -103,7 +104,7 @@ func (c *Client) report(email []byte) ([]string, error) {
return nil, err return nil, err
} }
defer conn.Close() defer func() { _ = conn.Close() }()
if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil { if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil {
return nil, err return nil, err
@@ -221,7 +222,7 @@ func (c *Client) Ping() error {
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() defer func() { _ = conn.Close() }()
if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil { if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil {
return err return err

View File

@@ -27,7 +27,6 @@ import (
var ( var (
db *sql.DB db *sql.DB
dbFile string
sqlDriver string sqlDriver string
dbLastAction time.Time dbLastAction time.Time
@@ -139,7 +138,6 @@ func InitDB() error {
LoadTagFilters() LoadTagFilters()
dbFile = p
dbLastAction = time.Now() dbLastAction = time.Now()
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)

View File

@@ -86,7 +86,7 @@ func Store(body *[]byte, username *string) (string, error) {
} }
// roll back if it fails // roll back if it fails
defer tx.Rollback() defer func() { _ = tx.Rollback() }()
subject := env.GetHeader("Subject") subject := env.GetHeader("Subject")
size := uint64(len(*body)) size := uint64(len(*body))
@@ -118,8 +118,6 @@ func Store(body *[]byte, username *string) (string, error) {
} else { } else {
_, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 1)`, tenant("mailbox_data")), id, compressed) // #nosec _, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 1)`, tenant("mailbox_data")), id, compressed) // #nosec
} }
compressed = nil
} else { } else {
// insert uncompressed raw message // insert uncompressed raw message
_, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 0)`, tenant("mailbox_data")), id, string(*body)) // #nosec _, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 0)`, tenant("mailbox_data")), id, string(*body)) // #nosec
@@ -639,7 +637,7 @@ func DeleteMessages(ids []string) error {
if err != nil { if err != nil {
return err return err
} }
defer rows.Close() defer func() { _ = rows.Close() }()
toDelete := []string{} toDelete := []string{}
var totalSize uint64 var totalSize uint64
@@ -738,7 +736,7 @@ func DeleteAllMessages() error {
} }
// roll back if it fails // roll back if it fails
defer tx.Rollback() defer func() { _ = tx.Rollback() }()
tables := []string{"mailbox", "mailbox_data", "tags", "message_tags"} tables := []string{"mailbox", "mailbox_data", "tags", "message_tags"}

View File

@@ -114,7 +114,7 @@ func ReindexAll() {
} }
// roll back if it fails // roll back if it fails
defer tx.Rollback() defer func() { _ = tx.Rollback() }()
// insert mail summary data // insert mail summary data
for _, u := range updates { for _, u := range updates {

View File

@@ -193,7 +193,7 @@ func DeleteSearch(search, timezone string) error {
} }
// roll back if it fails // roll back if it fails
defer tx.Rollback() defer func() { _ = tx.Rollback() }()
for _, ids := range chunks { for _, ids := range chunks {
delIDs := make([]interface{}, len(ids)) delIDs := make([]interface{}, len(ids))

View File

@@ -8,7 +8,7 @@ import (
// IsFile returns whether a file exists and is readable // IsFile returns whether a file exists and is readable
func IsFile(path string) bool { func IsFile(path string) bool {
f, err := os.Open(filepath.Clean(path)) f, err := os.Open(filepath.Clean(path))
defer f.Close() defer func() { _ = f.Close() }()
return err == nil return err == nil
} }

View File

@@ -27,7 +27,7 @@ func ListUnsubscribeParser(v string) ([]string, error) {
comments := reComments.FindAllStringSubmatch(v, -1) comments := reComments.FindAllStringSubmatch(v, -1)
for _, c := range comments { for _, c := range comments {
// strip comments // strip comments
v = strings.Replace(v, c[0], "", -1) v = strings.ReplaceAll(v, c[0], "")
v = strings.TrimSpace(v) v = strings.TrimSpace(v)
} }

View File

@@ -115,7 +115,7 @@ func extract(filePath string, directory string) error {
if err != nil { if err != nil {
return err return err
} }
defer gzipReader.Close() defer func() { _ = gzipReader.Close() }()
tarReader := tar.NewReader(gzipReader) tarReader := tar.NewReader(gzipReader)

View File

@@ -19,7 +19,7 @@ func Unzip(src string, dest string) ([]string, error) {
if err != nil { if err != nil {
return filenames, err return filenames, err
} }
defer r.Close() defer func() { _ = r.Close() }()
for _, f := range r.File { for _, f := range r.File {

View File

@@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -70,7 +71,7 @@ func GithubLatest(repo, name string) (string, string, string, error) {
return "", "", "", err return "", "", "", err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
@@ -119,7 +120,7 @@ func GithubLatest(repo, name string) (string, string, string, error) {
if len(allReleases) == 0 { if len(allReleases) == 0 {
// no releases with suitable assets found // no releases with suitable assets found
return "", "", "", fmt.Errorf("No binary releases found") return "", "", "", errors.New("no binary releases found")
} }
var latestRelease = Release{} var latestRelease = Release{}
@@ -149,11 +150,11 @@ func GithubUpdate(repo, appName, currentVersion string) (string, error) {
} }
if ver == currentVersion { if ver == currentVersion {
return "", fmt.Errorf("No new release found") return "", errors.New("no new release found")
} }
if semver.Compare(ver, currentVersion) < 1 { if semver.Compare(ver, currentVersion) < 1 {
return "", fmt.Errorf("No newer releases found (latest %s)", ver) return "", fmt.Errorf("no newer releases found (latest %s)", ver)
} }
tmpDir := getTempDir() tmpDir := getTempDir()
@@ -212,7 +213,7 @@ func downloadToFile(url, fileName string) error {
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
// Create the file // Create the file
out, err := os.Create(filepath.Clean(fileName)) out, err := os.Create(filepath.Clean(fileName))

View File

@@ -121,14 +121,14 @@ func Run() {
// handles `sendmail -bs` // handles `sendmail -bs`
// telnet directly to SMTP // telnet directly to SMTP
if UseB && UseS { if UseB && UseS {
var caller telnet.Caller = telnet.StandardCaller var caller = telnet.StandardCaller
switch isSocket {
if isSocket { case true:
if err := telnet.DialToAndCallUnix(socketAddr, caller); err != nil { if err := telnet.DialToAndCallUnix(socketAddr, caller); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} else { default:
if err := telnet.DialToAndCall(SMTPAddr, caller); err != nil { if err := telnet.DialToAndCall(SMTPAddr, caller); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@@ -18,7 +18,7 @@ func fourOFour(w http.ResponseWriter) {
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "404 page not found") _, _ = fmt.Fprint(w, "404 page not found")
} }
// HTTPError returns a basic error message (400 response) // HTTPError returns a basic error message (400 response)
@@ -27,7 +27,7 @@ func httpError(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, msg) _, _ = fmt.Fprint(w, msg)
} }
// httpJSONError returns a basic error message (400 response) in JSON format // httpJSONError returns a basic error message (400 response) in JSON format

View File

@@ -49,7 +49,7 @@ func GetMessage(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }
@@ -108,7 +108,7 @@ func GetHeaders(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }
@@ -179,7 +179,7 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }
@@ -238,7 +238,7 @@ func DownloadRaw(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }

View File

@@ -210,7 +210,7 @@ func SpamAssassinCheck(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }

View File

@@ -67,7 +67,7 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }
@@ -75,12 +75,12 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
msg, err := storage.GetMessage(id) msg, err := storage.GetMessage(id)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, "Message not found") _, _ = fmt.Fprint(w, "Message not found")
return return
} }
if msg.HTML == "" { if msg.HTML == "" {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, "This message does not contain a HTML part") _, _ = fmt.Fprint(w, "This message does not contain a HTML part")
return return
} }
@@ -161,7 +161,7 @@ func GetMessageText(w http.ResponseWriter, r *http.Request) {
id, err = storage.LatestID(r) id, err = storage.LatestID(r)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, err.Error()) _, _ = fmt.Fprint(w, err.Error())
return return
} }
} }
@@ -169,7 +169,7 @@ func GetMessageText(w http.ResponseWriter, r *http.Request) {
msg, err := storage.GetMessage(id) msg, err := storage.GetMessage(id)
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
fmt.Fprint(w, "Message not found") _, _ = fmt.Fprint(w, "Message not found")
return return
} }

View File

@@ -39,5 +39,5 @@ func RedirectToLatestMessage(w http.ResponseWriter, r *http.Request) {
} }
} }
http.Redirect(w, r, uri, 302) http.Redirect(w, r, uri, http.StatusFound)
} }

View File

@@ -60,7 +60,8 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
logger.Log().Warnf("[proxy] %s", err.Error()) logger.Log().Warnf("[proxy] %s", err.Error())
@@ -141,7 +142,7 @@ func absoluteURL(link, baseURL string) (string, error) {
// ensure link is HTTP(S) // ensure link is HTTP(S)
if result.Scheme != "http" && result.Scheme != "https" { if result.Scheme != "http" && result.Scheme != "https" {
return link, fmt.Errorf("Invalid URL: %s", result.String()) return link, fmt.Errorf("invalid URL: %s", result.String())
} }
return result.String(), nil return result.String(), nil
@@ -153,5 +154,5 @@ func httpError(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy) w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, msg) _, _ = fmt.Fprint(w, msg)
} }

View File

@@ -290,8 +290,9 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
} }
// Check basic authentication headers if configured. // Check basic authentication headers if configured.
// OPTIONS requests are skipped if CORS is enabled, since browsers omit credentials for preflight. // OPTIONS requests are skipped if CORS is enabled, since browsers omit credentials for preflight checks.
if !(AccessControlAllowOrigin != "" && r.Method == http.MethodOptions) && auth.UICredentials != nil { isCORSOptionsRequest := AccessControlAllowOrigin != "" && r.Method == http.MethodOptions
if !isCORSOptionsRequest && auth.UICredentials != nil {
user, pass, ok := r.BasicAuth() user, pass, ok := r.BasicAuth()
if !ok { if !ok {
@@ -312,7 +313,7 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
w.Header().Set("Content-Encoding", "gzip") w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w) gz := gzip.NewWriter(w)
defer gz.Close() defer func() { _ = gz.Close() }()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w} gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
fn(gzr, r) fn(gzr, r)
} }

View File

@@ -345,7 +345,9 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
// Set up UI auth that would normally block requests // Set up UI auth that would normally block requests
testHash, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.DefaultCost) testHash, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.DefaultCost)
auth.SetUIAuth("testuser:" + string(testHash)) if err := auth.SetUIAuth("testuser:" + string(testHash)); err != nil {
t.Fatalf("Failed to set UI auth: %s", err.Error())
}
r := apiRoutes() r := apiRoutes()
ts := httptest.NewServer(r) ts := httptest.NewServer(r)
@@ -374,11 +376,15 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
// Set up UI auth // Set up UI auth
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost) uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
auth.SetUIAuth("uiuser:" + string(uiHash)) if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
t.Fatalf("Failed to set UI auth: %s", err.Error())
}
// Set up dedicated Send API auth // Set up dedicated Send API auth
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost) sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
auth.SetSendAPIAuth("senduser:" + string(sendHash)) if err := auth.SetSendAPIAuth("senduser:" + string(sendHash)); err != nil {
t.Fatalf("Failed to set Send API auth: %s", err.Error())
}
r := apiRoutes() r := apiRoutes()
ts := httptest.NewServer(r) ts := httptest.NewServer(r)
@@ -421,7 +427,9 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
// Set up only UI auth // Set up only UI auth
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost) uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
auth.SetUIAuth("uiuser:" + string(uiHash)) if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
t.Fatalf("Failed to set UI auth: %s", err.Error())
}
r := apiRoutes() r := apiRoutes()
ts := httptest.NewServer(r) ts := httptest.NewServer(r)
@@ -455,9 +463,14 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
// Set up UI auth and Send API auth // Set up UI auth and Send API auth
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost) uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
auth.SetUIAuth("uiuser:" + string(uiHash)) if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
t.Fatalf("Failed to set UI auth: %s", err.Error())
}
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost) sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
auth.SetSendAPIAuth("senduser:" + string(sendHash)) if err := auth.SetSendAPIAuth("senduser:" + string(sendHash)); err != nil {
t.Fatalf("Failed to set Send API auth: %s", err.Error())
}
r := apiRoutes() r := apiRoutes()
ts := httptest.NewServer(r) ts := httptest.NewServer(r)
@@ -604,7 +617,7 @@ func clientGet(url string) ([]byte, error) {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
@@ -625,7 +638,7 @@ func clientDelete(url, body string) ([]byte, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
@@ -650,7 +663,7 @@ func clientPut(url, body string) ([]byte, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
@@ -675,7 +688,7 @@ func clientPost(url, body string) ([]byte, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
@@ -702,7 +715,7 @@ func clientPostWithAuth(url, body, username, password string) ([]byte, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
@@ -728,7 +741,7 @@ func clientGetWithAuth(url, username, password string) ([]byte, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode) return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)

View File

@@ -22,7 +22,7 @@ var (
) )
// Send will post the MessageSummary to a webhook (if configured) // Send will post the MessageSummary to a webhook (if configured)
func Send(msg interface{}) { func Send(msg any) {
if config.WebhookURL == "" { if config.WebhookURL == "" {
return return
} }
@@ -70,7 +70,7 @@ func Send(msg interface{}) {
return return
} }
defer resp.Body.Close() _ = resp.Body.Close()
}) })
}() }()
} }