From 14a578f3196f08e2fe47992b043dd0ac90d705fd Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Tue, 11 Oct 2022 18:04:39 +0200 Subject: [PATCH] Other: Linter fixes after bumping linter version --- Makefile | 4 +- go.mod | 2 +- go.sum | 4 +- internal/bridge/bridge.go | 128 ++++++++++++++++++---------- internal/bridge/bridge_test.go | 62 +++++--------- internal/bridge/bug_report.go | 4 +- internal/bridge/files.go | 8 +- internal/bridge/imap.go | 49 +++++++---- internal/bridge/mocks.go | 6 +- internal/bridge/settings.go | 8 +- internal/bridge/settings_test.go | 19 ++--- internal/bridge/smtp.go | 14 ++- internal/bridge/sync_test.go | 47 +++++++--- internal/bridge/updates.go | 7 ++ internal/bridge/user_events.go | 2 +- internal/bridge/user_test.go | 50 +++++------ internal/focus/proto/focus.go | 1 + internal/focus/service.go | 17 ++-- internal/safe/{type.go => value.go} | 16 ++-- internal/user/imap.go | 6 +- internal/user/smtp.go | 1 - internal/user/types.go | 12 ++- internal/user/user.go | 12 +-- 23 files changed, 273 insertions(+), 206 deletions(-) rename internal/safe/{type.go => value.go} (50%) diff --git a/Makefile b/Makefile index d3965e7d..b369bab5 100644 --- a/Makefile +++ b/Makefile @@ -218,10 +218,10 @@ change-copyright-year: ./utils/missing_license.sh change-year test: gofiles - go test -v -failfast -count=1 -p=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/... + go test -v -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/... test-integration: gofiles - go test -v -failfast -count=1 -p=1 github.com/ProtonMail/proton-bridge/v2/tests + go test -v github.com/ProtonMail/proton-bridge/v2/tests bench: go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store diff --git a/go.mod b/go.mod index 61511dfb..5a040601 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.0 github.com/urfave/cli/v2 v2.16.3 - gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf + gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b golang.org/x/exp v0.0.0-20220921164117-439092de6870 golang.org/x/net v0.1.0 golang.org/x/sys v0.1.0 diff --git a/go.sum b/go.sum index f1af1356..41b990f2 100644 --- a/go.sum +++ b/go.sum @@ -397,8 +397,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf h1:WBUv+vl0zTc+VEWied2YDv/HmLwjAMxqsqzNBoT7d4Y= -gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf/go.mod h1:NfsxXn1T81sz0gHnxuAfyCI4Agzm5UWVRyEtdQSch/4= +gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b h1:9bTndevIV9WTSbRsoLXmLj8bycla6O3KU7fFzEV09n0= +gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b/go.mod h1:NfsxXn1T81sz0gHnxuAfyCI4Agzm5UWVRyEtdQSch/4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index a7ada2b0..c0c3b7d1 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -1,3 +1,4 @@ +// Package bridge implements the Bridge, which acts as the backend to the UI. package bridge import ( @@ -59,7 +60,7 @@ type Bridge struct { updateCheckCh chan struct{} // focusService is used to raise the bridge window when needed. - focusService *focus.FocusService + focusService *focus.Service // autostarter is the bridge's autostarter. autostarter Autostarter @@ -70,36 +71,30 @@ type Bridge struct { // errors contains errors encountered during startup. errors []error + // These control the bridge's IMAP and SMTP logging behaviour. + logIMAPClient bool + logIMAPServer bool + logSMTP bool + // stopCh is used to stop ongoing goroutines when the bridge is closed. stopCh chan struct{} - - logIMAPClientCommands bool - logIMAPServerCommands bool - logSMTPCommands bool } // New creates a new bridge. func New( - apiURL string, // the URL of the API to use - locator Locator, // the locator to provide paths to store data - vault *vault.Vault, // the bridge's encrypted data store - identifier Identifier, // the identifier to keep track of the user agent - tlsReporter TLSReporter, // the TLS reporter to report TLS errors + apiURL string, // the URL of the API to use + locator Locator, // the locator to provide paths to store data + vault *vault.Vault, // the bridge's encrypted data store + identifier Identifier, // the identifier to keep track of the user agent + tlsReporter TLSReporter, // the TLS reporter to report TLS errors roundTripper http.RoundTripper, // the round tripper to use for API requests - proxyCtl ProxyController, // the DoH controller - autostarter Autostarter, // the autostarter to manage autostart settings - updater Updater, // the updater to fetch and install updates - curVersion *semver.Version, // the current version of the bridge - logIMAPClientCommands bool, - logIMAPServerCommands bool, - logSMTPCommands bool, + proxyCtl ProxyController, // the DoH controller + autostarter Autostarter, // the autostarter to manage autostart settings + updater Updater, // the updater to fetch and install updates + curVersion *semver.Version, // the current version of the bridge + logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity + logSMTP bool, // whether to log SMTP activity ) (*Bridge, error) { - if vault.GetProxyAllowed() { - proxyCtl.AllowProxy() - } else { - proxyCtl.DisallowProxy() - } - cookieJar, err := cookies.NewCookieJar(vault) if err != nil { return nil, fmt.Errorf("failed to create cookie jar: %w", err) @@ -117,25 +112,19 @@ func New( return nil, fmt.Errorf("failed to load TLS config: %w", err) } - // TODO: Handle case that the gluon directory is missing! gluonDir, err := getGluonDir(vault) if err != nil { return nil, fmt.Errorf("failed to get Gluon directory: %w", err) } - imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClientCommands, logIMAPServerCommands) - if err != nil { - return nil, fmt.Errorf("failed to create IMAP server: %w", err) - } - smtpBackend, err := newSMTPBackend() if err != nil { return nil, fmt.Errorf("failed to create SMTP backend: %w", err) } - smtpServer, err := newSMTPServer(smtpBackend, tlsConfig, logSMTPCommands) + imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClient, logIMAPServer) if err != nil { - return nil, fmt.Errorf("failed to create SMTP server: %w", err) + return nil, fmt.Errorf("failed to create IMAP server: %w", err) } focusService, err := focus.NewService() @@ -143,7 +132,49 @@ func New( return nil, fmt.Errorf("failed to create focus service: %w", err) } - bridge := &Bridge{ + bridge := newBridge( + vault, + api, + cookieJar, + proxyCtl, + identifier, + tlsConfig, + imapServer, + smtpBackend, + updater, + curVersion, + focusService, + autostarter, + locator, + logIMAPClient, + logIMAPServer, + logSMTP, + ) + + if err := bridge.init(tlsReporter); err != nil { + return nil, fmt.Errorf("failed to initialize bridge: %w", err) + } + + return bridge, nil +} + +func newBridge( + vault *vault.Vault, + api *liteapi.Manager, + cookieJar *cookies.Jar, + proxyCtl ProxyController, + identifier Identifier, + tlsConfig *tls.Config, + imapServer *gluon.Server, + smtpBackend *smtpBackend, + updater Updater, + curVersion *semver.Version, + focusService *focus.Service, + autostarter Autostarter, + locator Locator, + logIMAPClient, logIMAPServer, logSMTP bool, +) *Bridge { + return &Bridge{ vault: vault, users: make(map[string]*user.User), @@ -154,7 +185,7 @@ func New( tlsConfig: tlsConfig, imapServer: imapServer, - smtpServer: smtpServer, + smtpServer: newSMTPServer(smtpBackend, tlsConfig, logSMTP), smtpBackend: smtpBackend, updater: updater, @@ -165,14 +196,22 @@ func New( autostarter: autostarter, locator: locator, - stopCh: make(chan struct{}), + logIMAPClient: logIMAPClient, + logIMAPServer: logIMAPServer, + logSMTP: logSMTP, - logIMAPClientCommands: logIMAPClientCommands, - logIMAPServerCommands: logIMAPServerCommands, - logSMTPCommands: logSMTPCommands, + stopCh: make(chan struct{}), + } +} + +func (bridge *Bridge) init(tlsReporter TLSReporter) error { + if bridge.vault.GetProxyAllowed() { + bridge.proxyCtl.AllowProxy() + } else { + bridge.proxyCtl.DisallowProxy() } - api.AddStatusObserver(func(status liteapi.Status) { + bridge.api.AddStatusObserver(func(status liteapi.Status) { switch { case status == liteapi.StatusUp: go bridge.onStatusUp() @@ -182,17 +221,17 @@ func New( } }) - api.AddErrorHandler(liteapi.AppVersionBadCode, func() { + bridge.api.AddErrorHandler(liteapi.AppVersionBadCode, func() { bridge.publish(events.UpdateForced{}) }) - api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error { + bridge.api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error { req.SetHeader("User-Agent", bridge.identifier.GetUserAgent()) return nil }) if err := bridge.loadUsers(); err != nil { - return nil, fmt.Errorf("failed to load users: %w", err) + return fmt.Errorf("failed to load users: %w", err) } go func() { @@ -202,13 +241,13 @@ func New( }() go func() { - for range focusService.GetRaiseCh() { + for range bridge.focusService.GetRaiseCh() { bridge.publish(events.Raise{}) } }() go func() { - for event := range imapServer.AddWatcher() { + for event := range bridge.imapServer.AddWatcher() { bridge.handleIMAPEvent(event) } }() @@ -225,7 +264,7 @@ func New( bridge.PushError(ErrWatchUpdates) } - return bridge, nil + return nil } // GetEvents returns a channel of events of the given type. @@ -363,6 +402,7 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) { return nil, err } + // TODO: Do we have to set MinVersion to tls.VersionTLS12? return &tls.Config{ Certificates: []tls.Certificate{cert}, }, nil diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index 77fedc8b..b13c8976 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -45,7 +45,7 @@ func init() { func TestBridge_ConnStatus(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Get a stream of connection status events. eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{}) defer done() @@ -76,7 +76,7 @@ func TestBridge_ConnStatus(t *testing.T) { func TestBridge_TLSIssue(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Get a stream of TLS issue events. tlsEventCh, done := bridge.GetEvents(events.TLSIssue{}) defer done() @@ -94,7 +94,7 @@ func TestBridge_TLSIssue(t *testing.T) { func TestBridge_Focus(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Get a stream of TLS issue events. raiseCh, done := bridge.GetEvents(events.Raise{}) defer done() @@ -116,7 +116,7 @@ func TestBridge_UserAgent(t *testing.T) { calls = append(calls, call) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Set the platform to something other than the default. bridge.SetCurrentPlatform("platform") @@ -148,13 +148,13 @@ func TestBridge_Cookies(t *testing.T) { }) // Start bridge and add a user so that API assigns us a session ID via cookie. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { _, err := bridge.LoginUser(context.Background(), username, password, nil, nil) require.NoError(t, err) }) // Start bridge again and check that it uses the same session ID. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // ... }) @@ -167,7 +167,7 @@ func TestBridge_Cookies(t *testing.T) { func TestBridge_CheckUpdate(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Disable autoupdate for this test. require.NoError(t, bridge.SetAutoUpdate(false)) @@ -206,7 +206,7 @@ func TestBridge_CheckUpdate(t *testing.T) { func TestBridge_AutoUpdate(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Enable autoupdate for this test. require.NoError(t, bridge.SetAutoUpdate(true)) @@ -234,7 +234,7 @@ func TestBridge_AutoUpdate(t *testing.T) { func TestBridge_ManualUpdate(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Disable autoupdate for this test. require.NoError(t, bridge.SetAutoUpdate(false)) @@ -263,7 +263,7 @@ func TestBridge_ManualUpdate(t *testing.T) { func TestBridge_ForceUpdate(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Get a stream of update events. updateCh, done := bridge.GetEvents(events.UpdateForced{}) defer done() @@ -286,7 +286,7 @@ func TestBridge_BadVaultKey(t *testing.T) { var userID string // Login a user. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { newUserID, err := bridge.LoginUser(context.Background(), username, password, nil, nil) require.NoError(t, err) @@ -294,17 +294,17 @@ func TestBridge_BadVaultKey(t *testing.T) { }) // Start bridge with the correct vault key -- it should load the users correctly. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs()) }) // Start bridge with a bad vault key, the vault will be wiped and bridge will show no users. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Empty(t, bridge.GetUserIDs()) }) // Start bridge with a nil vault key, the vault will be wiped and bridge will show no users. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Empty(t, bridge.GetUserIDs()) }) }) @@ -314,12 +314,12 @@ func TestBridge_MissingGluonDir(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { var gluonDir string - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { _, err := bridge.LoginUser(context.Background(), username, password, nil, nil) require.NoError(t, err) // Move the gluon dir. - bridge.SetGluonDir(ctx, t.TempDir()) + require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir())) // Get the gluon dir. gluonDir = bridge.GetGluonDir() @@ -329,7 +329,7 @@ func TestBridge_MissingGluonDir(t *testing.T) { require.NoError(t, os.RemoveAll(gluonDir)) // Bridge starts but can't find the gluon dir; there should be no error. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // ... }) }) @@ -337,30 +337,12 @@ func TestBridge_MissingGluonDir(t *testing.T) { // withTLSEnv creates the full test environment and runs the tests. func withTLSEnv(t *testing.T, tests func(context.Context, *server.Server, *liteapi.NetCtl, bridge.Locator, []byte)) { - // Create test API. server := server.NewTLS() defer server.Close() - // Add test user. - _, _, err := server.CreateUser(username, username+"@pm.me", password) - require.NoError(t, err) - - // Generate a random vault key. - vaultKey, err := crypto.RandomToken(32) - require.NoError(t, err) - - // Create a context used for the test. - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Create a net controller so we can simulate network connectivity issues. - netCtl := liteapi.NewNetCtl() - - // Create a locations object to provide temporary locations for bridge data during the test. - locations := locations.New(bridge.NewTestLocationsProvider(t.TempDir()), "config-name") - - // Run the tests. - tests(ctx, server, netCtl, locations, vaultKey) + withEnv(t, server, func(ctx context.Context, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { + tests(ctx, server, netCtl, locator, vaultKey) + }) } // withEnv creates the full test environment and runs the tests. @@ -389,8 +371,8 @@ func withEnv(t *testing.T, server *server.Server, tests func(context.Context, *l // withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done. func withBridge( - t *testing.T, ctx context.Context, + t *testing.T, apiURL string, netCtl *liteapi.NetCtl, locator bridge.Locator, @@ -435,7 +417,7 @@ func withBridge( require.NoError(t, err) // Close the bridge when done. - defer bridge.Close(ctx) + defer func() { require.NoError(t, bridge.Close(ctx)) }() // Use the bridge. tests(bridge, mocks) diff --git a/internal/bridge/bug_report.go b/internal/bridge/bug_report.go index 24a3a316..69e26b6d 100644 --- a/internal/bridge/bug_report.go +++ b/internal/bridge/bug_report.go @@ -31,7 +31,7 @@ import ( ) const ( - MaxAttachmentSize = 7 * (1 << 20) // MaxAttachmentSize 7 MB total size of all attachments. + MaxTotalAttachmentSize = 7 * (1 << 20) MaxCompressedFilesCount = 6 ) @@ -166,7 +166,7 @@ func zipFiles(filenames []string) (io.Reader, error) { return nil, nil } - buf := newLimitedBuffer(MaxAttachmentSize) + buf := newLimitedBuffer(MaxTotalAttachmentSize) w := zip.NewWriter(buf) defer w.Close() //nolint:errcheck diff --git a/internal/bridge/files.go b/internal/bridge/files.go index 2a272d49..40c16aad 100644 --- a/internal/bridge/files.go +++ b/internal/bridge/files.go @@ -39,17 +39,17 @@ func move(from, to string) error { return err } - f, err := os.Open(from) + f, err := os.Open(from) // nolint:gosec if err != nil { return err } - defer f.Close() + defer func() { _ = f.Close() }() - c, err := os.Create(to) + c, err := os.Create(to) // nolint:gosec if err != nil { return err } - defer c.Close() + defer func() { _ = f.Close() }() if err := os.Chmod(to, 0600); err != nil { return err diff --git a/internal/bridge/imap.go b/internal/bridge/imap.go index 8f623b8a..88e16e2f 100644 --- a/internal/bridge/imap.go +++ b/internal/bridge/imap.go @@ -63,7 +63,7 @@ func (bridge *Bridge) serveIMAP() error { return nil } -func (bridge *Bridge) restartIMAP(ctx context.Context) error { +func (bridge *Bridge) restartIMAP() error { if err := bridge.imapListener.Close(); err != nil { logrus.WithError(err).Warn("Failed to close IMAP listener") } @@ -73,11 +73,11 @@ func (bridge *Bridge) restartIMAP(ctx context.Context) error { func (bridge *Bridge) closeIMAP(ctx context.Context) error { if err := bridge.imapServer.Close(ctx); err != nil { - logrus.WithError(err).Warn("Failed to close IMAP server") + return fmt.Errorf("failed to close IMAP server: %w", err) } if err := bridge.imapListener.Close(); err != nil { - logrus.WithError(err).Warn("Failed to close IMAP listener") + return fmt.Errorf("failed to close IMAP listener: %w", err) } return nil @@ -98,11 +98,18 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) { } func getGluonDir(encVault *vault.Vault) (string, error) { - empty, err := isEmpty(encVault.GetGluonDir()) + empty, exists, err := isEmpty(encVault.GetGluonDir()) if err != nil { return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err) } + // TODO: Handle case that the gluon directory is missing and we can't create it! + if !exists { + if err := os.MkdirAll(encVault.GetGluonDir(), 0700); err != nil { + return "", fmt.Errorf("failed to create gluon dir: %w", err) + } + } + if empty { if err := encVault.ForUser(func(user *vault.User) error { return user.ClearSyncStatus() @@ -114,27 +121,33 @@ func getGluonDir(encVault *vault.Vault) (string, error) { return encVault.GetGluonDir(), nil } -func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Config, logIMAPCommandsClient, logIMAPCommandsServer bool) (*gluon.Server, error) { - var imapClientLog io.Writer - var imapServerLog io.Writer - - if logIMAPCommandsClient || logIMAPCommandsServer { +func newIMAPServer( + gluonDir string, + version *semver.Version, + tlsConfig *tls.Config, + logClient, logServer bool, +) (*gluon.Server, error) { + if logClient || logServer { log := logrus.WithField("protocol", "IMAP") log.Warning("================================================") log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") log.Warning("================================================") } - if logIMAPCommandsClient { + var imapClientLog io.Writer + + if logClient { imapClientLog = logging.NewIMAPLogger() } else { imapClientLog = io.Discard } - if logIMAPCommandsServer { + var imapServerLog io.Writer + + if logServer { imapServerLog = logging.NewIMAPLogger() } else { - imapClientLog = io.Discard + imapServerLog = io.Discard } imapServer, err := gluon.New( @@ -160,19 +173,21 @@ func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Conf return imapServer, nil } -func isEmpty(dir string) (bool, error) { +// isEmpty returns whether the given directory is empty. +// If the directory does not exist, the second return value is false. +func isEmpty(dir string) (bool, bool, error) { if _, err := os.Stat(dir); err != nil { if !errors.Is(err, fs.ErrNotExist) { - return false, fmt.Errorf("failed to stat %s: %w", dir, err) + return false, false, fmt.Errorf("failed to stat %s: %w", dir, err) } - return true, nil + return true, false, nil } entries, err := os.ReadDir(dir) if err != nil { - return false, fmt.Errorf("failed to read dir %s: %w", dir, err) + return false, false, fmt.Errorf("failed to read dir %s: %w", dir, err) } - return len(entries) == 0, nil + return len(entries) == 0, true, nil } diff --git a/internal/bridge/mocks.go b/internal/bridge/mocks.go index 7c34fe7b..ae6108e7 100644 --- a/internal/bridge/mocks.go +++ b/internal/bridge/mocks.go @@ -94,10 +94,10 @@ func (testUpdater *TestUpdater) SetLatestVersion(version, minAuto *semver.Versio } } -func (updater *TestUpdater) GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) { - return updater.latest, nil +func (testUpdater *TestUpdater) GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) { + return testUpdater.latest, nil } -func (updater *TestUpdater) InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error { +func (testUpdater *TestUpdater) InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error { return nil } diff --git a/internal/bridge/settings.go b/internal/bridge/settings.go index 082265a3..37b2ceb2 100644 --- a/internal/bridge/settings.go +++ b/internal/bridge/settings.go @@ -40,7 +40,7 @@ func (bridge *Bridge) SetIMAPPort(newPort int) error { return err } - return bridge.restartIMAP(context.Background()) + return bridge.restartIMAP() } func (bridge *Bridge) GetIMAPSSL() bool { @@ -56,7 +56,7 @@ func (bridge *Bridge) SetIMAPSSL(newSSL bool) error { return err } - return bridge.restartIMAP(context.Background()) + return bridge.restartIMAP() } func (bridge *Bridge) GetSMTPPort() int { @@ -112,7 +112,7 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error return fmt.Errorf("failed to set new gluon dir: %w", err) } - imapServer, err := newIMAPServer(bridge.vault.GetGluonDir(), bridge.curVersion, bridge.tlsConfig, bridge.logIMAPClientCommands, bridge.logIMAPServerCommands) + imapServer, err := newIMAPServer(bridge.vault.GetGluonDir(), bridge.curVersion, bridge.tlsConfig, bridge.logIMAPClient, bridge.logIMAPServer) if err != nil { return fmt.Errorf("failed to create new IMAP server: %w", err) } @@ -193,7 +193,7 @@ func (bridge *Bridge) SetAutoUpdate(autoUpdate bool) error { } func (bridge *Bridge) GetUpdateChannel() updater.Channel { - return updater.Channel(bridge.vault.GetUpdateChannel()) + return bridge.vault.GetUpdateChannel() } func (bridge *Bridge) SetUpdateChannel(channel updater.Channel) error { diff --git a/internal/bridge/settings_test.go b/internal/bridge/settings_test.go index 174a9e4d..28f06858 100644 --- a/internal/bridge/settings_test.go +++ b/internal/bridge/settings_test.go @@ -13,7 +13,7 @@ import ( func TestBridge_Settings_GluonDir(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Create a user. _, err := bridge.LoginUser(context.Background(), username, password, nil, nil) require.NoError(t, err) @@ -36,7 +36,7 @@ func TestBridge_Settings_GluonDir(t *testing.T) { func TestBridge_Settings_IMAPPort(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { curPort := bridge.GetIMAPPort() // Set the port to 1144. @@ -53,7 +53,7 @@ func TestBridge_Settings_IMAPPort(t *testing.T) { func TestBridge_Settings_IMAPSSL(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, IMAP SSL is disabled. require.False(t, bridge.GetIMAPSSL()) @@ -68,7 +68,7 @@ func TestBridge_Settings_IMAPSSL(t *testing.T) { func TestBridge_Settings_SMTPPort(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { curPort := bridge.GetSMTPPort() // Set the port to 1024. @@ -79,14 +79,13 @@ func TestBridge_Settings_SMTPPort(t *testing.T) { // Assert that it has changed. require.NotEqual(t, curPort, bridge.GetSMTPPort()) - }) }) } func TestBridge_Settings_SMTPSSL(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, SMTP SSL is disabled. require.False(t, bridge.GetSMTPSSL()) @@ -101,7 +100,7 @@ func TestBridge_Settings_SMTPSSL(t *testing.T) { func TestBridge_Settings_Proxy(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, proxy is allowed. require.True(t, bridge.GetProxyAllowed()) @@ -117,7 +116,7 @@ func TestBridge_Settings_Proxy(t *testing.T) { func TestBridge_Settings_Autostart(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, autostart is disabled. require.False(t, bridge.GetAutostart()) @@ -133,7 +132,7 @@ func TestBridge_Settings_Autostart(t *testing.T) { func TestBridge_Settings_FirstStart(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, first start is true. require.True(t, bridge.GetFirstStart()) @@ -148,7 +147,7 @@ func TestBridge_Settings_FirstStart(t *testing.T) { func TestBridge_Settings_FirstStartGUI(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // By default, first start is true. require.True(t, bridge.GetFirstStartGUI()) diff --git a/internal/bridge/smtp.go b/internal/bridge/smtp.go index 760e7de7..ac0bdc15 100644 --- a/internal/bridge/smtp.go +++ b/internal/bridge/smtp.go @@ -3,10 +3,11 @@ package bridge import ( "crypto/tls" "fmt" - "github.com/ProtonMail/proton-bridge/v2/internal/logging" "net" "strconv" + "github.com/ProtonMail/proton-bridge/v2/internal/logging" + "github.com/ProtonMail/proton-bridge/v2/internal/constants" "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" @@ -49,12 +50,7 @@ func (bridge *Bridge) restartSMTP() error { return err } - smtpServer, err := newSMTPServer(bridge.smtpBackend, bridge.tlsConfig, bridge.logSMTPCommands) - if err != nil { - return err - } - - bridge.smtpServer = smtpServer + bridge.smtpServer = newSMTPServer(bridge.smtpBackend, bridge.tlsConfig, bridge.logSMTP) return bridge.serveSMTP() } @@ -69,7 +65,7 @@ func (bridge *Bridge) closeSMTP() error { return nil } -func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bool) (*smtp.Server, error) { +func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bool) *smtp.Server { smtpServer := smtp.NewServer(smtpBackend) smtpServer.TLSConfig = tlsConfig @@ -99,5 +95,5 @@ func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bo }) }) - return smtpServer, nil + return smtpServer } diff --git a/internal/bridge/sync_test.go b/internal/bridge/sync_test.go index a61f6f15..3f02a48f 100644 --- a/internal/bridge/sync_test.go +++ b/internal/bridge/sync_test.go @@ -44,18 +44,18 @@ func TestBridge_Sync(t *testing.T) { }) // The initial user should be fully synced. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { - syncCh, done := bridge.GetEvents(events.SyncFinished{}) + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{})) defer done() userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil) require.NoError(t, err) - require.Equal(t, userID, (<-syncCh).(events.SyncFinished).UserID) + require.Equal(t, userID, (<-syncCh).UserID) }) // If we then connect an IMAP client, it should see all the messages. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { info, err := bridge.GetUserInfo(userID) require.NoError(t, err) require.True(t, info.Connected) @@ -63,7 +63,7 @@ func TestBridge_Sync(t *testing.T) { client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) require.NoError(t, err) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) - defer client.Logout() + defer func() { _ = client.Logout() }() status, err := client.Select(`Folders/folder`, false) require.NoError(t, err) @@ -71,7 +71,7 @@ func TestBridge_Sync(t *testing.T) { }) // Now let's remove the user and simulate a network error. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.NoError(t, bridge.DeleteUser(ctx, userID)) }) @@ -79,14 +79,14 @@ func TestBridge_Sync(t *testing.T) { netCtl.SetReadLimit(2 * read / 3) // Login the user; its sync should fail. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { - syncCh, done := bridge.GetEvents(events.SyncFailed{}) + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + syncCh, done := chToType[events.Event, events.SyncFailed](bridge.GetEvents(events.SyncFailed{})) defer done() userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil) require.NoError(t, err) - require.Equal(t, userID, (<-syncCh).(events.SyncFailed).UserID) + require.Equal(t, userID, (<-syncCh).UserID) info, err := bridge.GetUserInfo(userID) require.NoError(t, err) @@ -95,7 +95,7 @@ func TestBridge_Sync(t *testing.T) { client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) require.NoError(t, err) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) - defer client.Logout() + defer func() { _ = client.Logout() }() status, err := client.Select(`Folders/folder`, false) require.NoError(t, err) @@ -107,11 +107,11 @@ func TestBridge_Sync(t *testing.T) { // Login the user; its sync should now finish. // If we then connect an IMAP client, it should eventually see all the messages. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { - syncCh, done := bridge.GetEvents(events.SyncFinished{}) + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{})) defer done() - require.Equal(t, userID, (<-syncCh).(events.SyncFinished).UserID) + require.Equal(t, userID, (<-syncCh).UserID) info, err := bridge.GetUserInfo(userID) require.NoError(t, err) @@ -120,7 +120,7 @@ func TestBridge_Sync(t *testing.T) { client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) require.NoError(t, err) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) - defer client.Logout() + defer func() { _ = client.Logout() }() status, err := client.Select(`Folders/folder`, false) require.NoError(t, err) @@ -128,3 +128,22 @@ func TestBridge_Sync(t *testing.T) { }) }) } + +func chToType[In, Out any](inCh <-chan In, done func()) (<-chan Out, func()) { + outCh := make(chan Out) + + go func() { + defer close(outCh) + + for in := range inCh { + out, ok := any(in).(Out) + if !ok { + panic(fmt.Sprintf("unexpected type %T", in)) + } + + outCh <- out + } + }() + + return outCh, done +} diff --git a/internal/bridge/updates.go b/internal/bridge/updates.go index de1524a4..21efdd72 100644 --- a/internal/bridge/updates.go +++ b/internal/bridge/updates.go @@ -13,6 +13,10 @@ func (bridge *Bridge) CheckForUpdates() { } func (bridge *Bridge) watchForUpdates() error { + if _, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel()); err != nil { + return err + } + ticker := time.NewTicker(constants.UpdateCheckInterval) go func() { @@ -22,7 +26,10 @@ func (bridge *Bridge) watchForUpdates() error { return case <-bridge.updateCheckCh: + // ... + case <-ticker.C: + // ... } version, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel()) diff --git a/internal/bridge/user_events.go b/internal/bridge/user_events.go index 80bb5bbc..8ac3435e 100644 --- a/internal/bridge/user_events.go +++ b/internal/bridge/user_events.go @@ -73,7 +73,7 @@ func (bridge *Bridge) handleUserAddressCreated(ctx context.Context, user *user.U } // TODO: Handle addresses that have been disabled! -func (bridge *Bridge) handleUserAddressUpdated(ctx context.Context, user *user.User, event events.UserAddressUpdated) error { +func (bridge *Bridge) handleUserAddressUpdated(_ context.Context, user *user.User, _ events.UserAddressUpdated) error { switch user.GetAddressMode() { case vault.CombinedMode: return fmt.Errorf("not implemented") diff --git a/internal/bridge/user_test.go b/internal/bridge/user_test.go index 931035a1..e9bb79ce 100644 --- a/internal/bridge/user_test.go +++ b/internal/bridge/user_test.go @@ -15,12 +15,12 @@ import ( func TestBridge_WithoutUsers(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Empty(t, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Empty(t, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) }) @@ -29,7 +29,7 @@ func TestBridge_WithoutUsers(t *testing.T) { func TestBridge_Login(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID, err := bridge.LoginUser(ctx, username, password, nil, nil) require.NoError(t, err) @@ -43,7 +43,7 @@ func TestBridge_Login(t *testing.T) { func TestBridge_LoginLogoutLogin(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -71,7 +71,7 @@ func TestBridge_LoginLogoutLogin(t *testing.T) { func TestBridge_LoginDeleteLogin(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -99,7 +99,7 @@ func TestBridge_LoginDeleteLogin(t *testing.T) { func TestBridge_LoginDeauthLogin(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -135,7 +135,7 @@ func TestBridge_LoginExpireLogin(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { s.SetAuthLife(authLife) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. Its auth will only be valid for a short time. userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -153,7 +153,7 @@ func TestBridge_FailToLoad(t *testing.T) { var userID string // Login the user. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) }) @@ -161,7 +161,7 @@ func TestBridge_FailToLoad(t *testing.T) { require.NoError(t, s.RevokeUser(userID)) // When bridge starts, the user will not be logged in. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) }) @@ -173,7 +173,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) { var userID string // Login the user. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) }) @@ -181,7 +181,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) { netCtl.Disable() // Start bridge without internet. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Initially, users are not connected. require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) @@ -203,11 +203,11 @@ func TestBridge_LoginRestart(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { var userID string - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge)) }) @@ -218,7 +218,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { var userID string - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -226,7 +226,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) { require.NoError(t, bridge.LogoutUser(ctx, userID)) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // The user is still disconnected. require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) @@ -238,7 +238,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { var userID string - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -246,7 +246,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) { require.NoError(t, bridge.DeleteUser(ctx, userID)) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // The user is still gone. require.Empty(t, bridge.GetUserIDs()) require.Empty(t, getConnectedUserIDs(t, bridge)) @@ -263,7 +263,7 @@ func TestBridge_FailLoginRecover(t *testing.T) { }) // Log the user in and record how much data was read. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) require.NoError(t, bridge.LogoutUser(ctx, userID)) }) @@ -272,7 +272,7 @@ func TestBridge_FailLoginRecover(t *testing.T) { netCtl.SetReadLimit(read / 2) // We should fail to log the user in because we can't fully read its data. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { require.Error(t, getErr(bridge.LoginUser(ctx, username, password, nil, nil))) // There should be no users. @@ -283,7 +283,7 @@ func TestBridge_FailLoginRecover(t *testing.T) { func TestBridge_FailLoadRecover(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { must(bridge.LoginUser(ctx, username, password, nil, nil)) }) @@ -294,7 +294,7 @@ func TestBridge_FailLoadRecover(t *testing.T) { }) // Start bridge and record how much data was read. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // ... }) @@ -302,7 +302,7 @@ func TestBridge_FailLoadRecover(t *testing.T) { netCtl.SetReadLimit(read / 2) // We should fail to load the user; it should be disconnected. - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { userIDs := bridge.GetUserIDs() require.False(t, must(bridge.GetUserInfo(userIDs[0])).Connected) @@ -316,7 +316,7 @@ func TestBridge_BridgePass(t *testing.T) { var pass []byte - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) @@ -333,7 +333,7 @@ func TestBridge_BridgePass(t *testing.T) { require.Equal(t, pass, pass) }) - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // The bridge should load the user. require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge)) @@ -346,7 +346,7 @@ func TestBridge_BridgePass(t *testing.T) { func TestBridge_AddressMode(t *testing.T) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { - withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { // Login the user. userID, err := bridge.LoginUser(ctx, username, password, nil, nil) require.NoError(t, err) diff --git a/internal/focus/proto/focus.go b/internal/focus/proto/focus.go index 696fefae..106c3c4e 100644 --- a/internal/focus/proto/focus.go +++ b/internal/focus/proto/focus.go @@ -1,3 +1,4 @@ +// Package proto provides the gRPC definition of the focus service. package proto //go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative focus.proto diff --git a/internal/focus/service.go b/internal/focus/service.go index 14f5479d..6a3f1f4a 100644 --- a/internal/focus/service.go +++ b/internal/focus/service.go @@ -1,3 +1,4 @@ +// Package focus provides a gRPC service for raising the application. package focus import ( @@ -14,10 +15,10 @@ import ( const Host = "127.0.0.1" // Port is the port to listen on. -var Port = 1042 +var Port = 1042 // nolint:gochecknoglobals -// FocusService is a gRPC service that can be used to raise the application. -type FocusService struct { +// Service is a gRPC service that can be used to raise the application. +type Service struct { proto.UnimplementedFocusServer server *grpc.Server @@ -27,13 +28,13 @@ type FocusService struct { // NewService creates a new focus service. // It listens on the local host and port 1042 (by default). -func NewService() (*FocusService, error) { +func NewService() (*Service, error) { listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(Port))) if err != nil { return nil, fmt.Errorf("failed to listen: %w", err) } - service := &FocusService{ + service := &Service{ server: grpc.NewServer(), listener: listener, raiseCh: make(chan struct{}, 1), @@ -51,18 +52,18 @@ func NewService() (*FocusService, error) { } // Raise implements the gRPC FocusService interface; it raises the application. -func (service *FocusService) Raise(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { +func (service *Service) Raise(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { service.raiseCh <- struct{}{} return &emptypb.Empty{}, nil } // GetRaiseCh returns a channel on which events are sent when the application should be raised. -func (service *FocusService) GetRaiseCh() <-chan struct{} { +func (service *Service) GetRaiseCh() <-chan struct{} { return service.raiseCh } // Close closes the service. -func (service *FocusService) Close() { +func (service *Service) Close() { service.server.Stop() close(service.raiseCh) } diff --git a/internal/safe/type.go b/internal/safe/value.go similarity index 50% rename from internal/safe/type.go rename to internal/safe/value.go index 2bcdbb02..854798fa 100644 --- a/internal/safe/type.go +++ b/internal/safe/value.go @@ -2,46 +2,46 @@ package safe import "sync" -type Type[T any] struct { +type Value[T any] struct { data T lock sync.RWMutex } -func NewType[T any](data T) *Type[T] { - return &Type[T]{ +func NewValue[T any](data T) *Value[T] { + return &Value[T]{ data: data, } } -func (s *Type[T]) Get(fn func(data T)) { +func (s *Value[T]) Get(fn func(data T)) { s.lock.RLock() defer s.lock.RUnlock() fn(s.data) } -func (s *Type[T]) GetErr(fn func(data T) error) error { +func (s *Value[T]) GetErr(fn func(data T) error) error { s.lock.RLock() defer s.lock.RUnlock() return fn(s.data) } -func (s *Type[T]) Set(data T) { +func (s *Value[T]) Set(data T) { s.lock.Lock() defer s.lock.Unlock() s.data = data } -func GetType[T, Ret any](s *Type[T], fn func(data T) Ret) Ret { +func GetType[T, Ret any](s *Value[T], fn func(data T) Ret) Ret { s.lock.RLock() defer s.lock.RUnlock() return fn(s.data) } -func GetTypeErr[T, Ret any](s *Type[T], fn func(data T) (Ret, error)) (Ret, error) { +func GetTypeErr[T, Ret any](s *Value[T], fn func(data T) (Ret, error)) (Ret, error) { s.lock.RLock() defer s.lock.RUnlock() diff --git a/internal/user/imap.go b/internal/user/imap.go index 122b16a4..06cc32ab 100644 --- a/internal/user/imap.go +++ b/internal/user/imap.go @@ -14,9 +14,9 @@ import ( ) var ( - defaultFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) - defaultPermanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) - defaultAttributes = imap.NewFlagSet() + defaultFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) // nolint:gochecknoglobals + defaultPermanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) // nolint:gochecknoglobals + defaultAttributes = imap.NewFlagSet() // nolint:gochecknoglobals ) const ( diff --git a/internal/user/smtp.go b/internal/user/smtp.go index e8f5cbf7..b06eea21 100644 --- a/internal/user/smtp.go +++ b/internal/user/smtp.go @@ -75,7 +75,6 @@ func (session *smtpSession) Mail(from string, opts smtp.MailOptions) error { logrus.Info("SMTP session mail") return session.apiAddrs.GetErr(func(apiAddrs []liteapi.Address) error { - switch { case opts.RequireTLS: return ErrNotImplemented diff --git a/internal/user/types.go b/internal/user/types.go index f65cd6cb..7fca1530 100644 --- a/internal/user/types.go +++ b/internal/user/types.go @@ -1,12 +1,20 @@ package user -import "reflect" +import ( + "fmt" + "reflect" +) func mapTo[From, To any](from []From) []To { to := make([]To, 0, len(from)) for _, from := range from { - to = append(to, reflect.ValueOf(from).Convert(reflect.TypeOf(to).Elem()).Interface().(To)) + val, ok := reflect.ValueOf(from).Convert(reflect.TypeOf(to).Elem()).Interface().(To) + if !ok { + panic(fmt.Sprintf("cannot convert %T to %T", from, *new(To))) + } + + to = append(to, val) } return to diff --git a/internal/user/user.go b/internal/user/user.go index 7fcaeb9c..113c5a79 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -22,8 +22,8 @@ import ( ) var ( - EventPeriod = 20 * time.Second - EventJitter = 20 * time.Second + EventPeriod = 20 * time.Second // nolint:gochecknoglobals + EventJitter = 20 * time.Second // nolint:gochecknoglobals ) type User struct { @@ -31,9 +31,9 @@ type User struct { client *liteapi.Client eventCh *queue.QueuedChannel[events.Event] - apiUser *safe.Type[liteapi.User] + apiUser *safe.Value[liteapi.User] apiAddrs *safe.Slice[liteapi.Address] - settings *safe.Type[liteapi.MailSettings] + settings *safe.Value[liteapi.MailSettings] userKR *crypto.KeyRing addrKRs map[string]*crypto.KeyRing @@ -90,9 +90,9 @@ func New(ctx context.Context, encVault *vault.User, client *liteapi.Client, apiU client: client, eventCh: queue.NewQueuedChannel[events.Event](0, 0), - apiUser: safe.NewType(apiUser), + apiUser: safe.NewValue(apiUser), apiAddrs: safe.NewSlice(apiAddrs), - settings: safe.NewType(settings), + settings: safe.NewValue(settings), userKR: userKR, addrKRs: addrKRs,