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:
@@ -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 {
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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"
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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")
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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()
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
|
@@ -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"}
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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))
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
|
||||||
|
@@ -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))
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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()
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user