diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2cbf895 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,28 @@ +linters: + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - errname + - gocheckcompilerdirectives + - gosec + # - maintidx + - misspell + - nilnil + - noctx + - nolintlint + - predeclared + - reassign + - sloglint + - spancheck + - unconvert + - unparam + - usestdlibvars + +linters-settings: + gosec: + excludes: + - G404 # Use of weak random number generator (math/rand instead of crypto/rand) + - G115 # integer overflow conversion diff --git a/Makefile b/Makefile index 7b2f4eb..f15a2b9 100644 --- a/Makefile +++ b/Makefile @@ -67,3 +67,19 @@ nightly: $(GOBIN)/goreleaser generate-install: @echo "[*] $@" godownloader .godownloader.yml -r creativeprojects/imap -o install.sh + +.PHONY: lint +lint: + @echo "[*] $@" + GOOS=darwin golangci-lint run + GOOS=linux golangci-lint run + GOOS=windows golangci-lint run + +.PHONY: fix +fix: + @echo "[*] $@" + $(GOCMD) mod tidy + $(GOCMD) fix ./... + GOOS=darwin golangci-lint run --fix + GOOS=linux golangci-lint run --fix + GOOS=windows golangci-lint run --fix diff --git a/cmd/copy.go b/cmd/copy.go index 0a58a6e..2079ad8 100644 --- a/cmd/copy.go +++ b/cmd/copy.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "errors" "fmt" "log" @@ -13,7 +14,6 @@ import ( "github.com/creativeprojects/imap/term" "github.com/pterm/pterm" "github.com/spf13/cobra" - "golang.org/x/net/context" ) var copyCmd = &cobra.Command{ diff --git a/go.mod b/go.mod index 6114b34..6740a56 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9 github.com/emersion/go-imap-uidplus v0.0.0-20200503180755-e75854c361e9 - github.com/emersion/go-maildir v0.5.0 + github.com/emersion/go-maildir v0.6.0 github.com/pterm/pterm v0.12.79 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index 308f87b..70a58be 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9 h1:7dmV1 github.com/emersion/go-imap-compress v0.0.0-20201103190257-14809af1d1b9/go.mod h1:2Ro1PbmiqYiRe5Ct2sGR5hHaKSVHeRpVZwXx8vyYt98= github.com/emersion/go-imap-uidplus v0.0.0-20200503180755-e75854c361e9 h1:2Kbw3iu7fFeSso6RWIArVNUj1VGG2PvjetnPUW7bnis= github.com/emersion/go-imap-uidplus v0.0.0-20200503180755-e75854c361e9/go.mod h1:GfiSiw/du0221I3Cf4F0DqX3Bv5Xe580gIIATrQtnJg= -github.com/emersion/go-maildir v0.5.0 h1:RhmSIKAvdSbKCicpe8lrlihjS/xLx0CzWIWJZQQyG4k= -github.com/emersion/go-maildir v0.5.0/go.mod h1:Wpgtt9EOIJWe++WKa+JRvDwv+qIV7MeFdvZu/VbsXN4= +github.com/emersion/go-maildir v0.6.0 h1:MPx2RSS1Xq8j1cNOzfq7YyF+5Leoeif1XqSeuytdET8= +github.com/emersion/go-maildir v0.6.0/go.mod h1:Wpgtt9EOIJWe++WKa+JRvDwv+qIV7MeFdvZu/VbsXN4= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-message v0.18.1 h1:tfTxIoXFSFRwWaZsgnqS1DSZuGpYGzSmCZD8SK3QA2E= github.com/emersion/go-message v0.18.1/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= @@ -188,8 +188,6 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/lib/email_generator.go b/lib/email_generator.go index 037bbac..9bb847c 100644 --- a/lib/email_generator.go +++ b/lib/email_generator.go @@ -48,7 +48,7 @@ func GenerateDateFrom(from time.Time) time.Time { } // GenerateFlags generates an random number of message flags -func GenerateFlags(max int) []string { +func GenerateFlags(maxInt int) []string { available := []string{ imap.SeenFlag, imap.AnsweredFlag, @@ -58,7 +58,7 @@ func GenerateFlags(max int) []string { imap.RecentFlag, } picked := make([]bool, len(available)) - count := seededRand.Intn(max) + count := seededRand.Intn(maxInt) flags := make([]string, count) for i := 0; i < count; i++ { for { diff --git a/lib/email_generator_test.go b/lib/email_generator_test.go index a575907..cd4a1e9 100644 --- a/lib/email_generator_test.go +++ b/lib/email_generator_test.go @@ -20,11 +20,11 @@ func TestDateGenerator(t *testing.T) { } func TestGenerateFlags(t *testing.T) { - max := 5 + maxInt := 5 for i := 0; i < 100000; i++ { - flags := GenerateFlags(max) + flags := GenerateFlags(maxInt) require.NotNil(t, flags) require.GreaterOrEqual(t, len(flags), 0) - require.Less(t, len(flags), max) + require.Less(t, len(flags), maxInt) } } diff --git a/lib/uid.go b/lib/uid.go index d9091cb..792b50a 100644 --- a/lib/uid.go +++ b/lib/uid.go @@ -1,14 +1,9 @@ package lib import ( - "math/rand" - "time" + "math/rand/v2" ) -func init() { - rand.Seed(time.Now().UnixMilli()) -} - func NewUID() uint32 { return rand.Uint32() } diff --git a/limitio/writer.go b/limitio/writer.go index 4f1906e..8d34a62 100644 --- a/limitio/writer.go +++ b/limitio/writer.go @@ -8,9 +8,8 @@ import ( ) type Writer struct { - w io.Writer - limiter *rate.Limiter - limitReader io.Reader + w io.Writer + limiter *rate.Limiter } // NewWriter returns a writer that implements io.Writer with rate limiting. diff --git a/storage/backend_test.go b/storage/backend_test.go index ae9a45c..236a254 100644 --- a/storage/backend_test.go +++ b/storage/backend_test.go @@ -16,7 +16,6 @@ import ( "github.com/creativeprojects/imap/storage/mdir" "github.com/creativeprojects/imap/storage/mem" "github.com/creativeprojects/imap/storage/remote" - "github.com/emersion/go-imap" compress "github.com/emersion/go-imap-compress" "github.com/emersion/go-imap/backend/memory" "github.com/emersion/go-imap/server" @@ -25,19 +24,6 @@ import ( "golang.org/x/net/nettest" ) -var ( - sampleMessage = "From: contact@example.org\r\n" + - "To: contact@example.org\r\n" + - "Subject: A little message, just for you\r\n" + - "Date: Wed, 11 May 2016 14:31:59 +0000\r\n" + - "Message-ID: <0000000@localhost/>\r\n" + - "Content-Type: text/plain\r\n" + - "\r\n" + - "Hi there :)" - sampleMessageDate = time.Date(2020, 10, 20, 12, 11, 0, 0, time.UTC) - sampleMessageFlags = []string{imap.SeenFlag} -) - func TestImapBackend(t *testing.T) { // Create a memory backend be := memory.New() diff --git a/storage/copy.go b/storage/copy.go index 43288ed..b204f3d 100644 --- a/storage/copy.go +++ b/storage/copy.go @@ -47,7 +47,7 @@ func CopyMessages(ctx context.Context, backendSource, backendDest Backend, mbox return entries, nil } -func copyMessage(ctx context.Context, msgSource *mailbox.Message, backendDest Backend, mboxDest mailbox.Info, history *mailbox.History) (*mailbox.MessageID, error) { +func copyMessage(_ context.Context, msgSource *mailbox.Message, backendDest Backend, mboxDest mailbox.Info, history *mailbox.History) (*mailbox.MessageID, error) { defer msgSource.Body.Close() if previousEntry := mailbox.FindHistoryEntryFromSourceID(history, msgSource.Uid); previousEntry != nil { diff --git a/storage/local/bolt_store.go b/storage/local/bolt_store.go index 7e3ee12..f6eecca 100644 --- a/storage/local/bolt_store.go +++ b/storage/local/bolt_store.go @@ -106,7 +106,9 @@ func (s *BoltStore) CreateMailbox(info mailbox.Info) error { if err != nil { return err } - defer tx.Rollback() + defer func() { + _ = tx.Rollback() + }() // Setup the mailbox bucket. root, err := tx.CreateBucketIfNotExists([]byte(mailboxBucket)) @@ -428,7 +430,9 @@ func (s *BoltStore) AddToHistory(info mailbox.Info, actions ...mailbox.HistoryAc if err != nil { return err } - defer tx.Rollback() + defer func() { + _ = tx.Rollback() + }() // Setup the mailbox bucket. root, err := tx.CreateBucketIfNotExists([]byte(mailboxBucket)) diff --git a/storage/mdir/maildir.go b/storage/mdir/maildir.go index 0b786f3..cf4ea22 100644 --- a/storage/mdir/maildir.go +++ b/storage/mdir/maildir.go @@ -141,24 +141,20 @@ func (m *Maildir) SelectMailbox(info mailbox.Info) (*mailbox.Status, error) { func (m *Maildir) PutMessage(info mailbox.Info, props mailbox.MessageProperties, body io.Reader) (mailbox.MessageID, error) { name := lib.VerifyDelimiter(info.Name, info.Delimiter, Delimiter) mbox := maildir.Dir(filepath.Join(m.root, name)) - key, copied, err := m.createFromStream(mbox, props.Flags, body) + msg, copied, err := m.createFromStream(mbox, props.Flags, body) if err != nil { return mailbox.EmptyMessageID, err } if props.Size > 0 && copied != int64(props.Size) { // delete the message - filename, err := mbox.Filename(key) - if err == nil { - _ = os.Remove(filename) - } + filename := msg.Filename() + _ = os.Remove(filename) return mailbox.EmptyMessageID, fmt.Errorf("message body size advertised as %d bytes but read %d bytes from buffer", props.Size, copied) } - m.log.Printf("Message saved: mailbox=%q key=%q size=%d flags=%v date=%q", name, key, copied, props.Flags, props.InternalDate) + m.log.Printf("Message saved: mailbox=%q key=%q size=%d flags=%v date=%q", name, msg, copied, props.Flags, props.InternalDate) - filename, err := mbox.Filename(key) - if err == nil { - _ = os.Chtimes(filename, time.Now(), props.InternalDate) - } + filename := msg.Filename() + _ = os.Chtimes(filename, time.Now(), props.InternalDate) status, err := m.getMailboxStatus(name) if err != nil { @@ -169,20 +165,20 @@ func (m *Maildir) PutMessage(info mailbox.Info, props mailbox.MessageProperties, if err != nil { return mailbox.EmptyMessageID, err } - return mailbox.NewMessageIDFromString(key), nil + return mailbox.NewMessageIDFromString(msg.Key()), nil } -func (m *Maildir) createFromStream(mbox maildir.Dir, flags []string, body io.Reader) (string, int64, error) { - key, writer, err := mbox.Create(toFlags(flags)) +func (m *Maildir) createFromStream(mbox maildir.Dir, flags []string, body io.Reader) (*maildir.Message, int64, error) { + msg, writer, err := mbox.Create(toFlags(flags)) if err != nil { - return key, 0, err + return msg, 0, err } defer writer.Close() copied, err := io.Copy(writer, body) if err != nil { - return key, copied, err + return msg, copied, err } - return key, copied, nil + return msg, copied, nil } func (m *Maildir) FetchMessages(ctx context.Context, since time.Time, messages chan *mailbox.Message) error { @@ -197,23 +193,17 @@ func (m *Maildir) FetchMessages(ctx context.Context, since time.Time, messages c name := m.selected mbox := maildir.Dir(filepath.Join(m.root, name)) - keys, err := mbox.Keys() + msgs, err := mbox.Messages() if err != nil { return err } - for _, key := range keys { + for _, msg := range msgs { if ctx.Err() != nil { return ctx.Err() } - flags, err := mbox.Flags(key) - if err != nil { - return fmt.Errorf("cannot read flags for key %q: %w", key, err) - } - filename, err := mbox.Filename(key) - if err != nil { - return fmt.Errorf("cannot find filename for key %q: %w", key, err) - } + flags := msg.Flags() + filename := msg.Filename() info, err := os.Stat(filename) if err != nil { return fmt.Errorf("cannot stat %q: %w", filename, err) @@ -222,9 +212,9 @@ func (m *Maildir) FetchMessages(ctx context.Context, since time.Time, messages c // skip this message continue } - file, err := mbox.Open(key) + file, err := msg.Open() if err != nil { - return fmt.Errorf("cannot open key %q: %w", key, err) + return fmt.Errorf("cannot open key %q: %w", msg, err) } messages <- &mailbox.Message{ MessageProperties: mailbox.MessageProperties{ @@ -232,7 +222,7 @@ func (m *Maildir) FetchMessages(ctx context.Context, since time.Time, messages c InternalDate: info.ModTime(), Size: uint32(info.Size()), }, - Uid: mailbox.NewMessageIDFromString(key), + Uid: mailbox.NewMessageIDFromString(msg.Key()), Body: file, } } @@ -248,20 +238,16 @@ func (m *Maildir) LatestDate(ctx context.Context) (time.Time, error) { } mbox := maildir.Dir(filepath.Join(m.root, m.selected)) - keys, err := mbox.Keys() + msgs, err := mbox.Messages() if err != nil { return latest, err } - for _, key := range keys { + for _, msg := range msgs { if ctx.Err() != nil { return latest, ctx.Err() } - filename, err := mbox.Filename(key) - if err != nil { - // should we keep going after an error? - return latest, fmt.Errorf("cannot find filename for key %q: %w", key, err) - } + filename := msg.Filename() info, err := os.Stat(filename) if err != nil { // should we keep going after an error? diff --git a/storage/mem/memory.go b/storage/mem/memory.go index c14b810..9053b9c 100644 --- a/storage/mem/memory.go +++ b/storage/mem/memory.go @@ -186,6 +186,7 @@ func (m *Backend) LatestDate(ctx context.Context) (time.Time, error) { return latest, nil } + //nolint:staticcheck for uid := mailbox.currentUid; uid >= 0; uid-- { if msg, found := mailbox.messages[uid]; found { return msg.date, nil diff --git a/storage/remote/imap.go b/storage/remote/imap.go index aa809af..5c12a6b 100644 --- a/storage/remote/imap.go +++ b/storage/remote/imap.go @@ -54,7 +54,9 @@ func NewImap(cfg Config) (*Imap, error) { if cfg.NoTLS { imapClient, err = client.Dial(cfg.ServerURL) } else { - tlsConfig := &tls.Config{} + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } if cfg.SkipTLSVerification { tlsConfig.InsecureSkipVerify = true }