1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-10-28 15:02:28 +00:00

Apply more mechanical simplifications using recent Go features

This is a follow-up on the `go fix` run in commit
0210c5fd92.

That commit recorded the automated updates.

This one applies further simplifications that are mechanical and do not
involve logic changes, but are outside of what `go fix` did.

It also removes some now-obsolete comments and statements.
This commit is contained in:
Alberto Bertogli
2026-10-03 09:08:22 +01:00
parent 0210c5fd92
commit 8d763f450d
33 changed files with 99 additions and 161 deletions
+6 -15
View File
@@ -7,9 +7,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@@ -166,7 +167,7 @@ func checkUserDB() {
path := userDBForDomain("") path := userDBForDomain("")
// Check if the file exists. This is because userdb.Load does not consider // Check if the file exists. This is because userdb.Load does not consider
// it an error. // it an error.
if _, err := os.Stat(path); os.IsNotExist(err) { if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
Fatalf("Error: file %q does not exist", path) Fatalf("Error: file %q does not exist", path)
} }
@@ -281,13 +282,7 @@ func aliasesResolve() {
// Result is a map of type -> []addresses. // Result is a map of type -> []addresses.
// Sort the types for deterministic output. // Sort the types for deterministic output.
ts := []string{} for _, t := range slices.Sorted(maps.Keys(vs)) {
for t := range vs {
ts = append(ts, t)
}
sort.Strings(ts)
for _, t := range ts {
for _, a := range vs[t] { for _, a := range vs[t] {
fmt.Printf("%v %s\n", t, a) fmt.Printf("%v %s\n", t, a)
} }
@@ -333,12 +328,8 @@ func parseArgs(usage string) map[string]string {
// Note: Consider handling end of args marker "--" explicitly in // Note: Consider handling end of args marker "--" explicitly in
// the future if needed. // the future if needed.
if strings.HasPrefix(a, "-") { if strings.HasPrefix(a, "-") {
sp := strings.SplitN(a, "=", 2) k, v, _ := strings.Cut(a, "=")
if len(sp) < 2 { args[k] = v
args[a] = ""
} else {
args[sp[0]] = sp[1]
}
} else { } else {
args["$"+strconv.Itoa(pos)] = a args["$"+strconv.Itoa(pos)] = a
pos++ pos++
+3 -1
View File
@@ -10,8 +10,10 @@ import (
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/mail" "net/mail"
"os" "os"
"path" "path"
@@ -161,7 +163,7 @@ func dkimKeygen() {
keyPath = keyPathFor(domain, selector) keyPath = keyPathFor(domain, selector)
} }
if _, err := os.Stat(keyPath); !os.IsNotExist(err) { if _, err := os.Stat(keyPath); !errors.Is(err, fs.ErrNotExist) {
Fatalf("Error: key already exists at %q", keyPath) Fatalf("Error: key already exists at %q", keyPath)
} }
-3
View File
@@ -1,9 +1,6 @@
// mda-lmtp is a very basic MDA that uses LMTP to do the delivery. // mda-lmtp is a very basic MDA that uses LMTP to do the delivery.
// //
// See the usage below for details. // See the usage below for details.
//
//go:build !coverage
package main package main
import ( import (
-3
View File
@@ -1,7 +1,4 @@
// smtp-check is a command-line too for checking SMTP setups. // smtp-check is a command-line too for checking SMTP setups.
//
//go:build !coverage
package main package main
import ( import (
-2
View File
@@ -1,9 +1,7 @@
// Support for overriding DNS lookups, for testing purposes. // Support for overriding DNS lookups, for testing purposes.
// This is only used in tests, when the "dnsoverride" tag is active. // This is only used in tests, when the "dnsoverride" tag is active.
// It requires Go >= 1.8.
// //
//go:build dnsoverride //go:build dnsoverride
// +build dnsoverride
package main package main
+10 -13
View File
@@ -58,8 +58,10 @@ package aliases
import ( import (
"bufio" "bufio"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"maps" "maps"
"os" "os"
"os/exec" "os/exec"
@@ -376,7 +378,7 @@ func (v *Resolver) AddAliasesFile(domain, path string) (int, error) {
v.mu.Unlock() v.mu.Unlock()
aliases, err := v.parseFile(domain, path) aliases, err := v.parseFile(domain, path)
if os.IsNotExist(err) { if errors.Is(err, fs.ErrNotExist) {
return 0, nil return 0, nil
} }
if err != nil { if err != nil {
@@ -404,7 +406,7 @@ func (v *Resolver) Reload() error {
for domain, paths := range v.files { for domain, paths := range v.files {
for _, path := range paths { for _, path := range paths {
aliases, err := v.parseFile(domain, path) aliases, err := v.parseFile(domain, path)
if os.IsNotExist(err) { if errors.Is(err, fs.ErrNotExist) {
continue continue
} }
if err != nil { if err != nil {
@@ -447,12 +449,12 @@ func (v *Resolver) parseReader(domain string, r io.Reader) (map[string][]Recipie
continue continue
} }
sp := strings.SplitN(line, ":", 2) addr, rawalias, ok := strings.Cut(line, ":")
if len(sp) != 2 { if !ok {
return nil, newParseError(i, "missing ':' in line") return nil, newParseError(i, "missing ':' in line")
} }
addr, rawalias := strings.TrimSpace(sp[0]), strings.TrimSpace(sp[1]) addr, rawalias = strings.TrimSpace(addr), strings.TrimSpace(rawalias)
if len(addr) == 0 || len(rawalias) == 0 { if len(addr) == 0 || len(rawalias) == 0 {
return nil, newParseError(i, "missing address or alias") return nil, newParseError(i, "missing address or alias")
} }
@@ -574,12 +576,7 @@ func removeAllAfter(s, seps string) string {
continue continue
} }
i := strings.Index(s, c) s, _, _ = strings.Cut(s, c)
if i == -1 {
continue
}
s = s[:i]
} }
return s return s
@@ -588,7 +585,7 @@ func removeAllAfter(s, seps string) string {
// removeChars removes the runes in "chars" from s. // removeChars removes the runes in "chars" from s.
func removeChars(s, chars string) string { func removeChars(s, chars string) string {
for c := range strings.SplitSeq(chars, "") { for c := range strings.SplitSeq(chars, "") {
s = strings.Replace(s, c, "", -1) s = strings.ReplaceAll(s, c, "")
} }
return s return s
@@ -600,7 +597,7 @@ func (v *Resolver) runResolveHook(tr *trace.Trace, addr string) ([]Recipient, er
return nil, nil return nil, nil
} }
// TODO: check if the file is executable. // TODO: check if the file is executable.
if _, err := os.Stat(v.ResolveHook); os.IsNotExist(err) { if _, err := os.Stat(v.ResolveHook); errors.Is(err, fs.ErrNotExist) {
hookResults.Add("resolve:skip", 1) hookResults.Add("resolve:skip", 1)
return nil, nil return nil, nil
} }
+2 -4
View File
@@ -214,10 +214,8 @@ func DecodeResponse(response string) (user, domain, passwd string, err error) {
// Split identity into "user@domain", if possible. // Split identity into "user@domain", if possible.
user = identity user = identity
idsp := strings.SplitN(identity, "@", 2) if u, d, ok := strings.Cut(identity, "@"); ok {
if len(idsp) >= 2 { user, domain = u, d
user = idsp[0]
domain = idsp[1]
} }
// Normalize the user and domain. This is so users can write the username // Normalize the user and domain. This is so users can write the username
+3 -1
View File
@@ -2,6 +2,8 @@ package courier
import ( import (
"bytes" "bytes"
"errors"
"io/fs"
"os" "os"
"testing" "testing"
"time" "time"
@@ -67,7 +69,7 @@ func TestExitCode(t *testing.T) {
// TODO: This can happen when building under unusual circumstances, such // TODO: This can happen when building under unusual circumstances, such
// as Debian package building. Are they reasonable enough for us to keep // as Debian package building. Are they reasonable enough for us to keep
// this? // this?
if _, err := os.Stat("../../test/util/exitcode"); os.IsNotExist(err) { if _, err := os.Stat("../../test/util/exitcode"); errors.Is(err, fs.ErrNotExist) {
t.Skipf("util/exitcode not found, running from outside repo?") t.Skipf("util/exitcode not found, running from outside repo?")
} }
+3 -7
View File
@@ -11,12 +11,8 @@ import (
// Split an user@domain address into user and domain. // Split an user@domain address into user and domain.
func Split(addr string) (string, string) { func Split(addr string) (string, string) {
ps := strings.SplitN(addr, "@", 2) user, domain, _ := strings.Cut(addr, "@")
if len(ps) != 2 { return user, domain
return addr, ""
}
return ps[0], ps[1]
} }
// UserOf user@domain returns user. // UserOf user@domain returns user.
@@ -48,7 +44,7 @@ func AddHeader(data []byte, k, v string) []byte {
if v[len(v)-1] == '\n' { if v[len(v)-1] == '\n' {
v = v[:len(v)-1] v = v[:len(v)-1]
} }
v = strings.Replace(v, "\n", "\n\t", -1) v = strings.ReplaceAll(v, "\n", "\n\t")
} }
header := []byte(fmt.Sprintf("%s: %s\n", k, v)) header := []byte(fmt.Sprintf("%s: %s\n", k, v))
+3 -3
View File
@@ -27,7 +27,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -90,8 +90,8 @@ func MetricsHandler(w http.ResponseWriter, r *http.Request) {
}) })
// Sort the variables for reproducibility and readability. // Sort the variables for reproducibility and readability.
sort.Slice(vars, func(i, j int) bool { slices.SortFunc(vars, func(a, b exportedVar) int {
return vars[i].Name < vars[j].Name return strings.Compare(a.Name, b.Name)
}) })
for _, v := range vars { for _, v := range vars {
+2 -5
View File
@@ -128,11 +128,8 @@ func readRequest(r *textproto.Reader) (string, string, error) {
return "", "", err return "", "", err
} }
sp := strings.SplitN(line, " ", 2) name, params, _ := strings.Cut(line, " ")
if len(sp) == 1 { return name, params, nil
return sp[0], "", nil
}
return sp[0], sp[1], nil
} }
func writeError(tr *trace.Trace, tconn *textproto.Conn, err error) { func writeError(tr *trace.Trace, tconn *textproto.Conn, err error) {
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"html/template" "html/template"
"math" "math"
"net/http" "net/http"
"sort" "slices"
"strconv" "strconv"
"time" "time"
) )
@@ -156,8 +156,8 @@ func allEvents(tr *trace) []traceAndEvent {
evts := appendAllEvents(tr, []traceAndEvent{}, seen, 0) evts := appendAllEvents(tr, []traceAndEvent{}, seen, 0)
// Sort them by time. // Sort them by time.
sort.Slice(evts, func(i, j int) bool { slices.SortFunc(evts, func(a, b traceAndEvent) int {
return evts[i].Event.When.Before(evts[j].Event.When) return a.Event.When.Compare(b.Event.When)
}) })
return evts return evts
+7 -14
View File
@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"maps" "maps"
"math/rand/v2" "math/rand/v2"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -29,11 +29,8 @@ func newID(family string, ts int64) id {
} }
func (id id) Family() string { func (id id) Family() string {
sp := strings.SplitN(string(id), "!", 2) family, _, _ := strings.Cut(string(id), "!")
if len(sp) != 2 { return family
return string(id)
}
return sp[0]
} }
// Trace represents a single request trace. // Trace represents a single request trace.
@@ -433,8 +430,8 @@ func (ft *familyTraces) TracesFor(b int, allgt bool) []*trace {
// Sort them by start, newer first. This is the order that will be used // Sort them by start, newer first. This is the order that will be used
// when displaying them. // when displaying them.
sort.Slice(trs, func(i, j int) bool { slices.SortFunc(trs, func(a, b *trace) int {
return trs[i].Start.After(trs[j].Start) return b.Start.Compare(a.Start)
}) })
return trs return trs
} }
@@ -510,13 +507,9 @@ var (
) )
func copyFamilies() map[string]*familyTraces { func copyFamilies() map[string]*familyTraces {
n := map[string]*familyTraces{}
familiesMu.Lock() familiesMu.Lock()
maps.Copy(n, families) defer familiesMu.Unlock()
familiesMu.Unlock() return maps.Clone(families)
return n
} }
func findInFamilies(traceID id, refID id) *trace { func findInFamilies(traceID id, refID id) *trace {
+3 -1
View File
@@ -2,6 +2,8 @@
package protoio package protoio
import ( import (
"errors"
"io/fs"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@@ -87,7 +89,7 @@ func (s *Store) Put(id string, m proto.Message) error {
// Get a message from the store. // Get a message from the store.
func (s *Store) Get(id string, m proto.Message) (bool, error) { func (s *Store) Get(id string, m proto.Message) (bool, error) {
err := ReadTextMessage(s.idToFname(id), m) err := ReadTextMessage(s.idToFname(id), m)
if os.IsNotExist(err) { if errors.Is(err, fs.ErrNotExist) {
return false, nil return false, nil
} }
return err == nil, err return err == nil, err
+1 -1
View File
@@ -88,7 +88,7 @@ type dsnInfo struct {
// indent s with the given number of spaces. // indent s with the given number of spaces.
func indent(sp int, s string) string { func indent(sp int, s string) string {
pad := strings.Repeat(" ", sp) pad := strings.Repeat(" ", sp)
return strings.Replace(s, "\n", "\n"+pad, -1) return strings.ReplaceAll(s, "\n", "\n"+pad)
} }
var dsnTemplate = template.Must( var dsnTemplate = template.Must(
+2 -2
View File
@@ -2,7 +2,7 @@ package queue
import ( import (
"fmt" "fmt"
"sort" "slices"
"strings" "strings"
"testing" "testing"
) )
@@ -182,7 +182,7 @@ func printDiff(print func(s string), expected, got string) {
// sort the lines, to make it easier to see the differences (this works // sort the lines, to make it easier to see the differences (this works
// ok when there's few, horrible when there's lots). // ok when there's few, horrible when there's lots).
sort.Strings(lines) slices.Sort(lines)
// print diff of expected vs. got // print diff of expected vs. got
seen := map[string]bool{} seen := map[string]bool{}
+12 -14
View File
@@ -252,19 +252,19 @@ func (q *Queue) DumpString() string {
defer q.mu.RUnlock() defer q.mu.RUnlock()
var s strings.Builder var s strings.Builder
s.WriteString("# Queue status\n\n") s.WriteString("# Queue status\n\n")
s.WriteString(fmt.Sprintf("date: %v\n", time.Now())) fmt.Fprintf(&s, "date: %v\n", time.Now())
s.WriteString(fmt.Sprintf("length: %d\n\n", len(q.q))) fmt.Fprintf(&s, "length: %d\n\n", len(q.q))
for id, item := range q.q { for id, item := range q.q {
s.WriteString(fmt.Sprintf("## Item %s\n", id)) fmt.Fprintf(&s, "## Item %s\n", id)
item.Lock() item.Lock()
s.WriteString(fmt.Sprintf("created at: %s\n", item.CreatedAt)) fmt.Fprintf(&s, "created at: %s\n", item.CreatedAt)
s.WriteString(fmt.Sprintf("from: %s\n", item.From)) fmt.Fprintf(&s, "from: %s\n", item.From)
s.WriteString(fmt.Sprintf("to: %s\n", item.To)) fmt.Fprintf(&s, "to: %s\n", item.To)
for _, rcpt := range item.Rcpt { for _, rcpt := range item.Rcpt {
s.WriteString(fmt.Sprintf("%s %s (%s)\n", rcpt.Status, rcpt.Address, rcpt.Type)) fmt.Fprintf(&s, "%s %s (%s)\n", rcpt.Status, rcpt.Address, rcpt.Type)
s.WriteString(fmt.Sprintf(" original address: %s\n", rcpt.OriginalAddress)) fmt.Fprintf(&s, " original address: %s\n", rcpt.OriginalAddress)
s.WriteString(fmt.Sprintf(" last failure: %q\n", rcpt.LastFailureMessage)) fmt.Fprintf(&s, " last failure: %q\n", rcpt.LastFailureMessage)
} }
item.Unlock() item.Unlock()
s.WriteString("\n") s.WriteString("\n")
@@ -329,8 +329,7 @@ func (item *Item) SendLoop(q *Queue) {
continue continue
} }
wg.Add(1) wg.Go(func() { item.sendOneRcpt(tr, q, rcpt) })
go item.sendOneRcpt(&wg, tr, q, rcpt)
} }
wg.Wait() wg.Wait()
@@ -359,8 +358,7 @@ func (item *Item) SendLoop(q *Queue) {
} }
// sendOneRcpt, and update it with the results. // sendOneRcpt, and update it with the results.
func (item *Item) sendOneRcpt(wg *sync.WaitGroup, tr *trace.Trace, q *Queue, rcpt *Recipient) { func (item *Item) sendOneRcpt(tr *trace.Trace, q *Queue, rcpt *Recipient) {
defer wg.Done()
to := rcpt.Address to := rcpt.Address
tr.Debugf("%s sending", to) tr.Debugf("%s sending", to)
@@ -452,7 +450,7 @@ func rewriteSender(from, originalAddr string) string {
// we can't know if the other side will support SMTPUTF8. // we can't know if the other side will support SMTPUTF8.
return fmt.Sprintf("%s+fwd_from=%s@%s", return fmt.Sprintf("%s+fwd_from=%s@%s",
envelope.UserOf(originalAddr), envelope.UserOf(originalAddr),
strings.Replace(from, "@", "=", -1), strings.ReplaceAll(from, "@", "="),
mustIDNAToASCII(envelope.DomainOf(originalAddr))) mustIDNAToASCII(envelope.DomainOf(originalAddr)))
} }
+15 -22
View File
@@ -9,6 +9,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/fs"
"math/rand/v2" "math/rand/v2"
"net" "net"
"net/mail" "net/mail"
@@ -343,7 +344,7 @@ loop:
} }
if err != nil { if err != nil {
if err == io.EOF { if errors.Is(err, io.EOF) {
c.tr.Debugf("client closed the connection") c.tr.Debugf("client closed the connection")
} else { } else {
c.tr.Errorf("exiting with error: %v", err) c.tr.Errorf("exiting with error: %v", err)
@@ -363,7 +364,7 @@ func (c *Conn) HELO(params string) (code int, msg string) {
"liquor emporium", "antique weapons outlet", "delicatessen", "liquor emporium", "antique weapons outlet", "delicatessen",
"jewelers", "quality apparel and accessories", "hardware", "jewelers", "quality apparel and accessories", "hardware",
"rare books", "lighting store"} "rare books", "lighting store"}
t := types[rand.Int()%len(types)] t := types[rand.IntN(len(types))]
msg = fmt.Sprintf("Hello my friend, welcome to chasqui's %s!", t) msg = fmt.Sprintf("Hello my friend, welcome to chasqui's %s!", t)
return 250, msg return 250, msg
@@ -408,7 +409,7 @@ func (c *Conn) RSET(params string) (code int, msg string) {
"Your mind releases itself from mundane concerns.", "Your mind releases itself from mundane concerns.",
"As your mind turns inward on itself, you forget everything else.", "As your mind turns inward on itself, you forget everything else.",
} }
return 250, "2.0.0 " + msgs[rand.Int()%len(msgs)] return 250, "2.0.0 " + msgs[rand.IntN(len(msgs))]
} }
// VRFY SMTP command handler. // VRFY SMTP command handler.
@@ -455,7 +456,7 @@ func (c *Conn) MAIL(params string) (code int, msg string) {
// It should be written "<>", we check for that and remove spaces just to // It should be written "<>", we check for that and remove spaces just to
// be more flexible. // be more flexible.
addr := "" addr := ""
if strings.Replace(rawAddr, " ", "", -1) == "<>" { if strings.ReplaceAll(rawAddr, " ", "") == "<>" {
addr = "<>" addr = "<>"
} else { } else {
e, err := mail.ParseAddress(rawAddr) e, err := mail.ParseAddress(rawAddr)
@@ -668,11 +669,11 @@ func (c *Conn) DATA(params string) (code int, msg string) {
// Read the data. Enforce CRLF correctness, and maximum size. // Read the data. Enforce CRLF correctness, and maximum size.
c.data, err = readUntilDot(c.reader, c.maxDataSize) c.data, err = readUntilDot(c.reader, c.maxDataSize)
if err != nil { if err != nil {
if err == errMessageTooLarge { if errors.Is(err, errMessageTooLarge) {
// Message is too big; excess data has already been discarded. // Message is too big; excess data has already been discarded.
return 552, "5.3.4 Message too big" return 552, "5.3.4 Message too big"
} }
if err == errInvalidLineEnding { if errors.Is(err, errInvalidLineEnding) {
// We can't properly recover from this, so we have to drop the // We can't properly recover from this, so we have to drop the
// connection. // connection.
c.writeResponse(521, "5.5.2 Error reading DATA: invalid line ending") c.writeResponse(521, "5.5.2 Error reading DATA: invalid line ending")
@@ -734,7 +735,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
"In return to thy service, I grant thee the gift of Immortality!", "In return to thy service, I grant thee the gift of Immortality!",
"You ascend to the status of Demigod(dess)...", "You ascend to the status of Demigod(dess)...",
} }
return 250, "2.0.0 " + msgs[rand.Int()%len(msgs)] return 250, "2.0.0 " + msgs[rand.IntN(len(msgs))]
} }
func (c *Conn) addReceivedHeader() { func (c *Conn) addReceivedHeader() {
@@ -884,7 +885,7 @@ func sanitizeEHLODomain(s string) string {
c >= '0' && c <= '9', c >= '0' && c <= '9',
c == '-', c == '.', c == '-', c == '.',
c == '[', c == ']', c == ':': c == '[', c == ']', c == ':':
n.WriteString(string(c)) n.WriteRune(c)
} }
} }
@@ -895,7 +896,7 @@ func sanitizeEHLODomain(s string) string {
// indicating if it's permanent, and the error itself. // indicating if it's permanent, and the error itself.
func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) { func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) {
// TODO: check if the file is executable. // TODO: check if the file is executable.
if _, err := os.Stat(c.postDataHook); os.IsNotExist(err) { if _, err := os.Stat(c.postDataHook); errors.Is(err, fs.ErrNotExist) {
hookResults.Add("post-data:skip", 1) hookResults.Add("post-data:skip", 1)
return nil, false, nil return nil, false, nil
} }
@@ -1152,8 +1153,8 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
// If the response is not there, we reply with 334, and expect the // If the response is not there, we reply with 334, and expect the
// response back from the client in the next message. // response back from the client in the next message.
sp := strings.SplitN(params, " ", 2) mech, response, hasResponse := strings.Cut(params, " ")
if len(sp) < 1 || sp[0] != "PLAIN" { if mech != "PLAIN" {
// As we only offer plain, this should not really happen. // As we only offer plain, this should not really happen.
return 534, "5.7.9 Asmodeus demands 534 zorkmids for safe passage" return 534, "5.7.9 Asmodeus demands 534 zorkmids for safe passage"
} }
@@ -1162,10 +1163,7 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
// find their way to the users in some circumstances. // find their way to the users in some circumstances.
// Get the response, either from the message or interactively. // Get the response, either from the message or interactively.
response := "" if !hasResponse {
if len(sp) == 2 {
response = sp[1]
} else {
// Reply 334 and expect the user to provide it. // Reply 334 and expect the user to provide it.
// In this case, the text IS relevant, as it is taken as the // In this case, the text IS relevant, as it is taken as the
// server-side SASL challenge (empty for PLAIN). // server-side SASL challenge (empty for PLAIN).
@@ -1232,13 +1230,8 @@ func (c *Conn) readCommand() (cmd, params string, err error) {
return "", "", err return "", "", err
} }
sp := strings.SplitN(msg, " ", 2) cmd, params, _ = strings.Cut(msg, " ")
cmd = strings.ToUpper(sp[0]) return strings.ToUpper(cmd), params, err
if len(sp) > 1 {
params = sp[1]
}
return cmd, params, err
} }
func (c *Conn) readLine() (line string, err error) { func (c *Conn) readLine() (line string, err error) {
+1 -1
View File
@@ -38,7 +38,7 @@ func readUntilDot(r *bufio.Reader, max int64) ([]byte, error) {
loop: loop:
for { for {
b, err := r.ReadByte() b, err := r.ReadByte()
if err == io.EOF { if errors.Is(err, io.EOF) {
return buf, io.ErrUnexpectedEOF return buf, io.ErrUnexpectedEOF
} else if err != nil { } else if err != nil {
return buf, err return buf, err
+1 -3
View File
@@ -340,9 +340,7 @@ func (s *Server) ListenAndServe() {
// Never return. If the serve goroutines have problems, they will abort // Never return. If the serve goroutines have problems, they will abort
// execution. // execution.
for { select {}
time.Sleep(24 * time.Hour)
}
} }
func (s *Server) serve(l net.Listener, mode SocketMode) { func (s *Server) serve(l net.Listener, mode SocketMode) {
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/fs"
"net" "net"
"net/smtp" "net/smtp"
"net/textproto" "net/textproto"
@@ -486,7 +487,7 @@ func TestStartTLSOnTLS(t *testing.T) {
func TestAddDKIMSigner(t *testing.T) { func TestAddDKIMSigner(t *testing.T) {
s := NewServer() s := NewServer()
err := s.AddDKIMSigner("example.com", "selector", "keyfile-does-not-exist") err := s.AddDKIMSigner("example.com", "selector", "keyfile-does-not-exist")
if !os.IsNotExist(err) { if !errors.Is(err, fs.ErrNotExist) {
t.Errorf("AddDKIMSigner: expected not exist, got %v", err) t.Errorf("AddDKIMSigner: expected not exist, got %v", err)
} }
+7 -7
View File
@@ -86,13 +86,13 @@ func parsePolicy(raw []byte) (*Policy, error) {
scanner := bufio.NewScanner(bytes.NewReader(raw)) scanner := bufio.NewScanner(bytes.NewReader(raw))
for scanner.Scan() { for scanner.Scan() {
sp := strings.SplitN(scanner.Text(), ":", 2) key, value, ok := strings.Cut(scanner.Text(), ":")
if len(sp) != 2 { if !ok {
continue continue
} }
key := strings.TrimSpace(sp[0]) key = strings.TrimSpace(key)
value := strings.TrimSpace(sp[1]) value = strings.TrimSpace(value)
// Only care for the keys we recognize. // Only care for the keys we recognize.
switch key { switch key {
@@ -293,9 +293,9 @@ func matchDomain(domain, pattern string) bool {
// For wildcards, skip the first part of the domain and match the rest. // For wildcards, skip the first part of the domain and match the rest.
// Note that if the pattern is malformed this might fail, but we are ok // Note that if the pattern is malformed this might fail, but we are ok
// with that. // with that.
if strings.HasPrefix(pattern, "*.") { if suffix, ok := strings.CutPrefix(pattern, "*."); ok {
parts := strings.SplitN(domain, ".", 2) _, rest, found := strings.Cut(domain, ".")
if len(parts) > 1 && parts[1] == pattern[2:] { if found && rest == suffix {
return true return true
} }
} }
+4 -2
View File
@@ -1,6 +1,8 @@
package testlib package testlib
import ( import (
"errors"
"io/fs"
"os" "os"
"testing" "testing"
"time" "time"
@@ -22,7 +24,7 @@ func TestBasic(t *testing.T) {
} }
RemoveIfOk(t, dir) RemoveIfOk(t, dir)
if _, err := os.Stat(dir); !os.IsNotExist(err) { if _, err := os.Stat(dir); !errors.Is(err, fs.ErrNotExist) {
t.Fatalf("%s existed, should have been deleted: %v", dir, err) t.Fatalf("%s existed, should have been deleted: %v", dir, err)
} }
} }
@@ -45,7 +47,7 @@ func TestLeaveDirOnError(t *testing.T) {
myt.Errorf("something bad happened") myt.Errorf("something bad happened")
RemoveIfOk(myt, dir) RemoveIfOk(myt, dir)
if _, err := os.Stat(dir); os.IsNotExist(err) { if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
t.Fatalf("%s was removed, should have been kept", dir) t.Fatalf("%s was removed, should have been kept", dir)
} }
+2 -2
View File
@@ -33,7 +33,7 @@ import (
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"fmt" "fmt"
"os" "io/fs"
"sync" "sync"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
@@ -73,7 +73,7 @@ func Load(fname string) (*DB, error) {
db.db = &ProtoDB{Users: map[string]*Password{}} db.db = &ProtoDB{Users: map[string]*Password{}}
} }
if os.IsNotExist(err) { if errors.Is(err, fs.ErrNotExist) {
// If the file does not exist now, it is not an error, as it might // If the file does not exist now, it is not an error, as it might
// exist later and we want to be able to read it. // exist later and we want to be able to read it.
err = nil err = nil
+2 -1
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"errors"
"expvar" "expvar"
"flag" "flag"
"fmt" "fmt"
@@ -126,7 +127,7 @@ func launchMonitoringServer(conf *config.Config) {
http.HandleFunc("/debug/config", debugConfigHandler(conf)) http.HandleFunc("/debug/config", debugConfigHandler(conf))
http.HandleFunc("/debug/traces", nettrace.RenderTraces) http.HandleFunc("/debug/traces", nettrace.RenderTraces)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Monitoring server failed: %v", err) log.Fatalf("Monitoring server failed: %v", err)
} }
} }
-3
View File
@@ -5,9 +5,6 @@
# #
# The .coverage directory is used to store the data, it will be erased and # The .coverage directory is used to store the data, it will be erased and
# recreated on each run. # recreated on each run.
#
# This is not very tidy, and relies on some hacky tricks (see
# coverage_test.go), but works for now.
set -e set -e
. "$(dirname "$0")/util/lib.sh" . "$(dirname "$0")/util/lib.sh"
@@ -5,7 +5,6 @@
// daemon for testing purposes only. // daemon for testing purposes only.
// //
//go:build ignore //go:build ignore
// +build ignore
package main package main
+1 -6
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// SMTP connection generator, for testing purposes. // SMTP connection generator, for testing purposes.
package main package main
@@ -8,7 +6,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/smtp" "net/smtp"
"time"
_ "net/http/pprof" _ "net/http/pprof"
@@ -66,9 +63,7 @@ func main() {
log.Infof("done, created %d simultaneous connections", *count) log.Infof("done, created %d simultaneous connections", *count)
if *wait { if *wait {
for { select {}
time.Sleep(24 * time.Hour)
}
} }
for _, c := range conns { for _, c := range conns {
-2
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// Generate an HTML visualization of a Go coverage profile. // Generate an HTML visualization of a Go coverage profile.
// Serves a similar purpose to "go tool cover -html", but has a different // Serves a similar purpose to "go tool cover -html", but has a different
// visual style. // visual style.
+3 -9
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// Fetch an URL, and check if the response matches what we expect. // Fetch an URL, and check if the response matches what we expect.
// //
// Useful for testing HTTP(s) servers. // Useful for testing HTTP(s) servers.
@@ -11,10 +9,11 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"maps"
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"sort" "slices"
"strconv" "strconv"
"strings" "strings"
) )
@@ -85,12 +84,7 @@ func main() {
fmt.Printf("Request: %s\n", url) fmt.Printf("Request: %s\n", url)
fmt.Printf("Response:\n") fmt.Printf("Response:\n")
fmt.Printf(" %v %v\n", resp.Proto, resp.Status) fmt.Printf(" %v %v\n", resp.Proto, resp.Status)
ks := []string{} for _, k := range slices.Sorted(maps.Keys(resp.Header)) {
for k := range resp.Header {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
fmt.Printf(" %v: %s\n", k, fmt.Printf(" %v: %s\n", k,
strings.Join(resp.Header[k], ", ")) strings.Join(resp.Header[k], ", "))
} }
-2
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// Utility to generate self-signed certificates. // Utility to generate self-signed certificates.
// It generates a self-signed x509 certificate and key pair, and writes them // It generates a self-signed x509 certificate and key pair, and writes them
// to "fullchain.pem" and "privkey.pem". // to "fullchain.pem" and "privkey.pem".
+1 -5
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// SMTP load generator, for testing purposes. // SMTP load generator, for testing purposes.
package main package main
@@ -114,9 +112,7 @@ func main() {
) )
if *wait { if *wait {
for { select {}
time.Sleep(24 * time.Hour)
}
} }
} }
-2
View File
@@ -1,5 +1,3 @@
//go:build !coverage
// minidns is a trivial DNS server used for testing. // minidns is a trivial DNS server used for testing.
// //
// It takes an "answers" file which contains lines with the following format: // It takes an "answers" file which contains lines with the following format: