1
1
Fork 0
mirror of https://github.com/mjl-/mox.git synced 2025-04-21 21:40:01 +03:00

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/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 {

View file

@ -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{

11
ctl.go
View file

@ -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

View file

@ -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]

View file

@ -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})

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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":

View file

@ -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:

View file

@ -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 {

View file

@ -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.

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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)

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.
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())

View file

@ -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)
}

View file

@ -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++
}

View file

@ -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")

View file

@ -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]

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.
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) {

View file

@ -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

View file

@ -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:]

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
// 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:])
}()

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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])

View file

@ -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 += " "
}

View file

@ -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

View file

@ -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 {

View file

@ -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)

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)
for i := 0; i < procs; i++ {
for range procs {
go func() {
defer wq.wg.Done()
preparer(wq.work, wq.done)

View file

@ -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.

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
for i := 0; i < m0.Attempts; i++ {
for range m0.Attempts {
backoff *= time.Duration(2)
}
m0.Attempts++

View file

@ -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.

View file

@ -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)

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
}
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

View file

@ -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)

View file

@ -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) {

View file

@ -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]

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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]
}
}

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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
}

View file

@ -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)

View file

@ -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) {

View file

@ -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(`<!doctype html>
@ -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")

View file

@ -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

View file

@ -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")

View file

@ -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")

View file

@ -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
}

View file

@ -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
}

View file

@ -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.