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.
This commit is contained in:
Mechiel Lukkien 2025-03-06 17:33:06 +01:00
parent f6132bdbc0
commit 64f2f788b1
No known key found for this signature in database
61 changed files with 146 additions and 232 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/mjl-/mox/config" "github.com/mjl-/mox/config"
"github.com/mjl-/mox/dns" "github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mox-" "github.com/mjl-/mox/mox-"
"slices"
) )
type TLSMode uint8 type TLSMode uint8
@ -130,9 +131,7 @@ func ClientConfigsDomain(d dns.Domain) (ClientConfigs, error) {
for name := range mox.Conf.Static.Listeners { for name := range mox.Conf.Static.Listeners {
listeners = append(listeners, name) listeners = append(listeners, name)
} }
sort.Slice(listeners, func(i, j int) bool { slices.Sort(listeners)
return listeners[i] < listeners[j]
})
note := func(tls bool, requiretls bool) string { note := func(tls bool, requiretls bool) string {
if !tls { if !tls {

View File

@ -8,7 +8,6 @@ import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net/url" "net/url"
"sort"
"strings" "strings"
"github.com/mjl-/adns" "github.com/mjl-/adns"
@ -21,6 +20,7 @@ import (
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/spf" "github.com/mjl-/mox/spf"
"github.com/mjl-/mox/tlsrpt" "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. // 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 { for name := range domConf.DKIM.Selectors {
selectors = append(selectors, name) selectors = append(selectors, name)
} }
sort.Slice(selectors, func(i, j int) bool { slices.Sort(selectors)
return selectors[i] < selectors[j]
})
for _, name := range selectors { for _, name := range selectors {
sel := domConf.DKIM.Selectors[name] sel := domConf.DKIM.Selectors[name]
dkimr := dkim.Record{ dkimr := dkim.Record{

11
ctl.go
View File

@ -15,7 +15,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime/debug" "runtime/debug"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -34,6 +33,7 @@ import (
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"github.com/mjl-/mox/webapi" "github.com/mjl-/mox/webapi"
"slices"
) )
// ctl represents a connection to the ctl unix domain socket of a running mox instance. // 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) s.npending = int(n)
} }
rn := len(buf) rn := min(len(buf), s.npending)
if rn > s.npending {
rn = s.npending
}
n, err := s.r.Read(buf[:rn]) n, err := s.r.Read(buf[:rn])
s.xcheck(err, "read from ctl") s.xcheck(err, "read from ctl")
s.npending -= n s.npending -= n
@ -1375,9 +1372,7 @@ func servectlcmd(ctx context.Context, ctl *ctl, cid int64, shutdown func()) {
for k := range l { for k := range l {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Slice(keys, func(i, j int) bool { slices.Sort(keys)
return keys[i] < keys[j]
})
s := "" s := ""
for _, k := range keys { for _, k := range keys {
ks := k ks := k

View File

@ -65,6 +65,7 @@ import (
"github.com/mjl-/mox/dns" "github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mlog" "github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/stub" "github.com/mjl-/mox/stub"
"slices"
) )
var ( var (
@ -214,12 +215,9 @@ func Dial(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, network
if allowedUsages != nil { if allowedUsages != nil {
o := 0 o := 0
for _, r := range records { for _, r := range records {
for _, usage := range allowedUsages { if slices.Contains(allowedUsages, r.Usage) {
if r.Usage == usage {
records[o] = r records[o] = r
o++ o++
break
}
} }
} }
records = records[:o] records = records[:o]

View File

@ -31,6 +31,7 @@ import (
"github.com/mjl-/mox/publicsuffix" "github.com/mjl-/mox/publicsuffix"
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/stub" "github.com/mjl-/mox/stub"
"slices"
) )
// If set, signatures for top-level domain "localhost" are accepted. // 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.Domain = domain
sig.Selector = sel.Domain sig.Selector = sel.Domain
sig.Identity = &Identity{&localpart, domain} sig.Identity = &Identity{&localpart, domain}
sig.SignedHeaders = append([]string{}, sel.Headers...) sig.SignedHeaders = slices.Clone(sel.Headers)
if sel.SealHeaders { if sel.SealHeaders {
// ../rfc/6376:2156 // ../rfc/6376:2156
// Each time a header name is added to the signature, the next unused value is // 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") return nil, 0, fmt.Errorf("empty header key")
} }
lkey = strings.ToLower(key) lkey = strings.ToLower(key)
value = append([]byte{}, t[1]...) value = slices.Clone(t[1])
raw = append([]byte{}, line...) raw = slices.Clone(line)
} }
if key != "" { if key != "" {
l = append(l, header{key, lkey, value, raw}) l = append(l, header{key, lkey, value, raw})

View File

@ -32,7 +32,7 @@ func TestParseRecord(t *testing.T) {
} }
if r != nil { if r != nil {
pk := r.Pubkey pk := r.Pubkey
for i := 0; i < 2; i++ { for range 2 {
ntxt, err := r.Record() ntxt, err := r.Record()
if err != nil { if err != nil {
t.Fatalf("making record: %v", err) t.Fatalf("making record: %v", err)

View File

@ -19,6 +19,7 @@ import (
"github.com/mjl-/mox/mox-" "github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio" "github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/queue" "github.com/mjl-/mox/queue"
"slices"
) )
func tcheckf(t *testing.T, err error, format string, args ...any) { 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. // Read message file. Also write copy to disk for inspection.
buf, err := io.ReadAll(&moxio.AtReader{R: msgFile}) buf, err := io.ReadAll(&moxio.AtReader{R: msgFile})
tcheckf(t, err, "read report message") 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") tcheckf(t, err, "write report message")
var feedback *dmarcrpt.Feedback var feedback *dmarcrpt.Feedback

View File

@ -340,10 +340,7 @@ func (m *Message) Compose(log mlog.Log, smtputf8 bool) ([]byte, error) {
data := base64.StdEncoding.EncodeToString(headers) data := base64.StdEncoding.EncodeToString(headers)
for len(data) > 0 { for len(data) > 0 {
line := data line := data
n := len(line) n := min(len(line), 78)
if n > 78 {
n = 78
}
line, data = data[:n], data[n:] line, data = data[:n], data[n:]
if _, err := origp.Write([]byte(line + "\r\n")); err != nil { if _, err := origp.Write([]byte(line + "\r\n")); err != nil {
return nil, err return nil, err

View File

@ -14,6 +14,7 @@ import (
"github.com/mjl-/mox/message" "github.com/mjl-/mox/message"
"github.com/mjl-/mox/mlog" "github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"slices"
) )
// Parse reads a DSN message. // Parse reads a DSN message.
@ -217,15 +218,9 @@ func parseRecipientHeader(mr *textproto.Reader, utf8 bool) (Recipient, error) {
case "Action": case "Action":
a := Action(strings.ToLower(v)) a := Action(strings.ToLower(v))
actions := []Action{Failed, Delayed, Delivered, Relayed, Expanded} actions := []Action{Failed, Delayed, Delivered, Relayed, Expanded}
var ok bool if slices.Contains(actions, a) {
for _, x := range actions {
if a == x {
ok = true
r.Action = a r.Action = a
break } else {
}
}
if !ok {
err = fmt.Errorf("unrecognized action %q", v) err = fmt.Errorf("unrecognized action %q", v)
} }
case "Status": case "Status":

View File

@ -64,7 +64,7 @@ func (m dict) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
case int: case int:
tokens = []xml.Token{ tokens = []xml.Token{
xml.StartElement{Name: xml.Name{Local: "integer"}}, 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"}}, xml.EndElement{Name: xml.Name{Local: "integer"}},
} }
case bool: case bool:

View File

@ -14,10 +14,7 @@ type prefixConn struct {
func (c *prefixConn) Read(buf []byte) (int, error) { func (c *prefixConn) Read(buf []byte) (int, error) {
if len(c.prefix) > 0 { if len(c.prefix) > 0 {
n := len(buf) n := min(len(buf), len(c.prefix))
if n > len(c.prefix) {
n = len(c.prefix)
}
copy(buf[:n], c.prefix[:n]) copy(buf[:n], c.prefix[:n])
c.prefix = c.prefix[n:] c.prefix = c.prefix[n:]
if len(c.prefix) == 0 { if len(c.prefix) == 0 {

View File

@ -10,6 +10,7 @@ import (
"github.com/mjl-/mox/imapclient" "github.com/mjl-/mox/imapclient"
"github.com/mjl-/mox/mox-" "github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"slices"
) )
func TestCondstore(t *testing.T) { 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 { 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. // uidvalidity 1, highest known modseq 1, sends full current state.

View File

@ -20,6 +20,7 @@ import (
"github.com/mjl-/mox/mox-" "github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio" "github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"slices"
) )
// functions to handle fetch attribute requests are defined on fetchCmd. // 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. // First sort the uids we already found, for fast lookup.
sort.Slice(vanishedUIDs, func(i, j int) bool { slices.Sort(vanishedUIDs)
return vanishedUIDs[i] < vanishedUIDs[j]
})
// We'll be gathering any more vanished uids in more. // We'll be gathering any more vanished uids in more.
more := map[store.UID]struct{}{} more := map[store.UID]struct{}{}
@ -239,9 +238,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) {
if len(vanishedUIDs) > 0 { if len(vanishedUIDs) > 0 {
// Mention all vanished UIDs in compact numset form. // Mention all vanished UIDs in compact numset form.
// ../rfc/7162:1985 // ../rfc/7162:1985
sort.Slice(vanishedUIDs, func(i, j int) bool { slices.Sort(vanishedUIDs)
return vanishedUIDs[i] < vanishedUIDs[j]
})
// No hard limit on response sizes, but clients are recommended to not send more // No hard limit on response sizes, but clients are recommended to not send more
// than 8k. We send a more conservative max 4k. // than 8k. We send a more conservative max 4k.
for _, s := range compactUIDSet(vanishedUIDs).Strings(4*1024 - 32) { for _, s := range compactUIDSet(vanishedUIDs).Strings(4*1024 - 32) {
@ -734,10 +731,7 @@ func (cmd *fetchCmd) xbody(a fetchAtt) (string, token) {
var offset int64 var offset int64
count := m.Size count := m.Size
if a.partial != nil { if a.partial != nil {
offset = int64(a.partial.offset) offset = min(int64(a.partial.offset), m.Size)
if offset > m.Size {
offset = m.Size
}
count = int64(a.partial.count) count = int64(a.partial.count)
if offset+count > m.Size { if offset+count > m.Size {
count = m.Size - offset count = m.Size - offset

View File

@ -15,10 +15,7 @@ type prefixConn struct {
func (c *prefixConn) Read(buf []byte) (int, error) { func (c *prefixConn) Read(buf []byte) (int, error) {
if len(c.prefix) > 0 { if len(c.prefix) > 0 {
n := len(buf) n := min(len(buf), len(c.prefix))
if n > len(c.prefix) {
n = len(c.prefix)
}
copy(buf[:n], c.prefix[:n]) copy(buf[:n], c.prefix[:n])
c.prefix = c.prefix[n:] c.prefix = c.prefix[n:]
if len(c.prefix) == 0 { if len(c.prefix) == 0 {

View File

@ -11,6 +11,7 @@ import (
"github.com/mjl-/mox/message" "github.com/mjl-/mox/message"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"slices"
) )
// Search returns messages matching criteria specified in parameters. // Search returns messages matching criteria specified in parameters.
@ -454,12 +455,7 @@ func (s *search) match0(sk searchKey) bool {
case "$mdnsent": case "$mdnsent":
return s.m.MDNSent return s.m.MDNSent
default: default:
for _, k := range s.m.Keywords { return slices.Contains(s.m.Keywords, kw)
if k == kw {
return true
}
}
return false
} }
case "SEEN": case "SEEN":
return s.m.Seen return s.m.Seen
@ -483,12 +479,7 @@ func (s *search) match0(sk searchKey) bool {
case "$mdnsent": case "$mdnsent":
return !s.m.MDNSent return !s.m.MDNSent
default: default:
for _, k := range s.m.Keywords { return !slices.Contains(s.m.Keywords, kw)
if k == kw {
return false
}
}
return true
} }
case "UNSEEN": case "UNSEEN":
return !s.m.Seen return !s.m.Seen

View File

@ -66,7 +66,7 @@ func TestSearch(t *testing.T) {
// Add 5 and delete first 4 messages. So UIDs start at 5. // 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) received := time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC)
saveDate := time.Now() saveDate := time.Now()
for i := 0; i < 5; i++ { for range 5 {
tc.client.Append("inbox", makeAppendTime(exampleMsg, received)) tc.client.Append("inbox", makeAppendTime(exampleMsg, received))
} }
tc.client.StoreFlagsSet("1:4", true, `\Deleted`) tc.client.StoreFlagsSet("1:4", true, `\Deleted`)
@ -394,7 +394,7 @@ func TestSearch(t *testing.T) {
// More than 1mb total for literals. // More than 1mb total for literals.
_, err = fmt.Fprintf(tc.client, "x0 uid search") _, err = fmt.Fprintf(tc.client, "x0 uid search")
tcheck(t, err, "write start of uit search") tcheck(t, err, "write start of uit search")
for i := 0; i < 10; i++ { for range 10 {
writeTextLit(100*1024, true) writeTextLit(100*1024, true)
} }
writeTextLit(1, false) writeTextLit(1, false)
@ -402,7 +402,7 @@ func TestSearch(t *testing.T) {
// More than 1000 literals. // More than 1000 literals.
_, err = fmt.Fprintf(tc.client, "x0 uid search") _, err = fmt.Fprintf(tc.client, "x0 uid search")
tcheck(t, err, "write start of uit search") tcheck(t, err, "write start of uit search")
for i := 0; i < 1000; i++ { for range 1000 {
writeTextLit(1, true) writeTextLit(1, true)
} }
writeTextLit(1, false) writeTextLit(1, false)

View File

@ -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. // Now that we have all vanished UIDs, send them over compactly.
if len(vanishedUIDs) > 0 { if len(vanishedUIDs) > 0 {
l := maps.Keys(vanishedUIDs) l := maps.Keys(vanishedUIDs)
sort.Slice(l, func(i, j int) bool { slices.Sort(l)
return l[i] < l[j]
})
// ../rfc/7162:1985 // ../rfc/7162:1985
for _, s := range compactUIDSet(l).Strings(4*1024 - 32) { for _, s := range compactUIDSet(l).Strings(4*1024 - 32) {
c.bwritelinef("* VANISHED (EARLIER) %s", s) 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. // response and interpret it differently than we intended.
// ../rfc/9051:5072 // ../rfc/9051:5072
uids := c.xnumSetUIDs(isUID, nums) uids := c.xnumSetUIDs(isUID, nums)
sort.Slice(uids, func(i, j int) bool { slices.Sort(uids)
return uids[i] < uids[j]
})
uidargs := make([]any, len(uids)) uidargs := make([]any, len(uids))
for i, uid := range uids { for i, uid := range uids {
uidargs[i] = uid 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()) c.bwritelinef("* OK [COPYUID %d %s %s] moved", mbDst.UIDValidity, compactUIDSet(uids).String(), newUIDs.String())
qresync := c.enabled[capQresync] qresync := c.enabled[capQresync]
var vanishedUIDs numSet var vanishedUIDs numSet
for i := 0; i < len(uids); i++ { for i := range uids {
seq := c.xsequence(uids[i]) seq := c.xsequence(uids[i])
c.sequenceRemove(seq, uids[i]) c.sequenceRemove(seq, uids[i])
if qresync { if qresync {
@ -4452,7 +4448,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
origFlags := m.Flags origFlags := m.Flags
m.Flags = m.Flags.Set(mask, flags) m.Flags = m.Flags.Set(mask, flags)
oldKeywords := append([]string{}, m.Keywords...) oldKeywords := slices.Clone(m.Keywords)
if minus { if minus {
m.Keywords, _ = store.RemoveKeywords(m.Keywords, keywords) m.Keywords, _ = store.RemoveKeywords(m.Keywords, keywords)
} else if plus { } else if plus {
@ -4463,7 +4459,7 @@ func (c *conn) cmdxStore(isUID bool, tag, cmd string, p *parser) {
keywordsChanged := func() bool { keywordsChanged := func() bool {
sort.Strings(oldKeywords) sort.Strings(oldKeywords)
n := append([]string{}, m.Keywords...) n := slices.Clone(m.Keywords)
sort.Strings(n) sort.Strings(n)
return !slices.Equal(oldKeywords, 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 { slices.Sort(mnums)
return mnums[i] < mnums[j]
})
set := compactUIDSet(mnums) set := compactUIDSet(mnums)
// ../rfc/7162:2506 // ../rfc/7162:2506
c.writeresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String()) c.writeresultf("%s OK [MODIFIED %s] conditional store did not modify all", tag, set.String())

View File

@ -25,6 +25,7 @@ import (
"github.com/mjl-/mox/mox-" "github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxvar" "github.com/mjl-/mox/moxvar"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"slices"
) )
var ctxbg = context.Background() var ctxbg = context.Background()
@ -210,7 +211,7 @@ func (tc *testconn) xuntagged(exps ...imapclient.Untagged) {
func (tc *testconn) xuntaggedOpt(all bool, exps ...imapclient.Untagged) { func (tc *testconn) xuntaggedOpt(all bool, exps ...imapclient.Untagged) {
tc.t.Helper() tc.t.Helper()
last := append([]imapclient.Untagged{}, tc.lastUntagged...) last := slices.Clone(tc.lastUntagged)
var mismatch any var mismatch any
next: next:
for ei, exp := range exps { for ei, exp := range exps {
@ -572,13 +573,13 @@ func TestState(t *testing.T) {
defer tc.close() defer tc.close()
// Not authenticated, lots of commands not allowed. // 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) tc.transactf("no", "%s", cmd)
} }
// Some commands not allowed when authenticated. // Some commands not allowed when authenticated.
tc.transactf("ok", `login mjl@mox.example "%s"`, password0) 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) tc.transactf("no", "%s", cmd)
} }

View File

@ -70,14 +70,14 @@ func NewBloom(data []byte, k int) (*Bloom, error) {
func (b *Bloom) Add(s string) { func (b *Bloom) Add(s string) {
h := hash([]byte(s), b.w) h := hash([]byte(s), b.w)
for i := 0; i < b.k; i++ { for range b.k {
b.set(h.nextPos()) b.set(h.nextPos())
} }
} }
func (b *Bloom) Has(s string) bool { func (b *Bloom) Has(s string) bool {
h := hash([]byte(s), b.w) h := hash([]byte(s), b.w)
for i := 0; i < b.k; i++ { for range b.k {
if !b.has(h.nextPos()) { if !b.has(h.nextPos()) {
return false return false
} }
@ -96,7 +96,7 @@ func (b *Bloom) Modified() bool {
// Ones returns the number of ones. // Ones returns the number of ones.
func (b *Bloom) Ones() (n int) { func (b *Bloom) Ones() (n int) {
for _, d := range b.data { for _, d := range b.data {
for i := 0; i < 8; i++ { for range 8 {
if d&1 != 0 { if d&1 != 0 {
n++ n++
} }

View File

@ -62,26 +62,26 @@ func TestBloom(t *testing.T) {
func TestBits(t *testing.T) { func TestBits(t *testing.T) {
b := &bits{width: 1, buf: []byte{0xff, 0xff}} b := &bits{width: 1, buf: []byte{0xff, 0xff}}
for i := 0; i < 16; i++ { for range 16 {
if b.nextPos() != 1 { if b.nextPos() != 1 {
t.Fatalf("pos not 1") t.Fatalf("pos not 1")
} }
} }
b = &bits{width: 2, buf: []byte{0xff, 0xff}} b = &bits{width: 2, buf: []byte{0xff, 0xff}}
for i := 0; i < 8; i++ { for range 8 {
if b.nextPos() != 0b11 { if b.nextPos() != 0b11 {
t.Fatalf("pos not 0b11") t.Fatalf("pos not 0b11")
} }
} }
b = &bits{width: 1, buf: []byte{0b10101010, 0b10101010}} b = &bits{width: 1, buf: []byte{0b10101010, 0b10101010}}
for i := 0; i < 16; i++ { for i := range 16 {
if b.nextPos() != ((i + 1) % 2) { if b.nextPos() != ((i + 1) % 2) {
t.Fatalf("bad pos") t.Fatalf("bad pos")
} }
} }
b = &bits{width: 2, buf: []byte{0b10101010, 0b10101010}} b = &bits{width: 2, buf: []byte{0b10101010, 0b10101010}}
for i := 0; i < 8; i++ { for range 8 {
if b.nextPos() != 0b10 { if b.nextPos() != 0b10 {
t.Fatalf("pos not 0b10") t.Fatalf("pos not 0b10")
} }
@ -97,7 +97,7 @@ func TestSet(t *testing.T) {
0b01010101, 0b01010101,
}, },
} }
for i := 0; i < 8; i++ { for i := range 8 {
v := b.has(i) v := b.has(i)
if v != (i%2 == 0) { if v != (i%2 == 0) {
t.Fatalf("bad has") t.Fatalf("bad has")

View File

@ -27,6 +27,7 @@ import (
"github.com/mjl-/mox/message" "github.com/mjl-/mox/message"
"github.com/mjl-/mox/mlog" "github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/moxvar" "github.com/mjl-/mox/moxvar"
"slices"
) )
var ( var (
@ -270,9 +271,7 @@ func (f *Filter) Save() error {
words[i] = w words[i] = w
i++ i++
} }
sort.Slice(words, func(i, j int) bool { slices.Sort(words)
return words[i] < words[j]
})
f.log.Debug("inserting words in junkfilter db", slog.Any("words", len(f.changed))) f.log.Debug("inserting words in junkfilter db", slog.Any("words", len(f.changed)))
// start := time.Now() // 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 { func loadWords(ctx context.Context, db *bstore.DB, l []string, dst map[string]word) error {
sort.Slice(l, func(i, j int) bool { slices.Sort(l)
return l[i] < l[j]
})
err := db.Read(ctx, func(tx *bstore.Tx) error { err := db.Read(ctx, func(tx *bstore.Tx) error {
for _, w := range l { for _, w := range l {
@ -478,14 +475,8 @@ func (f *Filter) ClassifyWords(ctx context.Context, words map[string]struct{}) (
return a.Score > b.Score return a.Score > b.Score
}) })
nham := f.TopWords nham := min(f.TopWords, len(topHam))
if nham > len(topHam) { nspam := min(f.TopWords, len(topSpam))
nham = len(topHam)
}
nspam := f.TopWords
if nspam > len(topSpam) {
nspam = len(topSpam)
}
topHam = topHam[:nham] topHam = topHam[:nham]
topSpam = topSpam[:nspam] topSpam = topSpam[:nspam]

View File

@ -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. // todo: deal with inline elements? they shouldn't cause a word break.
give := func(nbuf []byte) (int, error) { give := func(nbuf []byte) (int, error) {
n := len(buf) n := min(len(buf), len(nbuf))
if n > len(nbuf) {
n = len(nbuf)
}
copy(buf, nbuf[:n]) copy(buf, nbuf[:n])
nbuf = nbuf[n:] nbuf = nbuf[n:]
if len(nbuf) < cap(r.buf) { if len(nbuf) < cap(r.buf) {

View File

@ -1223,7 +1223,7 @@ error too, for reference.
xwriteFile(prefix+".ed25519privatekey.pkcs8.pem", privKeyBufPEM, "private key") xwriteFile(prefix+".ed25519privatekey.pkcs8.pem", privKeyBufPEM, "private key")
xwriteFile(prefix+".certificate.pem", certBufPEM, "certificate") 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") xwriteFile(prefix+".ed25519privatekey-certificate.pem", combinedPEM, "combined private key and certificate")
shabuf := sha256.Sum256(tlsCert.Leaf.RawSubjectPublicKeyInfo) shabuf := sha256.Sum256(tlsCert.Leaf.RawSubjectPublicKeyInfo)
@ -3229,7 +3229,7 @@ func cmdWebapi(c *cmd) {
t := reflect.TypeFor[webapi.Methods]() t := reflect.TypeFor[webapi.Methods]()
methods := map[string]reflect.Type{} methods := map[string]reflect.Type{}
var ml []string var ml []string
for i := 0; i < t.NumMethod(); i++ { for i := range t.NumMethod() {
mt := t.Method(i) mt := t.Method(i)
methods[mt.Name] = mt.Type methods[mt.Name] = mt.Type
ml = append(ml, mt.Name) 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") xcheckf(err, "removing temporary webhook after forwarding autoincrement sequence")
fh.ID -= int64(n) fh.ID -= int64(n)
for i := 0; i < n; i++ { for i := range n {
t0 := now.Add(-time.Duration(i) * time.Second) t0 := now.Add(-time.Duration(i) * time.Second)
last := now.Add(-time.Duration(i/10) * time.Second) last := now.Add(-time.Duration(i/10) * time.Second)
mr := queue.MsgRetired{ mr := queue.MsgRetired{
@ -4037,7 +4037,7 @@ For testing the pagination. Operates directly on queue database.
xcheckf(err, "inserting retired message") xcheckf(err, "inserting retired message")
} }
for i := 0; i < n; i++ { for i := range n {
t0 := now.Add(-time.Duration(i) * time.Second) t0 := now.Add(-time.Duration(i) * time.Second)
last := now.Add(-time.Duration(i/10) * time.Second) last := now.Add(-time.Duration(i/10) * time.Second)
var event string var event string

View File

@ -27,6 +27,7 @@ import (
"github.com/mjl-/mox/mlog" "github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"slices"
) )
// Pedantic enables stricter parsing. // Pedantic enables stricter parsing.
@ -848,11 +849,9 @@ func (b *bufAt) maxLineLength() int {
// ensure makes sure b.nbuf is up to maxLineLength, unless eof is encountered. // ensure makes sure b.nbuf is up to maxLineLength, unless eof is encountered.
func (b *bufAt) ensure() error { func (b *bufAt) ensure() error {
for _, c := range b.buf[:b.nbuf] { if slices.Contains(b.buf[:b.nbuf], '\n') {
if c == '\n' {
return nil return nil
} }
}
if b.scratch == nil { if b.scratch == nil {
b.scratch = make([]byte, b.maxLineLength()) b.scratch = make([]byte, b.maxLineLength())
} }
@ -1014,10 +1013,7 @@ func (b *boundReader) Read(buf []byte) (count int, rerr error) {
for { for {
// Read data from earlier line. // Read data from earlier line.
if b.nbuf > 0 { if b.nbuf > 0 {
n := b.nbuf n := min(b.nbuf, len(buf))
if n > len(buf) {
n = len(buf)
}
copy(buf, b.buf[:n]) copy(buf, b.buf[:n])
copy(b.buf, b.buf[n:]) copy(b.buf, b.buf[n:])
buf = buf[n:] buf = buf[n:]
@ -1046,10 +1042,7 @@ func (b *boundReader) Read(buf []byte) (count int, rerr error) {
return count, err return count, err
} }
if len(b.crlf) > 0 { if len(b.crlf) > 0 {
n := len(b.crlf) n := min(len(b.crlf), len(buf))
if n > len(buf) {
n = len(buf)
}
copy(buf, b.crlf[:n]) copy(buf, b.crlf[:n])
count += n count += n
buf = buf[n:] buf = buf[n:]

View File

@ -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 // Update w.tail after having written. Regardless of error, writers can't expect
// subsequent writes to work again properly anyway. // subsequent writes to work again properly anyway.
defer func() { defer func() {
n := len(buf) n := min(len(buf), 3)
if n > 3 {
n = 3
}
copy(w.tail[:], w.tail[n:]) copy(w.tail[:], w.tail[n:])
copy(w.tail[3-n:], buf[len(buf)-n:]) copy(w.tail[3-n:], buf[len(buf)-n:])
}() }()

View File

@ -28,6 +28,7 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"slices"
) )
var noctx = context.Background() var noctx = context.Background()
@ -452,7 +453,7 @@ func stringValue(iscid, nested bool, v any) string {
} }
b := &strings.Builder{} b := &strings.Builder{}
b.WriteString("[") b.WriteString("[")
for i := 0; i < n; i++ { for i := range n {
if i > 0 { if i > 0 {
b.WriteString(";") 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, // 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 // but will try again with those fields if we otherwise would otherwise log an
// empty string. // empty string.
for j := 0; j < 2; j++ { for j := range 2 {
first := true first := true
b := &strings.Builder{} b := &strings.Builder{}
for i := 0; i < n; i++ { for i := range n {
fv := rv.Field(i) fv := rv.Field(i)
if !t.Field(i).IsExported() { if !t.Field(i).IsExported() {
continue continue
@ -636,7 +637,7 @@ func (w *errWriter) Write(buf []byte) (int, error) {
func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler {
nh := *h nh := *h
if h.Attrs != nil { if h.Attrs != nil {
nh.Attrs = append([]slog.Attr{}, h.Attrs...) nh.Attrs = slices.Clone(h.Attrs)
} }
nh.Attrs = append(nh.Attrs, attrs...) nh.Attrs = append(nh.Attrs, attrs...)
return &nh return &nh
@ -654,7 +655,7 @@ func (h *handler) WithGroup(name string) slog.Handler {
func (h *handler) WithPkg(pkg string) *handler { func (h *handler) WithPkg(pkg string) *handler {
nh := *h nh := *h
if nh.Pkgs != nil { if nh.Pkgs != nil {
nh.Pkgs = append([]string{}, nh.Pkgs...) nh.Pkgs = slices.Clone(nh.Pkgs)
} }
nh.Pkgs = append(nh.Pkgs, pkg) nh.Pkgs = append(nh.Pkgs, pkg)
return &nh return &nh

View File

@ -25,7 +25,6 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices" "slices"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -199,9 +198,7 @@ func (c *Config) Domains() (l []string) {
l = append(l, name) l = append(l, name)
} }
}) })
sort.Slice(l, func(i, j int) bool { slices.Sort(l)
return l[i] < l[j]
})
return l return l
} }

View File

@ -9,7 +9,7 @@ import (
func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) { func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) {
switch rv.Kind() { switch rv.Kind() {
case reflect.Struct: case reflect.Struct:
for i := 0; i < rv.NumField(); i++ { for i := range rv.NumField() {
if !rv.Type().Field(i).IsExported() { if !rv.Type().Field(i).IsExported() {
continue continue
} }
@ -18,7 +18,7 @@ func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) {
if ch && !rv.CanSet() { if ch && !rv.CanSet() {
// Make struct settable. // Make struct settable.
nrv := reflect.New(rv.Type()).Elem() 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)) nrv.Field(j).Set(rv.Field(j))
} }
rv = nrv rv = nrv
@ -34,7 +34,7 @@ func FillNil(rv reflect.Value) (nv reflect.Value, changed bool) {
return reflect.MakeSlice(rv.Type(), 0, 0), true return reflect.MakeSlice(rv.Type(), 0, 0), true
} }
n := rv.Len() n := rv.Len()
for i := 0; i < n; i++ { for i := range n {
rve := rv.Index(i) rve := rv.Index(i)
nrv, ch := FillNil(rve) nrv, ch := FillNil(rve)
if ch { if ch {
@ -90,7 +90,7 @@ func FillExample(seen []reflect.Type, rv reflect.Value) reflect.Value {
switch rv.Kind() { switch rv.Kind() {
case reflect.Struct: case reflect.Struct:
for i := 0; i < rv.NumField(); i++ { for i := range rv.NumField() {
if !rvt.Field(i).IsExported() { if !rvt.Field(i).IsExported() {
continue continue
} }

View File

@ -8,7 +8,7 @@ func GeneratePassword() string {
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_;:,<.>/" chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_;:,<.>/"
s := "" s := ""
buf := make([]byte, 1) buf := make([]byte, 1)
for i := 0; i < 12; i++ { for range 12 {
for { for {
cryptorand.Read(buf) cryptorand.Read(buf)
i := int(buf[0]) i := int(buf[0])

View File

@ -9,10 +9,7 @@ func TXTStrings(s string) string {
r := "(\n" r := "(\n"
for len(s) > 0 { for len(s) > 0 {
n := len(s) n := min(len(s), 100)
if n > 100 {
n = 100
}
if r != "" { if r != "" {
r += " " r += " "
} }

View File

@ -39,10 +39,7 @@ type lineWrapper struct {
func (lw *lineWrapper) Write(buf []byte) (int, error) { func (lw *lineWrapper) Write(buf []byte) (int, error) {
wrote := 0 wrote := 0
for len(buf) > 0 { for len(buf) > 0 {
n := 78 - lw.n n := min(78-lw.n, len(buf))
if n > len(buf) {
n = len(buf)
}
nn, err := lw.w.Write(buf[:n]) nn, err := lw.w.Write(buf[:n])
if nn > 0 { if nn > 0 {
wrote += nn wrote += nn

View File

@ -54,7 +54,7 @@ func (b *Bufpool) put(log mlog.Log, buf []byte, n int) {
return return
} }
for i := 0; i < n; i++ { for i := range n {
buf[i] = 0 buf[i] = 0
} }
select { select {

View File

@ -15,7 +15,7 @@ func TestBufpool(t *testing.T) {
bp := NewBufpool(1, 8) bp := NewBufpool(1, 8)
a := bp.get() a := bp.get()
b := bp.get() b := bp.get()
for i := 0; i < len(a); i++ { for i := range a {
a[i] = 1 a[i] = 1
} }
log := mlog.New("moxio", nil) log := mlog.New("moxio", nil)

View File

@ -50,7 +50,7 @@ func NewWorkQueue[T, R any](procs, size int, preparer func(in, out chan Work[T,
} }
wq.wg.Add(procs) wq.wg.Add(procs)
for i := 0; i < procs; i++ { for range procs {
go func() { go func() {
defer wq.wg.Done() defer wq.wg.Done()
preparer(wq.work, wq.done) preparer(wq.work, wq.done)

View File

@ -429,7 +429,7 @@ func TestFromIDIncomingDelivery(t *testing.T) {
tcheck(t, err, "get added hook") tcheck(t, err, "get added hook")
h.URL = hs.URL h.URL = hs.URL
handler = handleError handler = handleError
for i := 0; i < len(hookIntervals); i++ { for i := range hookIntervals {
hookDeliver(pkglog, h) hookDeliver(pkglog, h)
<-hookDeliveryResults <-hookDeliveryResults
err := DB.Get(ctxbg, &h) err := DB.Get(ctxbg, &h)
@ -557,7 +557,7 @@ func TestHookListFilterSort(t *testing.T) {
// Descending by submitted,id. // Descending by submitted,id.
l, err = HookList(ctxbg, HookFilter{}, HookSort{Field: "Submitted"}) l, err = HookList(ctxbg, HookFilter{}, HookSort{Field: "Submitted"})
tcheck(t, err, "list") tcheck(t, err, "list")
ll := append(append([]Hook{}, hlrev[1:]...), hl[5]) ll := append(slices.Clone(hlrev[1:]), hl[5])
tcompare(t, l, ll) tcompare(t, l, ll)
// Filter by all fields to get a single. // Filter by all fields to get a single.

View File

@ -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 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) backoff *= time.Duration(2)
} }
m0.Attempts++ m0.Attempts++

View File

@ -310,10 +310,10 @@ func TestQueue(t *testing.T) {
writeline("250-" + ext) writeline("250-" + ext)
} }
writeline("250 pipelining") writeline("250 pipelining")
for tx := 0; tx < ntx; tx++ { for range ntx {
readline("mail") readline("mail")
writeline("250 ok") writeline("250 ok")
for i := 0; i < rcpts; i++ { for i := range rcpts {
readline("rcpt") readline("rcpt")
if onercpt && i > 0 { if onercpt && i > 0 {
writeline("552 ok") writeline("552 ok")
@ -462,7 +462,7 @@ func TestQueue(t *testing.T) {
fmt.Fprintf(server, "235 2.7.0 auth ok\r\n") fmt.Fprintf(server, "235 2.7.0 auth ok\r\n")
br.ReadString('\n') // Should be MAIL FROM. br.ReadString('\n') // Should be MAIL FROM.
fmt.Fprintf(server, "250 ok\r\n") fmt.Fprintf(server, "250 ok\r\n")
for i := 0; i < nrcpt; i++ { for range nrcpt {
br.ReadString('\n') // Should be RCPT TO. br.ReadString('\n') // Should be RCPT TO.
fmt.Fprintf(server, "250 ok\r\n") fmt.Fprintf(server, "250 ok\r\n")
} }
@ -520,7 +520,7 @@ func TestQueue(t *testing.T) {
// Wait for all results. // Wait for all results.
timer.Reset(time.Second) timer.Reset(time.Second)
for i := 0; i < nresults; i++ { for range nresults {
select { select {
case <-deliveryResults: case <-deliveryResults:
case <-timer.C: case <-timer.C:
@ -1331,7 +1331,7 @@ func TestListFilterSort(t *testing.T) {
// Descending by queued,id. // Descending by queued,id.
l, err = List(ctxbg, Filter{}, Sort{Field: "Queued"}) l, err = List(ctxbg, Filter{}, Sort{Field: "Queued"})
tcheck(t, err, "list messages") 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) tcompare(t, l, ql)
// Filter by all fields to get a single. // Filter by all fields to get a single.

View File

@ -39,6 +39,7 @@ import (
"github.com/mjl-/mox/rdap" "github.com/mjl-/mox/rdap"
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store" "github.com/mjl-/mox/store"
"slices"
) )
//go:embed mox.service //go:embed mox.service
@ -344,9 +345,7 @@ Troubleshooting hints:
for k := range names { for k := range names {
nameList = append(nameList, strings.TrimRight(k, ".")) nameList = append(nameList, strings.TrimRight(k, "."))
} }
sort.Slice(nameList, func(i, j int) bool { slices.Sort(nameList)
return nameList[i] < nameList[j]
})
if len(nameList) == 0 { if len(nameList) == 0 {
dnshostname, err = dns.ParseDomain(hostnameStr + "." + domain.Name()) dnshostname, err = dns.ParseDomain(hostnameStr + "." + domain.Name())
if err != nil { if err != nil {
@ -534,7 +533,7 @@ messages over SMTP.
fmt.Printf("\nWARNING: %s", fmt.Sprintf(format, args...)) fmt.Printf("\nWARNING: %s", fmt.Sprintf(format, args...))
warned = true warned = true
} }
for i := 0; i < len(ips); i++ { for range ips {
r := <-results r := <-results
if r.Err != nil { if r.Err != nil {
warnf("looking up reverse name for %s: %v", r.IP, r.Err) warnf("looking up reverse name for %s: %v", r.IP, r.Err)

View File

@ -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 l.WindowLimits[i].Counts = pl.Counts
} }
for j := 0; j < 3; j++ { for j := range 3 {
if i == 0 { if i == 0 {
l.ipmasked[j] = l.maskIP(j, ip) 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. // Finally record.
for _, pl := range l.WindowLimits { for _, pl := range l.WindowLimits {
for j := 0; j < 3; j++ { for j := range 3 {
pl.Counts[struct { pl.Counts[struct {
Index uint8 Index uint8
IPMasked [16]byte IPMasked [16]byte
@ -90,7 +90,7 @@ func (l *Limiter) Reset(ip net.IP, tm time.Time) {
defer l.Unlock() defer l.Unlock()
// Prepare masked ip's. // Prepare masked ip's.
for i := 0; i < 3; i++ { for i := range 3 {
l.ipmasked[i] = l.maskIP(i, ip) l.ipmasked[i] = l.maskIP(i, ip)
} }
@ -100,7 +100,7 @@ func (l *Limiter) Reset(ip net.IP, tm time.Time) {
continue continue
} }
var n int64 var n int64
for j := 0; j < 3; j++ { for j := range 3 {
k := struct { k := struct {
Index uint8 Index uint8
IPMasked [16]byte IPMasked [16]byte

View File

@ -77,7 +77,7 @@ func (a *clientPlain) Next(fromServer []byte) (toServer []byte, last bool, rerr
defer func() { a.step++ }() defer func() { a.step++ }()
switch a.step { switch a.step {
case 0: 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: default:
return nil, false, fmt.Errorf("invalid step %d", a.step) 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)) opadh.Write(ipadh.Sum(nil))
// ../rfc/2195:88 // ../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: default:
return nil, false, fmt.Errorf("invalid step %d", a.step) return nil, false, fmt.Errorf("invalid step %d", a.step)

View File

@ -85,7 +85,7 @@ func monitorDNSBL(log mlog.Log) {
last = time.Now() last = time.Now()
// Gather zones. // Gather zones.
zones := append([]dns.Domain{}, publicListener.SMTP.DNSBLZones...) zones := slices.Clone(publicListener.SMTP.DNSBLZones)
conf := mox.Conf.DynamicConfig() conf := mox.Conf.DynamicConfig()
for _, zone := range conf.MonitorDNSBLZones { for _, zone := range conf.MonitorDNSBLZones {
if !slices.Contains(zones, zone) { if !slices.Contains(zones, zone) {

View File

@ -164,10 +164,7 @@ func (r *DataReader) Read(p []byte) (int, error) {
// Reject "[^\r]\n.\n" and "[^\r]\n.\r\n" // Reject "[^\r]\n.\n" and "[^\r]\n.\r\n"
r.badcrlf = true r.badcrlf = true
} }
n := len(r.buf) n := min(len(r.buf), len(p))
if n > len(p) {
n = len(p)
}
copy(p, r.buf[:n]) copy(p, r.buf[:n])
if n == 1 { if n == 1 {
r.plast, r.last = r.last, r.buf[0] r.plast, r.last = r.last, r.buf[0]

View File

@ -1243,7 +1243,7 @@ func (c *Client) DeliverMultiple(ctx context.Context, mailFrom string, rcptTo []
// Read responses to RCPT TO. // Read responses to RCPT TO.
rcptResps = make([]Response, len(rcptTo)) rcptResps = make([]Response, len(rcptTo))
nok := 0 nok := 0
for i := 0; i < len(rcptTo); i++ { for i := range rcptTo {
code, secode, firstLine, moreLines, err := c.read() code, secode, firstLine, moreLines, err := c.read()
// 552 should be treated as temporary historically, ../rfc/5321:3576 // 552 should be treated as temporary historically, ../rfc/5321:3576
permanent := code/100 == 5 && code != smtp.C552MailboxFull permanent := code/100 == 5 && code != smtp.C552MailboxFull

View File

@ -273,7 +273,7 @@ func TestClient(t *testing.T) {
if n == 0 { if n == 0 {
n = 1 n = 1
} }
for i := 0; i < n; i++ { for i := range n {
readline("RCPT TO:") readline("RCPT TO:")
resp := "250 ok" resp := "250 ok"
if i < len(opts.resps) { if i < len(opts.resps) {
@ -293,7 +293,7 @@ func TestClient(t *testing.T) {
readline("MAIL FROM:") readline("MAIL FROM:")
writeline("250 ok") writeline("250 ok")
for i := 0; i < n; i++ { for i := range n {
readline("RCPT TO:") readline("RCPT TO:")
resp := "250 ok" resp := "250 ok"
if i < len(opts.resps) { if i < len(opts.resps) {
@ -366,7 +366,7 @@ func TestClient(t *testing.T) {
}() }()
var errs []error var errs []error
for i := 0; i < 2; i++ { for range 2 {
err := <-result err := <-result
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
@ -907,7 +907,7 @@ func run(t *testing.T, server func(s xserver), client func(conn net.Conn)) {
client(clientConn) client(clientConn)
}() }()
var errs []error var errs []error
for i := 0; i < 2; i++ { for range 2 {
err := <-result err := <-result
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)

View File

@ -104,7 +104,7 @@ func TestGatherDestinations(t *testing.T) {
var zerodom dns.Domain var zerodom dns.Domain
for i := 0; i < 2; i++ { for i := range 2 {
authic := i == 1 authic := i == 1
resolver.AllAuthentic = authic resolver.AllAuthentic = authic
// Basic with simple MX. // Basic with simple MX.
@ -187,7 +187,7 @@ func TestGatherIPs(t *testing.T) {
return r return r
} }
for i := 0; i < 2; i++ { for i := range 2 {
authic := i == 1 authic := i == 1
resolver.AllAuthentic = authic resolver.AllAuthentic = authic

View File

@ -3203,7 +3203,7 @@ func (c *conn) deliver(ctx context.Context, recvHdrFor func(string) string, msgW
rcptDMARCMethod.Comment += "override " + strings.Join(dmarcOverrides, ",") rcptDMARCMethod.Comment += "override " + strings.Join(dmarcOverrides, ",")
} }
rcptAuthResults := authResults rcptAuthResults := authResults
rcptAuthResults.Methods = append([]message.AuthMethod{}, authResults.Methods...) rcptAuthResults.Methods = slices.Clone(authResults.Methods)
rcptAuthResults.Methods = append(rcptAuthResults.Methods, rcptDMARCMethod) rcptAuthResults.Methods = append(rcptAuthResults.Methods, rcptDMARCMethod)
// Prepend reason as message header, for easy viewing in mail clients. // Prepend reason as message header, for easy viewing in mail clients.

View File

@ -727,7 +727,7 @@ func TestSpam(t *testing.T) {
Flags: store.Flags{Seen: true, Junk: true}, Flags: store.Flags{Seen: true, Junk: true},
Size: int64(len(deliverMessage)), Size: int64(len(deliverMessage)),
} }
for i := 0; i < 3; i++ { for range 3 {
nm := m nm := m
tinsertmsg(t, ts.acc, "Inbox", &nm, deliverMessage) tinsertmsg(t, ts.acc, "Inbox", &nm, deliverMessage)
nm = m nm = m
@ -868,7 +868,7 @@ happens to come from forwarding mail server.
mailFrom = "remote@bad.example" 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) err := client.Deliver(ctxbg, mailFrom, rcptTo, int64(len(msgBad)), strings.NewReader(msgBad), false, false, false)
tcheck(t, err, "deliver message") 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 // We need at least 50 ham messages for the junk filter to become significant. We
// offset it with negative messages for mediocre score. // offset it with negative messages for mediocre score.
for i := 0; i < 50; i++ { for range 50 {
nm := m nm := m
nm.Junk = true nm.Junk = true
tinsertmsg(t, ts.acc, "Archive", &nm, deliverMessage) tinsertmsg(t, ts.acc, "Archive", &nm, deliverMessage)

View File

@ -806,7 +806,7 @@ func expandDomainSpec(ctx context.Context, resolver dns.Resolver, domainSpec str
if reverse { if reverse {
nt := len(t) nt := len(t)
h := nt / 2 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] t[i], t[nt-1-i] = t[nt-1-i], t[i]
} }
} }

View File

@ -3198,7 +3198,7 @@ func RemoveKeywords(l, remove []string) ([]string, bool) {
for _, k := range remove { for _, k := range remove {
if i := slices.Index(l, k); i >= 0 { if i := slices.Index(l, k); i >= 0 {
if !copied { if !copied {
l = append([]string{}, l...) l = slices.Clone(l)
copied = true copied = true
} }
copy(l[i:], l[i+1:]) copy(l[i:], l[i+1:])
@ -3219,7 +3219,7 @@ func MergeKeywords(l, add []string) ([]string, bool) {
for _, k := range add { for _, k := range add {
if !slices.Contains(l, k) { if !slices.Contains(l, k) {
if !copied { if !copied {
l = append([]string{}, l...) l = slices.Clone(l)
copied = true copied = true
} }
l = append(l, k) l = append(l, k)

View File

@ -234,14 +234,14 @@ func TestMailbox(t *testing.T) {
}) })
// Run the auth tests twice for possible cache effects. // 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) _, _, err := OpenEmailAuth(log, "mjl@mox.example", "bogus", false)
if err != ErrUnknownCredentials { if err != ErrUnknownCredentials {
t.Fatalf("got %v, expected ErrUnknownCredentials", err) 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) acc2, _, err := OpenEmailAuth(log, "mjl@mox.example", "testtest", false)
tcheck(t, err, "open for email with auth") tcheck(t, err, "open for email with auth")
err = acc2.Close() err = acc2.Close()

View File

@ -99,7 +99,7 @@ func TestLoginAttempt(t *testing.T) {
// Insert 3 failing entries. Then add another and see we still have 3. // Insert 3 failing entries. Then add another and see we still have 3.
loginAttemptsMaxPerAccount = 3 loginAttemptsMaxPerAccount = 3
for i := 0; i < loginAttemptsMaxPerAccount; i++ { for i := range loginAttemptsMaxPerAccount {
a := a2 a := a2
a.UserAgent = fmt.Sprintf("%d", i) a.UserAgent = fmt.Sprintf("%d", i)
LoginAttemptAdd(ctxbg, pkglog, a) LoginAttemptAdd(ctxbg, pkglog, a)

View File

@ -696,7 +696,7 @@ func composeMessage(ctx context.Context, log mlog.Log, mf *os.File, policyDomain
selectors := mox.DKIMSelectors(confDKIM) selectors := mox.DKIMSelectors(confDKIM)
for i, sel := range selectors { for i, sel := range selectors {
// Also sign the TLS-Report headers. ../rfc/8460:940 // 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 selectors[i] = sel
} }

View File

@ -20,6 +20,7 @@ import (
"github.com/mjl-/mox/queue" "github.com/mjl-/mox/queue"
"github.com/mjl-/mox/tlsrpt" "github.com/mjl-/mox/tlsrpt"
"github.com/mjl-/mox/tlsrptdb" "github.com/mjl-/mox/tlsrptdb"
"slices"
) )
var ctxbg = context.Background() var ctxbg = context.Background()
@ -423,7 +424,7 @@ func TestSendReports(t *testing.T) {
tcheckf(t, err, "read report message") tcheckf(t, err, "read report message")
p := fmt.Sprintf("../testdata/tlsrptsend/data/report%d.eml", index) p := fmt.Sprintf("../testdata/tlsrptsend/data/report%d.eml", index)
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") tcheckf(t, err, "write report message")
reportJSON, err := tlsrpt.ParseMessage(log.Logger, msgFile) reportJSON, err := tlsrpt.ParseMessage(log.Logger, msgFile)

View File

@ -657,7 +657,7 @@ EOF
} }
} }
r.IPRev.IPNames = map[string][]string{} r.IPRev.IPNames = map[string][]string{}
for i := 0; i < n; i++ { for range n {
lr := <-results lr := <-results
host, addrs, ip, err := lr.Host, lr.Addrs, lr.IP, lr.Err host, addrs, ip, err := lr.Host, lr.Addrs, lr.IP, lr.Err
if err != nil { 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. // Accounts returns the names of all configured and all disabled accounts.
func (Admin) Accounts(ctx context.Context) (all, disabled []string) { func (Admin) Accounts(ctx context.Context) (all, disabled []string) {
all, disabled = mox.Conf.AccountsDisabled() all, disabled = mox.Conf.AccountsDisabled()
sort.Slice(all, func(i, j int) bool { slices.Sort(all)
return all[i] < all[j]
})
return 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) { 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? // todo: check health before using dnsbl?
using = mox.Conf.Static.Listeners["public"].SMTP.DNSBLZones using = mox.Conf.Static.Listeners["public"].SMTP.DNSBLZones
zones := append([]dns.Domain{}, using...) zones := slices.Clone(using)
conf := mox.Conf.DynamicConfig() conf := mox.Conf.DynamicConfig()
for _, zone := range conf.MonitorDNSBLZones { for _, zone := range conf.MonitorDNSBLZones {
if !slices.Contains(zones, zone) { if !slices.Contains(zones, zone) {

View File

@ -202,7 +202,7 @@ func init() {
var methods []string var methods []string
mt := reflect.TypeFor[webapi.Methods]() mt := reflect.TypeFor[webapi.Methods]()
n := mt.NumMethod() n := mt.NumMethod()
for i := 0; i < n; i++ { for i := range n {
methods = append(methods, mt.Method(i).Name) methods = append(methods, mt.Method(i).Name)
} }
docsIndexTmpl := htmltemplate.Must(htmltemplate.New("index").Parse(`<!doctype html> docsIndexTmpl := htmltemplate.Must(htmltemplate.New("index").Parse(`<!doctype html>
@ -877,10 +877,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
for len(base64Data) > 0 { for len(base64Data) > 0 {
line := base64Data line := base64Data
n := len(line) n := min(len(line), 78)
if n > 78 {
n = 78
}
line, base64Data = base64Data[:n], base64Data[n:] line, base64Data = base64Data[:n], base64Data[n:]
_, err := p.Write([]byte(line)) _, err := p.Write([]byte(line))
xcheckf(err, "writing attachment") xcheckf(err, "writing attachment")

View File

@ -132,11 +132,11 @@ func TestServer(t *testing.T) {
testHTTP("PUT", "/v0/Send", http.StatusMethodNotAllowed, "") testHTTP("PUT", "/v0/Send", http.StatusMethodNotAllowed, "")
testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "") testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "")
for i := 0; i < 11; i++ { for range 11 {
// Missing auth doesn't trigger auth rate limiter. // Missing auth doesn't trigger auth rate limiter.
testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "") testHTTP("POST", "/v0/Send", http.StatusUnauthorized, "")
} }
for i := 0; i < 21; i++ { for i := range 21 {
// Bad auth does. // Bad auth does.
expCode := http.StatusUnauthorized expCode := http.StatusUnauthorized
tooMany := i >= 10 tooMany := i >= 10

View File

@ -826,10 +826,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
for len(base64Data) > 0 { for len(base64Data) > 0 {
line := base64Data line := base64Data
n := len(line) n := min(len(line), 78)
if n > 78 {
n = 78
}
line, base64Data = base64Data[:n], base64Data[n:] line, base64Data = base64Data[:n], base64Data[n:]
_, err := ap.Write(line) _, err := ap.Write(line)
xcheckf(ctx, err, "writing attachment") xcheckf(ctx, err, "writing attachment")

View File

@ -143,7 +143,7 @@ func TestAPI(t *testing.T) {
testLogin("bad@bad.example", pw0, "user:loginFailed") testLogin("bad@bad.example", pw0, "user:loginFailed")
} }
// Ensure rate limiter is triggered, also for slow tests. // 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:loginFailed", "user:error")
} }
testLogin("bad@bad.example", pw0, "user:error") testLogin("bad@bad.example", pw0, "user:error")

View File

@ -20,6 +20,7 @@ import (
"github.com/mjl-/mox/moxio" "github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/smtp" "github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store" "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. // 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 { if len(l) >= 5 {
return false return false
} }
for _, line := range l { return !slices.Contains(l, "")
if line == "" {
return false
}
}
return true
} }
result := "" result := ""
@ -287,7 +283,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
if mt == "MULTIPART/SIGNED" && i >= 1 { if mt == "MULTIPART/SIGNED" && i >= 1 {
continue continue
} }
usePart(sp, i, &p, append(append([]int{}, path...), i), newParentMixed) usePart(sp, i, &p, append(slices.Clone(path), i), newParentMixed)
} }
switch mt { switch mt {
case "TEXT/PLAIN", "/": case "TEXT/PLAIN", "/":
@ -313,7 +309,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
return return
} }
pm.Texts = append(pm.Texts, string(buf)) 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 == "" { if msgitem && pm.firstLine == "" {
pm.firstLine, rerr = formatFirstLine(p.ReaderUTF8OrBinary()) 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": case "TEXT/HTML":
pm.HasHTML = true pm.HasHTML = true
if full && pm.HTMLPath == nil { if full && pm.HTMLPath == nil {
pm.HTMLPath = append([]int{}, path...) pm.HTMLPath = slices.Clone(path)
} }
default: default:
@ -353,7 +349,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
return return
} }
pm.Texts = append(pm.Texts, string(buf)) pm.Texts = append(pm.Texts, string(buf))
pm.TextPaths = append(pm.TextPaths, append([]int{}, path...)) pm.TextPaths = append(pm.TextPaths, slices.Clone(path))
} }
return return
} }
@ -365,7 +361,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
return return
} }
pm.Texts = append(pm.Texts, string(buf)) pm.Texts = append(pm.Texts, string(buf))
pm.TextPaths = append(pm.TextPaths, append([]int{}, path...)) pm.TextPaths = append(pm.TextPaths, slices.Clone(path))
} }
return return
} }

View File

@ -1971,7 +1971,7 @@ func (q Query) envFilterFn(log mlog.Log, state *msgState) func(m store.Message)
return false return false
} }
if len(filterTo) > 0 || len(notFilterTo) > 0 { 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) { if len(filterTo) > 0 && !contains(filterTo, to, true) {
return false return false
} }

View File

@ -99,7 +99,7 @@ func TestView(t *testing.T) {
// Token // Token
tokens := []string{} tokens := []string{}
for i := 0; i < 20; i++ { for range 20 {
tokens = append(tokens, api.Token(ctx)) tokens = append(tokens, api.Token(ctx))
} }
// Only last 10 tokens are still valid and around, checked below. // Only last 10 tokens are still valid and around, checked below.