diff --git a/cmd/client/main.go b/cmd/client/main.go index 7ea2867..15c0556 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -5,8 +5,10 @@ import ( "context" "flag" "fmt" + "net" "os" "regexp" + "strconv" "github.com/google/subcommands" ) @@ -68,7 +70,8 @@ func main() { } func baseURL() string { - return fmt.Sprintf("http://%s:%v", *host, *port) + return fmt.Sprintf("http://%s", + net.JoinHostPort(*host, strconv.FormatUint(uint64(*port), 10))) } func fatal(msg string, err error) subcommands.ExitStatus { diff --git a/cmd/inbucket/main.go b/cmd/inbucket/main.go index 1f9b407..254d9e7 100644 --- a/cmd/inbucket/main.go +++ b/cmd/inbucket/main.go @@ -162,7 +162,7 @@ signalLoop: } // openLog configures zerolog output, returns func to close logfile. -func openLog(level string, logfile string, json bool) (close func(), err error) { +func openLog(level string, logfile string, json bool) (closeLog func(), err error) { switch level { case "debug": zerolog.SetGlobalLevel(zerolog.DebugLevel) @@ -175,7 +175,8 @@ func openLog(level string, logfile string, json bool) (close func(), err error) default: return nil, fmt.Errorf("log level %q not one of: debug, info, warn, error", level) } - close = func() {} + + closeLog = func() {} var w io.Writer color := runtime.GOOS != "windows" switch logfile { @@ -191,21 +192,24 @@ func openLog(level string, logfile string, json bool) (close func(), err error) bw := bufio.NewWriter(logf) w = bw color = false - close = func() { + closeLog = func() { _ = bw.Flush() _ = logf.Close() } } + w = zerolog.SyncWriter(w) if json { log.Logger = log.Output(w) - return close, nil + return closeLog, nil } + log.Logger = log.Output(zerolog.ConsoleWriter{ Out: w, NoColor: !color, }) - return close, nil + + return closeLog, nil } // removePIDFile removes the PID file if created. diff --git a/pkg/policy/address.go b/pkg/policy/address.go index a0b628f..f9d7ea4 100644 --- a/pkg/policy/address.go +++ b/pkg/policy/address.go @@ -285,7 +285,7 @@ LOOP: return } inCharQuote = false - case bytes.IndexByte([]byte("!#$%&'*+-/=?^_`{|}~"), c) >= 0: + case strings.IndexByte("!#$%&'*+-/=?^_`{|}~", c) >= 0: // These specials can be used unquoted. err = buf.WriteByte(c) if err != nil { @@ -378,7 +378,7 @@ func parseMailboxName(localPart string) (result string, err error) { switch { case 'a' <= c && c <= 'z': case '0' <= c && c <= '9': - case bytes.IndexByte([]byte("!#$%&'*+-=/?^_`.{|}~"), c) >= 0: + case strings.IndexByte("!#$%&'*+-=/?^_`.{|}~", c) >= 0: default: invalid = append(invalid, c) } diff --git a/pkg/policy/address_test.go b/pkg/policy/address_test.go index 3d8eb69..a9cde5a 100644 --- a/pkg/policy/address_test.go +++ b/pkg/policy/address_test.go @@ -283,24 +283,18 @@ func TestExtractMailboxValid(t *testing.T) { for _, tc := range testTable { if result, err := localPolicy.ExtractMailbox(tc.input); err != nil { t.Errorf("Error while parsing with local naming %q: %v", tc.input, err) - } else { - if result != tc.local { - t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.local, result) - } + } else if result != tc.local { + t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.local, result) } if result, err := fullPolicy.ExtractMailbox(tc.input); err != nil { t.Errorf("Error while parsing with full naming %q: %v", tc.input, err) - } else { - if result != tc.full { - t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.full, result) - } + } else if result != tc.full { + t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.full, result) } if result, err := domainPolicy.ExtractMailbox(tc.input); tc.domain != "" && err != nil { t.Errorf("Error while parsing with domain naming %q: %v", tc.input, err) - } else { - if result != tc.domain { - t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.domain, result) - } + } else if result != tc.domain { + t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.domain, result) } } } @@ -327,10 +321,8 @@ func TestExtractDomainMailboxValid(t *testing.T) { t.Run(name, func(t *testing.T) { if result, err := domainPolicy.ExtractMailbox(tc.input); tc.domain != "" && err != nil { t.Errorf("Error while parsing with domain naming %q: %v", tc.input, err) - } else { - if result != tc.domain { - t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.domain, result) - } + } else if result != tc.domain { + t.Errorf("Parsing %q, expected %q, got %q", tc.input, tc.domain, result) } }) } diff --git a/pkg/rest/testutils_test.go b/pkg/rest/testutils_test.go index 68dfb43..acd8913 100644 --- a/pkg/rest/testutils_test.go +++ b/pkg/rest/testutils_test.go @@ -122,9 +122,10 @@ func getDecodedPath(o interface{}, path ...string) (interface{}, string) { if o == nil { return nil, " is nil" } - key := path[0] - present := false + + var present bool var val interface{} + key := path[0] if key[0] == '[' { // Expecting slice. index, err := strconv.Atoi(strings.Trim(key, "[]")) @@ -147,12 +148,15 @@ func getDecodedPath(o interface{}, path ...string) (interface{}, string) { } val, present = omap[key] } + if !present { return nil, "/" + key + " is missing" } + result, msg := getDecodedPath(val, path[1:]...) if msg != "" { return nil, "/" + key + msg } + return result, "" } diff --git a/pkg/server/pop3/handler.go b/pkg/server/pop3/handler.go index 93506bf..ce7df98 100644 --- a/pkg/server/pop3/handler.go +++ b/pkg/server/pop3/handler.go @@ -134,48 +134,46 @@ func (s *Server) startSession(id int, conn net.Conn) { line, err := ssn.readLine() ssn.logger.Debug().Msgf("read %s", line) if err == nil { - if cmd, arg, ok := ssn.parseCmd(line); ok { - // Check against valid SMTP commands - if cmd == "" { - ssn.send("-ERR Speak up") - continue + cmd, arg := ssn.parseCmd(line) + // Commands we handle in any state + if cmd == "CAPA" { + // List our capabilities per RFC2449 + ssn.send("+OK Capability list follows") + ssn.send("TOP") + ssn.send("USER") + ssn.send("UIDL") + ssn.send("IMPLEMENTATION Inbucket") + if s.tlsConfig != nil && s.tlsState == nil && !s.config.ForceTLS { + ssn.send("STLS") } - if !commands[cmd] { - ssn.send(fmt.Sprintf("-ERR Syntax error, %v command unrecognized", cmd)) - ssn.logger.Warn().Msgf("Unrecognized command: %v", cmd) - continue - } - - // Commands we handle in any state - switch cmd { - case "CAPA": - // List our capabilities per RFC2449 - ssn.send("+OK Capability list follows") - ssn.send("TOP") - ssn.send("USER") - ssn.send("UIDL") - ssn.send("IMPLEMENTATION Inbucket") - if s.tlsConfig != nil && s.tlsState == nil && !s.config.ForceTLS { - ssn.send("STLS") - } - ssn.send(".") - continue - } - - // Send command to handler for current state - switch ssn.state { - case AUTHORIZATION: - ssn.authorizationHandler(cmd, arg) - continue - case TRANSACTION: - ssn.transactionHandler(cmd, arg) - continue - } - ssn.logger.Error().Msgf("Session entered unexpected state %v", ssn.state) - break - } else { - ssn.send("-ERR Syntax error, command garbled") + ssn.send(".") + continue } + + // Check against valid SMTP commands + if cmd == "" { + ssn.send("-ERR Speak up") + continue + } + + if !commands[cmd] { + ssn.send(fmt.Sprintf("-ERR Syntax error, %v command unrecognized", cmd)) + ssn.logger.Warn().Msgf("Unrecognized command: %v", cmd) + continue + } + + // Send command to handler for current state + switch ssn.state { + case AUTHORIZATION: + ssn.authorizationHandler(cmd, arg) + continue + case TRANSACTION: + ssn.transactionHandler(cmd, arg) + continue + } + + ssn.logger.Error().Msgf("Session entered unexpected state %v", ssn.state) + break } else { // readLine() returned an error if err == io.EOF { @@ -631,14 +629,14 @@ func (s *Session) readLine() (line string, err error) { return line, nil } -func (s *Session) parseCmd(line string) (cmd string, args []string, ok bool) { +func (s *Session) parseCmd(line string) (cmd string, args []string) { line = strings.TrimRight(line, "\r\n") if line == "" { - return "", nil, true + return "", nil } words := strings.Split(line, " ") - return strings.ToUpper(words[0]), words[1:], true + return strings.ToUpper(words[0]), words[1:] } func (s *Session) reset() { diff --git a/pkg/server/smtp/handler.go b/pkg/server/smtp/handler.go index 9891c24..33c3866 100644 --- a/pkg/server/smtp/handler.go +++ b/pkg/server/smtp/handler.go @@ -173,13 +173,13 @@ func (s *Server) startSession(id int, conn net.Conn, logger zerolog.Logger) { } line, err := ssn.readLine() if err == nil { - //Handle LOGIN/PASSWORD states here, because they don't expect a command + // Handle LOGIN/PASSWORD states here, because they don't expect a command. switch ssn.state { case LOGIN: - ssn.loginHandler(line) + ssn.loginHandler() continue case PASSWORD: - ssn.passwordHandler(line) + ssn.passwordHandler() continue } @@ -312,13 +312,13 @@ func parseHelloArgument(arg string) (string, error) { return domain, nil } -func (s *Session) loginHandler(line string) { +func (s *Session) loginHandler() { // Content and length of username is ignored. s.send(fmt.Sprintf("334 %v", passwordChallenge)) s.enterState(PASSWORD) } -func (s *Session) passwordHandler(line string) { +func (s *Session) passwordHandler() { // Content and length of password is ignored. s.send("235 Authentication successful") s.enterState(READY) diff --git a/pkg/storage/file/mbox.go b/pkg/storage/file/mbox.go index a7399c2..afcb346 100644 --- a/pkg/storage/file/mbox.go +++ b/pkg/storage/file/mbox.go @@ -112,6 +112,8 @@ func (mb *mbox) readIndex() error { log.Debug().Str("module", "storage").Str("path", mb.indexPath). Msg("Index does not yet exist") mb.indexLoaded = true + + //lint:ignore nilerr missing mailboxes are considered empty. return nil } file, err := os.Open(mb.indexPath) diff --git a/pkg/webui/sanitize/html.go b/pkg/webui/sanitize/html.go index b72aff4..4878171 100644 --- a/pkg/webui/sanitize/html.go +++ b/pkg/webui/sanitize/html.go @@ -39,7 +39,7 @@ func sanitizeStyleTags(input string) (string, error) { func styleTagFilter(w io.Writer, r io.Reader) error { bw := bufio.NewWriter(w) - b := make([]byte, 256) + b := make([]byte, 0, 256) z := html.NewTokenizer(r) for { b = b[:0]