From 64f2f788b18aa52c57291d7ce38897f90aab5f8d Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Thu, 6 Mar 2025 17:33:06 +0100 Subject: [PATCH] Run modernize to rewrite some older go constructs to newer ones Mostly using slice.Sort, using min/max, slices.Concat, range of int and fmt.Appendf for byte slices instead of strings. --- admin/clientconfig.go | 5 ++--- admin/dnsrecords.go | 6 ++---- ctl.go | 11 +++-------- dane/dane.go | 10 ++++------ dkim/dkim.go | 7 ++++--- dkim/txt_test.go | 2 +- dmarcdb/eval_test.go | 3 ++- dsn/dsn.go | 5 +---- dsn/parse.go | 13 ++++--------- http/mobileconfig.go | 2 +- imapclient/prefixconn.go | 5 +---- imapserver/condstore_test.go | 3 ++- imapserver/fetch.go | 14 ++++---------- imapserver/prefixconn.go | 5 +---- imapserver/search.go | 15 +++------------ imapserver/search_test.go | 6 +++--- imapserver/server.go | 18 ++++++------------ imapserver/server_test.go | 7 ++++--- junk/bloom.go | 6 +++--- junk/bloom_test.go | 10 +++++----- junk/filter.go | 19 +++++-------------- junk/parse.go | 5 +---- main.go | 8 ++++---- message/part.go | 17 +++++------------ message/writer.go | 5 +---- mlog/log.go | 11 ++++++----- mox-/config.go | 5 +---- mox-/fill.go | 8 ++++---- mox-/password.go | 2 +- mox-/txt.go | 5 +---- moxio/base64writer.go | 5 +---- moxio/bufpool.go | 2 +- moxio/bufpool_test.go | 2 +- moxio/workq.go | 2 +- queue/hook_test.go | 4 ++-- queue/queue.go | 2 +- queue/queue_test.go | 10 +++++----- quickstart.go | 7 +++---- ratelimit/ratelimit.go | 8 ++++---- sasl/sasl.go | 4 ++-- serve_unix.go | 2 +- smtp/data.go | 5 +---- smtpclient/client.go | 2 +- smtpclient/client_test.go | 8 ++++---- smtpclient/gather_test.go | 4 ++-- smtpserver/server.go | 2 +- smtpserver/server_test.go | 6 +++--- spf/spf.go | 2 +- store/account.go | 4 ++-- store/account_test.go | 4 ++-- store/loginattempt_test.go | 2 +- tlsrptsend/send.go | 2 +- tlsrptsend/send_test.go | 3 ++- webadmin/admin.go | 8 +++----- webapisrv/server.go | 7 ++----- webapisrv/server_test.go | 4 ++-- webmail/api.go | 5 +---- webmail/api_test.go | 2 +- webmail/message.go | 18 +++++++----------- webmail/view.go | 2 +- webmail/view_test.go | 2 +- 61 files changed, 146 insertions(+), 232 deletions(-) diff --git a/admin/clientconfig.go b/admin/clientconfig.go index 24fae5c..8d92580 100644 --- a/admin/clientconfig.go +++ b/admin/clientconfig.go @@ -9,6 +9,7 @@ import ( "github.com/mjl-/mox/config" "github.com/mjl-/mox/dns" "github.com/mjl-/mox/mox-" + "slices" ) type TLSMode uint8 @@ -130,9 +131,7 @@ func ClientConfigsDomain(d dns.Domain) (ClientConfigs, error) { for name := range mox.Conf.Static.Listeners { listeners = append(listeners, name) } - sort.Slice(listeners, func(i, j int) bool { - return listeners[i] < listeners[j] - }) + slices.Sort(listeners) note := func(tls bool, requiretls bool) string { if !tls { diff --git a/admin/dnsrecords.go b/admin/dnsrecords.go index 5c92b59..6287d17 100644 --- a/admin/dnsrecords.go +++ b/admin/dnsrecords.go @@ -8,7 +8,6 @@ import ( "crypto/x509" "fmt" "net/url" - "sort" "strings" "github.com/mjl-/adns" @@ -21,6 +20,7 @@ import ( "github.com/mjl-/mox/smtp" "github.com/mjl-/mox/spf" "github.com/mjl-/mox/tlsrpt" + "slices" ) // todo: find a way to automatically create the dns records as it would greatly simplify setting up email for a domain. we could also dynamically make changes, e.g. providing grace periods after disabling a dkim key, only automatically removing the dkim dns key after a few days. but this requires some kind of api and authentication to the dns server. there doesn't appear to be a single commonly used api for dns management. each of the numerous cloud providers have their own APIs and rather large SKDs to use them. we don't want to link all of them in. @@ -135,9 +135,7 @@ func DomainRecords(domConf config.Domain, domain dns.Domain, hasDNSSEC bool, cer for name := range domConf.DKIM.Selectors { selectors = append(selectors, name) } - sort.Slice(selectors, func(i, j int) bool { - return selectors[i] < selectors[j] - }) + slices.Sort(selectors) for _, name := range selectors { sel := domConf.DKIM.Selectors[name] dkimr := dkim.Record{ diff --git a/ctl.go b/ctl.go index b16cb72..e0e80bd 100644 --- a/ctl.go +++ b/ctl.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "runtime/debug" - "sort" "strconv" "strings" "time" @@ -34,6 +33,7 @@ import ( "github.com/mjl-/mox/smtp" "github.com/mjl-/mox/store" "github.com/mjl-/mox/webapi" + "slices" ) // ctl represents a connection to the ctl unix domain socket of a running mox instance. @@ -242,10 +242,7 @@ func (s *ctlreader) Read(buf []byte) (N int, Err error) { } s.npending = int(n) } - rn := len(buf) - if rn > s.npending { - rn = s.npending - } + rn := min(len(buf), s.npending) n, err := s.r.Read(buf[:rn]) s.xcheck(err, "read from ctl") s.npending -= n @@ -1375,9 +1372,7 @@ func servectlcmd(ctx context.Context, ctl *ctl, cid int64, shutdown func()) { for k := range l { keys = append(keys, k) } - sort.Slice(keys, func(i, j int) bool { - return keys[i] < keys[j] - }) + slices.Sort(keys) s := "" for _, k := range keys { ks := k diff --git a/dane/dane.go b/dane/dane.go index 1f0a0de..d72be27 100644 --- a/dane/dane.go +++ b/dane/dane.go @@ -65,6 +65,7 @@ import ( "github.com/mjl-/mox/dns" "github.com/mjl-/mox/mlog" "github.com/mjl-/mox/stub" + "slices" ) var ( @@ -214,12 +215,9 @@ func Dial(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, network if allowedUsages != nil { o := 0 for _, r := range records { - for _, usage := range allowedUsages { - if r.Usage == usage { - records[o] = r - o++ - break - } + if slices.Contains(allowedUsages, r.Usage) { + records[o] = r + o++ } } records = records[:o] diff --git a/dkim/dkim.go b/dkim/dkim.go index d4ccf0e..f05d21e 100644 --- a/dkim/dkim.go +++ b/dkim/dkim.go @@ -31,6 +31,7 @@ import ( "github.com/mjl-/mox/publicsuffix" "github.com/mjl-/mox/smtp" "github.com/mjl-/mox/stub" + "slices" ) // If set, signatures for top-level domain "localhost" are accepted. @@ -173,7 +174,7 @@ func Sign(ctx context.Context, elog *slog.Logger, localpart smtp.Localpart, doma sig.Domain = domain sig.Selector = sel.Domain sig.Identity = &Identity{&localpart, domain} - sig.SignedHeaders = append([]string{}, sel.Headers...) + sig.SignedHeaders = slices.Clone(sel.Headers) if sel.SealHeaders { // ../rfc/6376:2156 // Each time a header name is added to the signature, the next unused value is @@ -839,8 +840,8 @@ func parseHeaders(br *bufio.Reader) ([]header, int, error) { return nil, 0, fmt.Errorf("empty header key") } lkey = strings.ToLower(key) - value = append([]byte{}, t[1]...) - raw = append([]byte{}, line...) + value = slices.Clone(t[1]) + raw = slices.Clone(line) } if key != "" { l = append(l, header{key, lkey, value, raw}) diff --git a/dkim/txt_test.go b/dkim/txt_test.go index 97d358b..ec98bc6 100644 --- a/dkim/txt_test.go +++ b/dkim/txt_test.go @@ -32,7 +32,7 @@ func TestParseRecord(t *testing.T) { } if r != nil { pk := r.Pubkey - for i := 0; i < 2; i++ { + for range 2 { ntxt, err := r.Record() if err != nil { t.Fatalf("making record: %v", err) diff --git a/dmarcdb/eval_test.go b/dmarcdb/eval_test.go index e2c5eb8..d11d5be 100644 --- a/dmarcdb/eval_test.go +++ b/dmarcdb/eval_test.go @@ -19,6 +19,7 @@ import ( "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/moxio" "github.com/mjl-/mox/queue" + "slices" ) func tcheckf(t *testing.T, err error, format string, args ...any) { @@ -301,7 +302,7 @@ func TestSendReports(t *testing.T) { // Read message file. Also write copy to disk for inspection. buf, err := io.ReadAll(&moxio.AtReader{R: msgFile}) tcheckf(t, err, "read report message") - err = os.WriteFile("../testdata/dmarcdb/data/report.eml", append(append([]byte{}, qm.MsgPrefix...), buf...), 0600) + err = os.WriteFile("../testdata/dmarcdb/data/report.eml", slices.Concat(qm.MsgPrefix, buf), 0600) tcheckf(t, err, "write report message") var feedback *dmarcrpt.Feedback diff --git a/dsn/dsn.go b/dsn/dsn.go index 13cde4a..d769073 100644 --- a/dsn/dsn.go +++ b/dsn/dsn.go @@ -340,10 +340,7 @@ func (m *Message) Compose(log mlog.Log, smtputf8 bool) ([]byte, error) { data := base64.StdEncoding.EncodeToString(headers) for len(data) > 0 { line := data - n := len(line) - if n > 78 { - n = 78 - } + n := min(len(line), 78) line, data = data[:n], data[n:] if _, err := origp.Write([]byte(line + "\r\n")); err != nil { return nil, err diff --git a/dsn/parse.go b/dsn/parse.go index 0cd29be..982c701 100644 --- a/dsn/parse.go +++ b/dsn/parse.go @@ -14,6 +14,7 @@ import ( "github.com/mjl-/mox/message" "github.com/mjl-/mox/mlog" "github.com/mjl-/mox/smtp" + "slices" ) // Parse reads a DSN message. @@ -217,15 +218,9 @@ func parseRecipientHeader(mr *textproto.Reader, utf8 bool) (Recipient, error) { case "Action": a := Action(strings.ToLower(v)) actions := []Action{Failed, Delayed, Delivered, Relayed, Expanded} - var ok bool - for _, x := range actions { - if a == x { - ok = true - r.Action = a - break - } - } - if !ok { + if slices.Contains(actions, a) { + r.Action = a + } else { err = fmt.Errorf("unrecognized action %q", v) } case "Status": diff --git a/http/mobileconfig.go b/http/mobileconfig.go index af29fa6..6d65aeb 100644 --- a/http/mobileconfig.go +++ b/http/mobileconfig.go @@ -64,7 +64,7 @@ func (m dict) MarshalXML(e *xml.Encoder, start xml.StartElement) error { case int: tokens = []xml.Token{ xml.StartElement{Name: xml.Name{Local: "integer"}}, - xml.CharData([]byte(fmt.Sprintf("%d", v))), + xml.CharData(fmt.Appendf(nil, "%d", v)), xml.EndElement{Name: xml.Name{Local: "integer"}}, } case bool: diff --git a/imapclient/prefixconn.go b/imapclient/prefixconn.go index 2718d7e..d1848cf 100644 --- a/imapclient/prefixconn.go +++ b/imapclient/prefixconn.go @@ -14,10 +14,7 @@ type prefixConn struct { func (c *prefixConn) Read(buf []byte) (int, error) { if len(c.prefix) > 0 { - n := len(buf) - if n > len(c.prefix) { - n = len(c.prefix) - } + n := min(len(buf), len(c.prefix)) copy(buf[:n], c.prefix[:n]) c.prefix = c.prefix[n:] if len(c.prefix) == 0 { diff --git a/imapserver/condstore_test.go b/imapserver/condstore_test.go index 7195fa5..378725e 100644 --- a/imapserver/condstore_test.go +++ b/imapserver/condstore_test.go @@ -10,6 +10,7 @@ import ( "github.com/mjl-/mox/imapclient" "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/store" + "slices" ) func TestCondstore(t *testing.T) { @@ -577,7 +578,7 @@ func testQresync(t *testing.T, tc *testconn, clientModseq int64) { } makeUntagged := func(l ...imapclient.Untagged) []imapclient.Untagged { - return append(append([]imapclient.Untagged{}, baseUntagged...), l...) + return slices.Concat(baseUntagged, l) } // uidvalidity 1, highest known modseq 1, sends full current state. diff --git a/imapserver/fetch.go b/imapserver/fetch.go index b83d554..9019754 100644 --- a/imapserver/fetch.go +++ b/imapserver/fetch.go @@ -20,6 +20,7 @@ import ( "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/moxio" "github.com/mjl-/mox/store" + "slices" ) // functions to handle fetch attribute requests are defined on fetchCmd. @@ -203,9 +204,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) { } // First sort the uids we already found, for fast lookup. - sort.Slice(vanishedUIDs, func(i, j int) bool { - return vanishedUIDs[i] < vanishedUIDs[j] - }) + slices.Sort(vanishedUIDs) // We'll be gathering any more vanished uids in more. more := map[store.UID]struct{}{} @@ -239,9 +238,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) { if len(vanishedUIDs) > 0 { // Mention all vanished UIDs in compact numset form. // ../rfc/7162:1985 - sort.Slice(vanishedUIDs, func(i, j int) bool { - return vanishedUIDs[i] < vanishedUIDs[j] - }) + slices.Sort(vanishedUIDs) // No hard limit on response sizes, but clients are recommended to not send more // than 8k. We send a more conservative max 4k. for _, s := range compactUIDSet(vanishedUIDs).Strings(4*1024 - 32) { @@ -734,10 +731,7 @@ func (cmd *fetchCmd) xbody(a fetchAtt) (string, token) { var offset int64 count := m.Size if a.partial != nil { - offset = int64(a.partial.offset) - if offset > m.Size { - offset = m.Size - } + offset = min(int64(a.partial.offset), m.Size) count = int64(a.partial.count) if offset+count > m.Size { count = m.Size - offset diff --git a/imapserver/prefixconn.go b/imapserver/prefixconn.go index 9731098..7dfc834 100644 --- a/imapserver/prefixconn.go +++ b/imapserver/prefixconn.go @@ -15,10 +15,7 @@ type prefixConn struct { func (c *prefixConn) Read(buf []byte) (int, error) { if len(c.prefix) > 0 { - n := len(buf) - if n > len(c.prefix) { - n = len(c.prefix) - } + n := min(len(buf), len(c.prefix)) copy(buf[:n], c.prefix[:n]) c.prefix = c.prefix[n:] if len(c.prefix) == 0 { diff --git a/imapserver/search.go b/imapserver/search.go index 76a9f50..08e76e2 100644 --- a/imapserver/search.go +++ b/imapserver/search.go @@ -11,6 +11,7 @@ import ( "github.com/mjl-/mox/message" "github.com/mjl-/mox/store" + "slices" ) // Search returns messages matching criteria specified in parameters. @@ -454,12 +455,7 @@ func (s *search) match0(sk searchKey) bool { case "$mdnsent": return s.m.MDNSent default: - for _, k := range s.m.Keywords { - if k == kw { - return true - } - } - return false + return slices.Contains(s.m.Keywords, kw) } case "SEEN": return s.m.Seen @@ -483,12 +479,7 @@ func (s *search) match0(sk searchKey) bool { case "$mdnsent": return !s.m.MDNSent default: - for _, k := range s.m.Keywords { - if k == kw { - return false - } - } - return true + return !slices.Contains(s.m.Keywords, kw) } case "UNSEEN": return !s.m.Seen diff --git a/imapserver/search_test.go b/imapserver/search_test.go index 4af4203..8594512 100644 --- a/imapserver/search_test.go +++ b/imapserver/search_test.go @@ -66,7 +66,7 @@ func TestSearch(t *testing.T) { // Add 5 and delete first 4 messages. So UIDs start at 5. received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC) saveDate := time.Now() - for i := 0; i < 5; i++ { + for range 5 { tc.client.Append("inbox", makeAppendTime(exampleMsg, received)) } tc.client.StoreFlagsSet("1:4", true, `\Deleted`) @@ -394,7 +394,7 @@ func TestSearch(t *testing.T) { // More than 1mb total for literals. _, err = fmt.Fprintf(tc.client, "x0 uid search") tcheck(t, err, "write start of uit search") - for i := 0; i < 10; i++ { + for range 10 { writeTextLit(100*1024, true) } writeTextLit(1, false) @@ -402,7 +402,7 @@ func TestSearch(t *testing.T) { // More than 1000 literals. _, err = fmt.Fprintf(tc.client, "x0 uid search") tcheck(t, err, "write start of uit search") - for i := 0; i < 1000; i++ { + for range 1000 { writeTextLit(1, true) } writeTextLit(1, false) diff --git a/imapserver/server.go b/imapserver/server.go index 366e988..a193bfb 100644 --- a/imapserver/server.go +++ b/imapserver/server.go @@ -2760,9 +2760,7 @@ func (c *conn) cmdSelectExamine(isselect bool, tag, cmd string, p *parser) { // Now that we have all vanished UIDs, send them over compactly. if len(vanishedUIDs) > 0 { l := maps.Keys(vanishedUIDs) - sort.Slice(l, func(i, j int) bool { - return l[i] < l[j] - }) + slices.Sort(l) // ../rfc/7162:1985 for _, s := range compactUIDSet(l).Strings(4*1024 - 32) { c.bwritelinef("* VANISHED (EARLIER) %s", s) @@ -3913,9 +3911,7 @@ func (c *conn) gatherCopyMoveUIDs(isUID bool, nums numSet) ([]store.UID, []any) // response and interpret it differently than we intended. // ../rfc/9051:5072 uids := c.xnumSetUIDs(isUID, nums) - sort.Slice(uids, func(i, j int) bool { - return uids[i] < uids[j] - }) + slices.Sort(uids) uidargs := make([]any, len(uids)) for i, uid := range uids { uidargs[i] = uid @@ -4200,7 +4196,7 @@ func (c *conn) cmdxMove(isUID bool, tag, cmd string, p *parser) { c.bwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String()) qresync := c.enabled[capQresync] var vanishedUIDs numSet - for i := 0; i < len(uids); i++ { + for i := range uids { seq := c.xsequence(uids[i]) c.sequenceRemove(seq, uids[i]) if qresync { @@ -4452,7 +4448,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) { origFlags := m.Flags m.Flags = m.Flags.Set(mask, flags) - oldKeywords := append([]string{}, m.Keywords...) + oldKeywords := slices.Clone(m.Keywords) if minus { m.Keywords, _ = store.RemoveKeywords(m.Keywords, keywords) } else if plus { @@ -4463,7 +4459,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) { keywordsChanged := func() bool { sort.Strings(oldKeywords) - n := append([]string{}, m.Keywords...) + n := slices.Clone(m.Keywords) sort.Strings(n) return !slices.Equal(oldKeywords, n) } @@ -4581,9 +4577,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) { } } - sort.Slice(mnums, func(i, j int) bool { - return mnums[i] < mnums[j] - }) + slices.Sort(mnums) set := compactUIDSet(mnums) // ../rfc/7162:2506 c.writeresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String()) diff --git a/imapserver/server_test.go b/imapserver/server_test.go index 88488d8..9025f40 100644 --- a/imapserver/server_test.go +++ b/imapserver/server_test.go @@ -25,6 +25,7 @@ import ( "github.com/mjl-/mox/mox-" "github.com/mjl-/mox/moxvar" "github.com/mjl-/mox/store" + "slices" ) var ctxbg = context.Background() @@ -210,7 +211,7 @@ func (tc *testconn) xuntagged(exps ...imapclient.Untagged) { func (tc *testconn) xuntaggedOpt(all bool, exps ...imapclient.Untagged) { tc.t.Helper() - last := append([]imapclient.Untagged{}, tc.lastUntagged...) + last := slices.Clone(tc.lastUntagged) var mismatch any next: for ei, exp := range exps { @@ -572,13 +573,13 @@ func TestState(t *testing.T) { defer tc.close() // Not authenticated, lots of commands not allowed. - for _, cmd := range append(append([]string{}, authenticatedOrSelected...), selected...) { + for _, cmd := range slices.Concat(authenticatedOrSelected, selected) { tc.transactf("no", "%s", cmd) } // Some commands not allowed when authenticated. tc.transactf("ok", `login mjl@mox.example "%s"`, password0) - for _, cmd := range append(append([]string{}, notAuthenticated...), selected...) { + for _, cmd := range slices.Concat(notAuthenticated, selected) { tc.transactf("no", "%s", cmd) } diff --git a/junk/bloom.go b/junk/bloom.go index 50e6783..273d2be 100644 --- a/junk/bloom.go +++ b/junk/bloom.go @@ -70,14 +70,14 @@ func NewBloom(data []byte, k int) (*Bloom, error) { func (b *Bloom) Add(s string) { h := hash([]byte(s), b.w) - for i := 0; i < b.k; i++ { + for range b.k { b.set(h.nextPos()) } } func (b *Bloom) Has(s string) bool { h := hash([]byte(s), b.w) - for i := 0; i < b.k; i++ { + for range b.k { if !b.has(h.nextPos()) { return false } @@ -96,7 +96,7 @@ func (b *Bloom) Modified() bool { // Ones returns the number of ones. func (b *Bloom) Ones() (n int) { for _, d := range b.data { - for i := 0; i < 8; i++ { + for range 8 { if d&1 != 0 { n++ } diff --git a/junk/bloom_test.go b/junk/bloom_test.go index 96fa54b..ebb58da 100644 --- a/junk/bloom_test.go +++ b/junk/bloom_test.go @@ -62,26 +62,26 @@ func TestBloom(t *testing.T) { func TestBits(t *testing.T) { b := &bits{width: 1, buf: []byte{0xff, 0xff}} - for i := 0; i < 16; i++ { + for range 16 { if b.nextPos() != 1 { t.Fatalf("pos not 1") } } b = &bits{width: 2, buf: []byte{0xff, 0xff}} - for i := 0; i < 8; i++ { + for range 8 { if b.nextPos() != 0b11 { t.Fatalf("pos not 0b11") } } b = &bits{width: 1, buf: []byte{0b10101010, 0b10101010}} - for i := 0; i < 16; i++ { + for i := range 16 { if b.nextPos() != ((i + 1) % 2) { t.Fatalf("bad pos") } } b = &bits{width: 2, buf: []byte{0b10101010, 0b10101010}} - for i := 0; i < 8; i++ { + for range 8 { if b.nextPos() != 0b10 { t.Fatalf("pos not 0b10") } @@ -97,7 +97,7 @@ func TestSet(t *testing.T) { 0b01010101, }, } - for i := 0; i < 8; i++ { + for i := range 8 { v := b.has(i) if v != (i%2 == 0) { t.Fatalf("bad has") diff --git a/junk/filter.go b/junk/filter.go index f2bb6ce..7b207b4 100644 --- a/junk/filter.go +++ b/junk/filter.go @@ -27,6 +27,7 @@ import ( "github.com/mjl-/mox/message" "github.com/mjl-/mox/mlog" "github.com/mjl-/mox/moxvar" + "slices" ) var ( @@ -270,9 +271,7 @@ func (f *Filter) Save() error { words[i] = w i++ } - sort.Slice(words, func(i, j int) bool { - return words[i] < words[j] - }) + slices.Sort(words) f.log.Debug("inserting words in junkfilter db", slog.Any("words", len(f.changed))) // start := time.Now() @@ -336,9 +335,7 @@ func (f *Filter) Save() error { } func loadWords(ctx context.Context, db *bstore.DB, l []string, dst map[string]word) error { - sort.Slice(l, func(i, j int) bool { - return l[i] < l[j] - }) + slices.Sort(l) err := db.Read(ctx, func(tx *bstore.Tx) error { for _, w := range l { @@ -478,14 +475,8 @@ func (f *Filter) ClassifyWords(ctx context.Context, words map[string]struct{}) ( return a.Score > b.Score }) - nham := f.TopWords - if nham > len(topHam) { - nham = len(topHam) - } - nspam := f.TopWords - if nspam > len(topSpam) { - nspam = len(topSpam) - } + nham := min(f.TopWords, len(topHam)) + nspam := min(f.TopWords, len(topSpam)) topHam = topHam[:nham] topSpam = topSpam[:nspam] diff --git a/junk/parse.go b/junk/parse.go index 3336198..ad5373b 100644 --- a/junk/parse.go +++ b/junk/parse.go @@ -253,10 +253,7 @@ func (r *htmlTextReader) Read(buf []byte) (n int, err error) { // todo: deal with inline elements? they shouldn't cause a word break. give := func(nbuf []byte) (int, error) { - n := len(buf) - if n > len(nbuf) { - n = len(nbuf) - } + n := min(len(buf), len(nbuf)) copy(buf, nbuf[:n]) nbuf = nbuf[n:] if len(nbuf) < cap(r.buf) { diff --git a/main.go b/main.go index 84524da..bd04037 100644 --- a/main.go +++ b/main.go @@ -1223,7 +1223,7 @@ error too, for reference. xwriteFile(prefix+".ed25519privatekey.pkcs8.pem", privKeyBufPEM, "private key") xwriteFile(prefix+".certificate.pem", certBufPEM, "certificate") - combinedPEM := append(append([]byte{}, privKeyBufPEM...), certBufPEM...) + combinedPEM := slices.Concat(privKeyBufPEM, certBufPEM) xwriteFile(prefix+".ed25519privatekey-certificate.pem", combinedPEM, "combined private key and certificate") shabuf := sha256.Sum256(tlsCert.Leaf.RawSubjectPublicKeyInfo) @@ -3229,7 +3229,7 @@ func cmdWebapi(c *cmd) { t := reflect.TypeFor[webapi.Methods]() methods := map[string]reflect.Type{} var ml []string - for i := 0; i < t.NumMethod(); i++ { + for i := range t.NumMethod() { mt := t.Method(i) methods[mt.Name] = mt.Type ml = append(ml, mt.Name) @@ -3999,7 +3999,7 @@ For testing the pagination. Operates directly on queue database. xcheckf(err, "removing temporary webhook after forwarding autoincrement sequence") fh.ID -= int64(n) - for i := 0; i < n; i++ { + for i := range n { t0 := now.Add(-time.Duration(i) * time.Second) last := now.Add(-time.Duration(i/10) * time.Second) mr := queue.MsgRetired{ @@ -4037,7 +4037,7 @@ For testing the pagination. Operates directly on queue database. xcheckf(err, "inserting retired message") } - for i := 0; i < n; i++ { + for i := range n { t0 := now.Add(-time.Duration(i) * time.Second) last := now.Add(-time.Duration(i/10) * time.Second) var event string diff --git a/message/part.go b/message/part.go index 61e2faa..0a1a0bc 100644 --- a/message/part.go +++ b/message/part.go @@ -27,6 +27,7 @@ import ( "github.com/mjl-/mox/mlog" "github.com/mjl-/mox/smtp" + "slices" ) // Pedantic enables stricter parsing. @@ -848,10 +849,8 @@ func (b *bufAt) maxLineLength() int { // ensure makes sure b.nbuf is up to maxLineLength, unless eof is encountered. func (b *bufAt) ensure() error { - for _, c := range b.buf[:b.nbuf] { - if c == '\n' { - return nil - } + if slices.Contains(b.buf[:b.nbuf], '\n') { + return nil } if b.scratch == nil { b.scratch = make([]byte, b.maxLineLength()) @@ -1014,10 +1013,7 @@ func (b *boundReader) Read(buf []byte) (count int, rerr error) { for { // Read data from earlier line. if b.nbuf > 0 { - n := b.nbuf - if n > len(buf) { - n = len(buf) - } + n := min(b.nbuf, len(buf)) copy(buf, b.buf[:n]) copy(b.buf, b.buf[n:]) buf = buf[n:] @@ -1046,10 +1042,7 @@ func (b *boundReader) Read(buf []byte) (count int, rerr error) { return count, err } if len(b.crlf) > 0 { - n := len(b.crlf) - if n > len(buf) { - n = len(buf) - } + n := min(len(b.crlf), len(buf)) copy(buf, b.crlf[:n]) count += n buf = buf[n:] diff --git a/message/writer.go b/message/writer.go index a75ac38..e632b24 100644 --- a/message/writer.go +++ b/message/writer.go @@ -56,10 +56,7 @@ func (w *Writer) Write(buf []byte) (int, error) { // Update w.tail after having written. Regardless of error, writers can't expect // subsequent writes to work again properly anyway. defer func() { - n := len(buf) - if n > 3 { - n = 3 - } + n := min(len(buf), 3) copy(w.tail[:], w.tail[n:]) copy(w.tail[3-n:], buf[len(buf)-n:]) }() diff --git a/mlog/log.go b/mlog/log.go index decab52..327e674 100644 --- a/mlog/log.go +++ b/mlog/log.go @@ -28,6 +28,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "slices" ) var noctx = context.Background() @@ -452,7 +453,7 @@ func stringValue(iscid, nested bool, v any) string { } b := &strings.Builder{} b.WriteString("[") - for i := 0; i < n; i++ { + for i := range n { if i > 0 { b.WriteString(";") } @@ -469,10 +470,10 @@ func stringValue(iscid, nested bool, v any) string { // We first try making a string without recursing into structs/pointers/interfaces, // but will try again with those fields if we otherwise would otherwise log an // empty string. - for j := 0; j < 2; j++ { + for j := range 2 { first := true b := &strings.Builder{} - for i := 0; i < n; i++ { + for i := range n { fv := rv.Field(i) if !t.Field(i).IsExported() { continue @@ -636,7 +637,7 @@ func (w *errWriter) Write(buf []byte) (int, error) { func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { nh := *h if h.Attrs != nil { - nh.Attrs = append([]slog.Attr{}, h.Attrs...) + nh.Attrs = slices.Clone(h.Attrs) } nh.Attrs = append(nh.Attrs, attrs...) return &nh @@ -654,7 +655,7 @@ func (h *handler) WithGroup(name string) slog.Handler { func (h *handler) WithPkg(pkg string) *handler { nh := *h if nh.Pkgs != nil { - nh.Pkgs = append([]string{}, nh.Pkgs...) + nh.Pkgs = slices.Clone(nh.Pkgs) } nh.Pkgs = append(nh.Pkgs, pkg) return &nh diff --git a/mox-/config.go b/mox-/config.go index e371c3e..7757e67 100644 --- a/mox-/config.go +++ b/mox-/config.go @@ -25,7 +25,6 @@ import ( "path/filepath" "regexp" "slices" - "sort" "strconv" "strings" "sync" @@ -199,9 +198,7 @@ func (c *Config) Domains() (l []string) { l = append(l, name) } }) - sort.Slice(l, func(i, j int) bool { - return l[i] < l[j] - }) + slices.Sort(l) return l } diff --git a/mox-/fill.go b/mox-/fill.go index 9c96373..2ce9e38 100644 --- a/mox-/fill.go +++ b/mox-/fill.go @@ -9,7 +9,7 @@ import ( func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) { switch rv.Kind() { case reflect.Struct: - for i := 0; i < rv.NumField(); i++ { + for i := range rv.NumField() { if !rv.Type().Field(i).IsExported() { continue } @@ -18,7 +18,7 @@ func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) { if ch && !rv.CanSet() { // Make struct settable. nrv := reflect.New(rv.Type()).Elem() - for j := 0; j < rv.NumField(); j++ { + for j := range rv.NumField() { nrv.Field(j).Set(rv.Field(j)) } rv = nrv @@ -34,7 +34,7 @@ func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) { return reflect.MakeSlice(rv.Type(), 0, 0), true } n := rv.Len() - for i := 0; i < n; i++ { + for i := range n { rve := rv.Index(i) nrv, ch := FillNil(rve) if ch { @@ -90,7 +90,7 @@ func FillExample(seen []reflect.Type, rv reflect.Value) reflect.Value { switch rv.Kind() { case reflect.Struct: - for i := 0; i < rv.NumField(); i++ { + for i := range rv.NumField() { if !rvt.Field(i).IsExported() { continue } diff --git a/mox-/password.go b/mox-/password.go index 0498007..f22f26f 100644 --- a/mox-/password.go +++ b/mox-/password.go @@ -8,7 +8,7 @@ func GeneratePassword() string { chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_;:,<.>/" s := "" buf := make([]byte, 1) - for i := 0; i < 12; i++ { + for range 12 { for { cryptorand.Read(buf) i := int(buf[0]) diff --git a/mox-/txt.go b/mox-/txt.go index 47bc173..af3946f 100644 --- a/mox-/txt.go +++ b/mox-/txt.go @@ -9,10 +9,7 @@ func TXTStrings(s string) string { r := "(\n" for len(s) > 0 { - n := len(s) - if n > 100 { - n = 100 - } + n := min(len(s), 100) if r != "" { r += " " } diff --git a/moxio/base64writer.go b/moxio/base64writer.go index be54921..5ec31c7 100644 --- a/moxio/base64writer.go +++ b/moxio/base64writer.go @@ -39,10 +39,7 @@ type lineWrapper struct { func (lw *lineWrapper) Write(buf []byte) (int, error) { wrote := 0 for len(buf) > 0 { - n := 78 - lw.n - if n > len(buf) { - n = len(buf) - } + n := min(78-lw.n, len(buf)) nn, err := lw.w.Write(buf[:n]) if nn > 0 { wrote += nn diff --git a/moxio/bufpool.go b/moxio/bufpool.go index f3e79dc..9752d39 100644 --- a/moxio/bufpool.go +++ b/moxio/bufpool.go @@ -54,7 +54,7 @@ func (b *Bufpool) put(log mlog.Log, buf []byte, n int) { return } - for i := 0; i < n; i++ { + for i := range n { buf[i] = 0 } select { diff --git a/moxio/bufpool_test.go b/moxio/bufpool_test.go index ce705af..2940e19 100644 --- a/moxio/bufpool_test.go +++ b/moxio/bufpool_test.go @@ -15,7 +15,7 @@ func TestBufpool(t *testing.T) { bp := NewBufpool(1, 8) a := bp.get() b := bp.get() - for i := 0; i < len(a); i++ { + for i := range a { a[i] = 1 } log := mlog.New("moxio", nil) diff --git a/moxio/workq.go b/moxio/workq.go index 3a0268c..3d5501f 100644 --- a/moxio/workq.go +++ b/moxio/workq.go @@ -50,7 +50,7 @@ func NewWorkQueue[T, R any](procs, size int, preparer func(in, out chan Work[T, } wq.wg.Add(procs) - for i := 0; i < procs; i++ { + for range procs { go func() { defer wq.wg.Done() preparer(wq.work, wq.done) diff --git a/queue/hook_test.go b/queue/hook_test.go index 58484ca..802e40b 100644 --- a/queue/hook_test.go +++ b/queue/hook_test.go @@ -429,7 +429,7 @@ func TestFromIDIncomingDelivery(t *testing.T) { tcheck(t, err, "get added hook") h.URL = hs.URL handler = handleError - for i := 0; i < len(hookIntervals); i++ { + for i := range hookIntervals { hookDeliver(pkglog, h) <-hookDeliveryResults err := DB.Get(ctxbg, &h) @@ -557,7 +557,7 @@ func TestHookListFilterSort(t *testing.T) { // Descending by submitted,id. l, err = HookList(ctxbg, HookFilter{}, HookSort{Field: "Submitted"}) tcheck(t, err, "list") - ll := append(append([]Hook{}, hlrev[1:]...), hl[5]) + ll := append(slices.Clone(hlrev[1:]), hl[5]) tcompare(t, l, ll) // Filter by all fields to get a single. diff --git a/queue/queue.go b/queue/queue.go index e2e722e..2fdcf33 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -1372,7 +1372,7 @@ func deliver(log mlog.Log, resolver dns.Resolver, m0 Msg) { } backoff = time.Duration(7*60+30+jitter.IntN(10)-5) * time.Second - for i := 0; i < m0.Attempts; i++ { + for range m0.Attempts { backoff *= time.Duration(2) } m0.Attempts++ diff --git a/queue/queue_test.go b/queue/queue_test.go index e791d00..4443c33 100644 --- a/queue/queue_test.go +++ b/queue/queue_test.go @@ -310,10 +310,10 @@ func TestQueue(t *testing.T) { writeline("250-" + ext) } writeline("250 pipelining") - for tx := 0; tx < ntx; tx++ { + for range ntx { readline("mail") writeline("250 ok") - for i := 0; i < rcpts; i++ { + for i := range rcpts { readline("rcpt") if onercpt && i > 0 { writeline("552 ok") @@ -462,7 +462,7 @@ func TestQueue(t *testing.T) { fmt.Fprintf(server, "235 2.7.0 auth ok\r\n") br.ReadString('\n') // Should be MAIL FROM. fmt.Fprintf(server, "250 ok\r\n") - for i := 0; i < nrcpt; i++ { + for range nrcpt { br.ReadString('\n') // Should be RCPT TO. fmt.Fprintf(server, "250 ok\r\n") } @@ -520,7 +520,7 @@ func TestQueue(t *testing.T) { // Wait for all results. timer.Reset(time.Second) - for i := 0; i < nresults; i++ { + for range nresults { select { case <-deliveryResults: case <-timer.C: @@ -1331,7 +1331,7 @@ func TestListFilterSort(t *testing.T) { // Descending by queued,id. l, err = List(ctxbg, Filter{}, Sort{Field: "Queued"}) tcheck(t, err, "list messages") - ql := append(append([]Msg{}, qmlrev[1:]...), qml[5]) + ql := append(slices.Clone(qmlrev[1:]), qml[5]) tcompare(t, l, ql) // Filter by all fields to get a single. diff --git a/quickstart.go b/quickstart.go index ee54d76..248276f 100644 --- a/quickstart.go +++ b/quickstart.go @@ -39,6 +39,7 @@ import ( "github.com/mjl-/mox/rdap" "github.com/mjl-/mox/smtp" "github.com/mjl-/mox/store" + "slices" ) //go:embed mox.service @@ -344,9 +345,7 @@ Troubleshooting hints: for k := range names { nameList = append(nameList, strings.TrimRight(k, ".")) } - sort.Slice(nameList, func(i, j int) bool { - return nameList[i] < nameList[j] - }) + slices.Sort(nameList) if len(nameList) == 0 { dnshostname, err = dns.ParseDomain(hostnameStr + "." + domain.Name()) if err != nil { @@ -534,7 +533,7 @@ messages over SMTP. fmt.Printf("\nWARNING: %s", fmt.Sprintf(format, args...)) warned = true } - for i := 0; i < len(ips); i++ { + for range ips { r := <-results if r.Err != nil { warnf("looking up reverse name for %s: %v", r.IP, r.Err) diff --git a/ratelimit/ratelimit.go b/ratelimit/ratelimit.go index 6f5914b..2790039 100644 --- a/ratelimit/ratelimit.go +++ b/ratelimit/ratelimit.go @@ -55,7 +55,7 @@ func (l *Limiter) checkAdd(add bool, ip net.IP, tm time.Time, n int64) bool { l.WindowLimits[i].Counts = pl.Counts } - for j := 0; j < 3; j++ { + for j := range 3 { if i == 0 { l.ipmasked[j] = l.maskIP(j, ip) } @@ -74,7 +74,7 @@ func (l *Limiter) checkAdd(add bool, ip net.IP, tm time.Time, n int64) bool { } // Finally record. for _, pl := range l.WindowLimits { - for j := 0; j < 3; j++ { + for j := range 3 { pl.Counts[struct { Index uint8 IPMasked [16]byte @@ -90,7 +90,7 @@ func (l *Limiter) Reset(ip net.IP, tm time.Time) { defer l.Unlock() // Prepare masked ip's. - for i := 0; i < 3; i++ { + for i := range 3 { l.ipmasked[i] = l.maskIP(i, ip) } @@ -100,7 +100,7 @@ func (l *Limiter) Reset(ip net.IP, tm time.Time) { continue } var n int64 - for j := 0; j < 3; j++ { + for j := range 3 { k := struct { Index uint8 IPMasked [16]byte diff --git a/sasl/sasl.go b/sasl/sasl.go index 22c4348..0d0b340 100644 --- a/sasl/sasl.go +++ b/sasl/sasl.go @@ -77,7 +77,7 @@ func (a *clientPlain) Next(fromServer []byte) (toServer []byte, last bool, rerr defer func() { a.step++ }() switch a.step { case 0: - return []byte(fmt.Sprintf("\u0000%s\u0000%s", a.Username, a.Password)), true, nil + return fmt.Appendf(nil, "\u0000%s\u0000%s", a.Username, a.Password), true, nil default: return nil, false, fmt.Errorf("invalid step %d", a.step) } @@ -189,7 +189,7 @@ func (a *clientCRAMMD5) Next(fromServer []byte) (toServer []byte, last bool, rer opadh.Write(ipadh.Sum(nil)) // ../rfc/2195:88 - return []byte(fmt.Sprintf("%s %x", a.Username, opadh.Sum(nil))), true, nil + return fmt.Appendf(nil, "%s %x", a.Username, opadh.Sum(nil)), true, nil default: return nil, false, fmt.Errorf("invalid step %d", a.step) diff --git a/serve_unix.go b/serve_unix.go index 0c727df..a8867c5 100644 --- a/serve_unix.go +++ b/serve_unix.go @@ -85,7 +85,7 @@ func monitorDNSBL(log mlog.Log) { last = time.Now() // Gather zones. - zones := append([]dns.Domain{}, publicListener.SMTP.DNSBLZones...) + zones := slices.Clone(publicListener.SMTP.DNSBLZones) conf := mox.Conf.DynamicConfig() for _, zone := range conf.MonitorDNSBLZones { if !slices.Contains(zones, zone) { diff --git a/smtp/data.go b/smtp/data.go index ac73fa6..2d2113a 100644 --- a/smtp/data.go +++ b/smtp/data.go @@ -164,10 +164,7 @@ func (r *DataReader) Read(p []byte) (int, error) { // Reject "[^\r]\n.\n" and "[^\r]\n.\r\n" r.badcrlf = true } - n := len(r.buf) - if n > len(p) { - n = len(p) - } + n := min(len(r.buf), len(p)) copy(p, r.buf[:n]) if n == 1 { r.plast, r.last = r.last, r.buf[0] diff --git a/smtpclient/client.go b/smtpclient/client.go index d9302d3..3cf2817 100644 --- a/smtpclient/client.go +++ b/smtpclient/client.go @@ -1243,7 +1243,7 @@ func (c *Client) DeliverMultiple(ctx context.Context, mailFrom string, rcptTo [] // Read responses to RCPT TO. rcptResps = make([]Response, len(rcptTo)) nok := 0 - for i := 0; i < len(rcptTo); i++ { + for i := range rcptTo { code, secode, firstLine, moreLines, err := c.read() // 552 should be treated as temporary historically, ../rfc/5321:3576 permanent := code/100 == 5 && code != smtp.C552MailboxFull diff --git a/smtpclient/client_test.go b/smtpclient/client_test.go index 4073ae8..373d0b0 100644 --- a/smtpclient/client_test.go +++ b/smtpclient/client_test.go @@ -273,7 +273,7 @@ func TestClient(t *testing.T) { if n == 0 { n = 1 } - for i := 0; i < n; i++ { + for i := range n { readline("RCPT TO:") resp := "250 ok" if i < len(opts.resps) { @@ -293,7 +293,7 @@ func TestClient(t *testing.T) { readline("MAIL FROM:") writeline("250 ok") - for i := 0; i < n; i++ { + for i := range n { readline("RCPT TO:") resp := "250 ok" if i < len(opts.resps) { @@ -366,7 +366,7 @@ func TestClient(t *testing.T) { }() var errs []error - for i := 0; i < 2; i++ { + for range 2 { err := <-result if err != nil { errs = append(errs, err) @@ -907,7 +907,7 @@ func run(t *testing.T, server func(s xserver), client func(conn net.Conn)) { client(clientConn) }() var errs []error - for i := 0; i < 2; i++ { + for range 2 { err := <-result if err != nil { errs = append(errs, err) diff --git a/smtpclient/gather_test.go b/smtpclient/gather_test.go index de0b241..b1760a9 100644 --- a/smtpclient/gather_test.go +++ b/smtpclient/gather_test.go @@ -104,7 +104,7 @@ func TestGatherDestinations(t *testing.T) { var zerodom dns.Domain - for i := 0; i < 2; i++ { + for i := range 2 { authic := i == 1 resolver.AllAuthentic = authic // Basic with simple MX. @@ -187,7 +187,7 @@ func TestGatherIPs(t *testing.T) { return r } - for i := 0; i < 2; i++ { + for i := range 2 { authic := i == 1 resolver.AllAuthentic = authic diff --git a/smtpserver/server.go b/smtpserver/server.go index 358e48c..1cc5c98 100644 --- a/smtpserver/server.go +++ b/smtpserver/server.go @@ -3203,7 +3203,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW rcptDMARCMethod.Comment += "override " + strings.Join(dmarcOverrides, ",") } rcptAuthResults := authResults - rcptAuthResults.Methods = append([]message.AuthMethod{}, authResults.Methods...) + rcptAuthResults.Methods = slices.Clone(authResults.Methods) rcptAuthResults.Methods = append(rcptAuthResults.Methods, rcptDMARCMethod) // Prepend reason as message header, for easy viewing in mail clients. diff --git a/smtpserver/server_test.go b/smtpserver/server_test.go index a2f58d0..17e7e7c 100644 --- a/smtpserver/server_test.go +++ b/smtpserver/server_test.go @@ -727,7 +727,7 @@ func TestSpam(t *testing.T) { Flags: store.Flags{Seen: true, Junk: true}, Size: int64(len(deliverMessage)), } - for i := 0; i < 3; i++ { + for range 3 { nm := m tinsertmsg(t, ts.acc, "Inbox", &nm, deliverMessage) nm = m @@ -868,7 +868,7 @@ happens to come from forwarding mail server. mailFrom = "remote@bad.example" } - for i := 0; i < 10; i++ { + for range 10 { err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msgBad)), strings.NewReader(msgBad), false, false, false) tcheck(t, err, "deliver message") } @@ -967,7 +967,7 @@ func TestDMARCSent(t *testing.T) { } // We need at least 50 ham messages for the junk filter to become significant. We // offset it with negative messages for mediocre score. - for i := 0; i < 50; i++ { + for range 50 { nm := m nm.Junk = true tinsertmsg(t, ts.acc, "Archive", &nm, deliverMessage) diff --git a/spf/spf.go b/spf/spf.go index 48120c9..2471fe1 100644 --- a/spf/spf.go +++ b/spf/spf.go @@ -806,7 +806,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str if reverse { nt := len(t) h := nt / 2 - for i := 0; i < h; i++ { + for i := range h { t[i], t[nt-1-i] = t[nt-1-i], t[i] } } diff --git a/store/account.go b/store/account.go index 0ad62d4..d890733 100644 --- a/store/account.go +++ b/store/account.go @@ -3198,7 +3198,7 @@ func RemoveKeywords(l, remove []string) ([]string, bool) { for _, k := range remove { if i := slices.Index(l, k); i >= 0 { if !copied { - l = append([]string{}, l...) + l = slices.Clone(l) copied = true } copy(l[i:], l[i+1:]) @@ -3219,7 +3219,7 @@ func MergeKeywords(l, add []string) ([]string, bool) { for _, k := range add { if !slices.Contains(l, k) { if !copied { - l = append([]string{}, l...) + l = slices.Clone(l) copied = true } l = append(l, k) diff --git a/store/account_test.go b/store/account_test.go index c5b485d..c8f3713 100644 --- a/store/account_test.go +++ b/store/account_test.go @@ -234,14 +234,14 @@ func TestMailbox(t *testing.T) { }) // Run the auth tests twice for possible cache effects. - for i := 0; i < 2; i++ { + for range 2 { _, _, err := OpenEmailAuth(log, "mjl@mox.example", "bogus", false) if err != ErrUnknownCredentials { t.Fatalf("got %v, expected ErrUnknownCredentials", err) } } - for i := 0; i < 2; i++ { + for range 2 { acc2, _, err := OpenEmailAuth(log, "mjl@mox.example", "testtest", false) tcheck(t, err, "open for email with auth") err = acc2.Close() diff --git a/store/loginattempt_test.go b/store/loginattempt_test.go index 26c8a5a..8c467af 100644 --- a/store/loginattempt_test.go +++ b/store/loginattempt_test.go @@ -99,7 +99,7 @@ func TestLoginAttempt(t *testing.T) { // Insert 3 failing entries. Then add another and see we still have 3. loginAttemptsMaxPerAccount = 3 - for i := 0; i < loginAttemptsMaxPerAccount; i++ { + for i := range loginAttemptsMaxPerAccount { a := a2 a.UserAgent = fmt.Sprintf("%d", i) LoginAttemptAdd(ctxbg, pkglog, a) diff --git a/tlsrptsend/send.go b/tlsrptsend/send.go index 33ede81..50b1ebd 100644 --- a/tlsrptsend/send.go +++ b/tlsrptsend/send.go @@ -696,7 +696,7 @@ func composeMessage(ctx context.Context, log mlog.Log, mf *os.File, policyDomain selectors := mox.DKIMSelectors(confDKIM) for i, sel := range selectors { // Also sign the TLS-Report headers. ../rfc/8460:940 - sel.Headers = append(append([]string{}, sel.Headers...), "TLS-Report-Domain", "TLS-Report-Submitter") + sel.Headers = append(slices.Clone(sel.Headers), "TLS-Report-Domain", "TLS-Report-Submitter") selectors[i] = sel } diff --git a/tlsrptsend/send_test.go b/tlsrptsend/send_test.go index 3cbb36c..e69ceb1 100644 --- a/tlsrptsend/send_test.go +++ b/tlsrptsend/send_test.go @@ -20,6 +20,7 @@ import ( "github.com/mjl-/mox/queue" "github.com/mjl-/mox/tlsrpt" "github.com/mjl-/mox/tlsrptdb" + "slices" ) var ctxbg = context.Background() @@ -423,7 +424,7 @@ func TestSendReports(t *testing.T) { tcheckf(t, err, "read report message") p := fmt.Sprintf("../testdata/tlsrptsend/data/report%d.eml", index) index++ - err = os.WriteFile(p, append(append([]byte{}, qml[0].MsgPrefix...), buf...), 0600) + err = os.WriteFile(p, slices.Concat(qml[0].MsgPrefix, buf), 0600) tcheckf(t, err, "write report message") reportJSON, err := tlsrpt.ParseMessage(log.Logger, msgFile) diff --git a/webadmin/admin.go b/webadmin/admin.go index b857fa9..824566b 100644 --- a/webadmin/admin.go +++ b/webadmin/admin.go @@ -657,7 +657,7 @@ EOF } } r.IPRev.IPNames = map[string][]string{} - for i := 0; i < n; i++ { + for range n { lr := <-results host, addrs, ip, err := lr.Host, lr.Addrs, lr.IP, lr.Err if err != nil { @@ -1598,9 +1598,7 @@ func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAcco // Accounts returns the names of all configured and all disabled accounts. func (Admin) Accounts(ctx context.Context) (all, disabled []string) { all, disabled = mox.Conf.AccountsDisabled() - sort.Slice(all, func(i, j int) bool { - return all[i] < all[j] - }) + slices.Sort(all) return } @@ -1862,7 +1860,7 @@ func (Admin) DNSBLStatus(ctx context.Context) (results map[string]map[string]str func dnsblsStatus(ctx context.Context, log mlog.Log, resolver dns.Resolver) (results map[string]map[string]string, using, monitoring []dns.Domain) { // todo: check health before using dnsbl? using = mox.Conf.Static.Listeners["public"].SMTP.DNSBLZones - zones := append([]dns.Domain{}, using...) + zones := slices.Clone(using) conf := mox.Conf.DynamicConfig() for _, zone := range conf.MonitorDNSBLZones { if !slices.Contains(zones, zone) { diff --git a/webapisrv/server.go b/webapisrv/server.go index 0c0b8a0..cacd852 100644 --- a/webapisrv/server.go +++ b/webapisrv/server.go @@ -202,7 +202,7 @@ func init() { var methods []string mt := reflect.TypeFor[webapi.Methods]() n := mt.NumMethod() - for i := 0; i < n; i++ { + for i := range n { methods = append(methods, mt.Method(i).Name) } docsIndexTmpl := htmltemplate.Must(htmltemplate.New("index").Parse(` @@ -877,10 +877,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S for len(base64Data) > 0 { line := base64Data - n := len(line) - if n > 78 { - n = 78 - } + n := min(len(line), 78) line, base64Data = base64Data[:n], base64Data[n:] _, err := p.Write([]byte(line)) xcheckf(err, "writing attachment") diff --git a/webapisrv/server_test.go b/webapisrv/server_test.go index b4b7564..d677352 100644 --- a/webapisrv/server_test.go +++ b/webapisrv/server_test.go @@ -132,11 +132,11 @@ func TestServer(t *testing.T) { testHTTP("PUT", "/v0/Send", http.StatusMethodNotAllowed, "") testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "") - for i := 0; i < 11; i++ { + for range 11 { // Missing auth doesn't trigger auth rate limiter. testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "") } - for i := 0; i < 21; i++ { + for i := range 21 { // Bad auth does. expCode := http.StatusUnauthorized tooMany := i >= 10 diff --git a/webmail/api.go b/webmail/api.go index bb4d14d..61d30ac 100644 --- a/webmail/api.go +++ b/webmail/api.go @@ -826,10 +826,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) { for len(base64Data) > 0 { line := base64Data - n := len(line) - if n > 78 { - n = 78 - } + n := min(len(line), 78) line, base64Data = base64Data[:n], base64Data[n:] _, err := ap.Write(line) xcheckf(ctx, err, "writing attachment") diff --git a/webmail/api_test.go b/webmail/api_test.go index 3e2cb3e..662298a 100644 --- a/webmail/api_test.go +++ b/webmail/api_test.go @@ -143,7 +143,7 @@ func TestAPI(t *testing.T) { testLogin("bad@bad.example", pw0, "user:loginFailed") } // Ensure rate limiter is triggered, also for slow tests. - for i := 0; i < 10; i++ { + for range 10 { testLogin("bad@bad.example", pw0, "user:loginFailed", "user:error") } testLogin("bad@bad.example", pw0, "user:error") diff --git a/webmail/message.go b/webmail/message.go index 0d6fe91..5f789fa 100644 --- a/webmail/message.go +++ b/webmail/message.go @@ -20,6 +20,7 @@ import ( "github.com/mjl-/mox/moxio" "github.com/mjl-/mox/smtp" "github.com/mjl-/mox/store" + "slices" ) // todo: we should have all needed information for messageItem in store.Message (perhaps some data in message.Part) for fast access, not having to parse the on-disk message file. @@ -139,12 +140,7 @@ func formatFirstLine(r io.Reader) (string, error) { if len(l) >= 5 { return false } - for _, line := range l { - if line == "" { - return false - } - } - return true + return !slices.Contains(l, "") } result := "" @@ -287,7 +283,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem if mt == "MULTIPART/SIGNED" && i >= 1 { continue } - usePart(sp, i, &p, append(append([]int{}, path...), i), newParentMixed) + usePart(sp, i, &p, append(slices.Clone(path), i), newParentMixed) } switch mt { case "TEXT/PLAIN", "/": @@ -313,7 +309,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem return } pm.Texts = append(pm.Texts, string(buf)) - pm.TextPaths = append(pm.TextPaths, append([]int{}, path...)) + pm.TextPaths = append(pm.TextPaths, slices.Clone(path)) } if msgitem && pm.firstLine == "" { pm.firstLine, rerr = formatFirstLine(p.ReaderUTF8OrBinary()) @@ -326,7 +322,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem case "TEXT/HTML": pm.HasHTML = true if full && pm.HTMLPath == nil { - pm.HTMLPath = append([]int{}, path...) + pm.HTMLPath = slices.Clone(path) } default: @@ -353,7 +349,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem return } pm.Texts = append(pm.Texts, string(buf)) - pm.TextPaths = append(pm.TextPaths, append([]int{}, path...)) + pm.TextPaths = append(pm.TextPaths, slices.Clone(path)) } return } @@ -365,7 +361,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem return } pm.Texts = append(pm.Texts, string(buf)) - pm.TextPaths = append(pm.TextPaths, append([]int{}, path...)) + pm.TextPaths = append(pm.TextPaths, slices.Clone(path)) } return } diff --git a/webmail/view.go b/webmail/view.go index cb89025..513a154 100644 --- a/webmail/view.go +++ b/webmail/view.go @@ -1971,7 +1971,7 @@ func (q Query) envFilterFn(log mlog.Log, state *msgState) func(m store.Message) return false } if len(filterTo) > 0 || len(notFilterTo) > 0 { - to := append(append(append([]message.Address{}, env.To...), env.CC...), env.BCC...) + to := slices.Concat(env.To, env.CC, env.BCC) if len(filterTo) > 0 && !contains(filterTo, to, true) { return false } diff --git a/webmail/view_test.go b/webmail/view_test.go index 345c2bc..bad209b 100644 --- a/webmail/view_test.go +++ b/webmail/view_test.go @@ -99,7 +99,7 @@ func TestView(t *testing.T) { // Token tokens := []string{} - for i := 0; i < 20; i++ { + for range 20 { tokens = append(tokens, api.Token(ctx)) } // Only last 10 tokens are still valid and around, checked below.