Tests and final touches

This commit is contained in:
Michal Horejsek
2021-01-04 15:03:49 +01:00
parent 8ab852277c
commit 6ef2bb254d
24 changed files with 316 additions and 93 deletions

View File

@ -290,8 +290,10 @@ gofiles: ./internal/bridge/credits.go ./internal/importexport/credits.go
## Run and debug
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview run-ie run-ie-qt run-ie-qt-cli run-ie-nogui run-ie-nogui-cli clean-vendor clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common clean
VERBOSITY?=debug
RUN_FLAGS:=-m -l=${VERBOSITY}
LOG?=debug
LOG_IMAP?=client # client/server/all, or empty to turn it off
LOG_SMTP?=--log-smtp # empty to turn it off
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
run: run-nogui-cli

2
go.sum
View File

@ -274,6 +274,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=

View File

@ -158,13 +158,13 @@ func New( // nolint[funlen]
apiConfig := pmapi.GetAPIConfig(configName, constants.Version)
apiConfig.ConnectionOffHandler = func() {
eventListener.Emit(events.InternetOffEvent, "")
listener.Emit(events.InternetOffEvent, "")
}
apiConfig.ConnectionOnHandler = func() {
eventListener.Emit(events.InternetOnEvent, "")
listener.Emit(events.InternetOnEvent, "")
}
apiConfig.UpgradeApplicationHandler = func() {
eventListener.Emit(events.UpgradeApplicationEvent, "")
listener.Emit(events.UpgradeApplicationEvent, "")
}
cm := pmapi.NewClientManager(apiConfig)
cm.SetRoundTripper(pmapi.GetRoundTripper(cm, listener))

View File

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./credits.sh at Mon Jan 4 03:19:07 PM CET 2021. DO NOT EDIT.
// Code generated by ./credits.sh at Fri Jan 22 11:28:55 CET 2021. DO NOT EDIT.
package bridge
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli/v2;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;github.com/urfave/cli/v2;github.com/vmihailenco/msgpack/v5;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"

View File

@ -23,7 +23,7 @@ import (
"sync"
"time"
backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
)
type key struct {
@ -41,7 +41,7 @@ func (s oldestFirst) Less(i, j int) bool { return s[i].Timestamp < s[j].Timestam
type cachedMessage struct {
key
data []byte
structure backendMessage.BodyStructure
structure pkgMsg.BodyStructure
}
//nolint[gochecknoglobals]
@ -101,7 +101,7 @@ func BuildUnlock(messageID string) {
delete(buildLocks, messageID)
}
func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyStructure) {
func LoadMail(mID string) (reader *bytes.Reader, structure *pkgMsg.BodyStructure) {
reader = &bytes.Reader{}
cacheMutex.Lock()
defer cacheMutex.Unlock()
@ -115,7 +115,7 @@ func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyS
return
}
func SaveMail(mID string, msg []byte, structure *backendMessage.BodyStructure) {
func SaveMail(mID string, msg []byte, structure *pkgMsg.BodyStructure) {
cacheMutex.Lock()
defer cacheMutex.Unlock()

View File

@ -22,11 +22,11 @@ import (
"testing"
"time"
bckMsg "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/stretchr/testify/require"
)
var bs = &bckMsg.BodyStructure{} //nolint[gochecknoglobals]
var bs = &pkgMsg.BodyStructure{} //nolint[gochecknoglobals]
const testUID = "testmsg"
func TestSaveAndLoad(t *testing.T) {

View File

@ -197,8 +197,8 @@ func (im *imapMailbox) Expunge() error {
// UIDExpunge permanently removes messages that have the \Deleted flag set
// and UID passed from SeqSet from the currently selected mailbox.
func (im *imapMailbox) UIDExpunge(seqSet *imap.SeqSet) error {
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
im.user.backend.updates.block(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
defer im.user.backend.updates.unblock(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
messageIDs, err := im.apiIDsFromSeqSet(true, seqSet)
if err != nil || len(messageIDs) == 0 {

View File

@ -328,12 +328,21 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
var body []byte
structure, body, err = im.buildMessage(m)
m.Size = int64(len(body))
// Save size and body structure even for messages unable to decrypt
// so the size or body structure doesn't have to be computed every time.
if err := storeMessage.SetSize(m.Size); err != nil {
im.log.WithError(err).
WithField("newSize", m.Size).
WithField("msgID", m.ID).
Warn("Cannot update size while building")
}
if structure != nil && !isMessageInDraftFolder(m) {
if err := storeMessage.SetBodyStructure(structure); err != nil {
im.log.WithError(err).
WithField("msgID", m.ID).
Warn("Cannot update bodystructure while building")
}
}
if err == nil && structure != nil && len(body) > 0 {
if err := storeMessage.SetContentTypeAndHeader(m.MIMEType, m.Header); err != nil {
im.log.WithError(err).
@ -342,11 +351,6 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
}
// Drafts can change and we don't want to cache them.
if !isMessageInDraftFolder(m) {
if err := storeMessage.SetBodyStructure(structure); err != nil {
im.log.WithError(err).
WithField("msgID", m.ID).
Warn("Cannot update bodystructure while building")
}
cache.SaveMail(id, body, structure)
}
bodyReader = bytes.NewReader(body)

View File

@ -23,6 +23,7 @@ import (
"io"
"net"
"strings"
"sync/atomic"
"time"
imapid "github.com/ProtonMail/go-imap-id"
@ -31,6 +32,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/imap/id"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/emersion/go-imap"
imapappendlimit "github.com/emersion/go-imap-appendlimit"
imapidle "github.com/emersion/go-imap-idle"
@ -48,6 +50,8 @@ type imapServer struct {
eventListener listener.Listener
debugClient bool
debugServer bool
port int
isRunning atomic.Value
}
// NewIMAPServer constructs a new IMAP server configured with the given options.
@ -96,13 +100,16 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
uidplus.NewExtension(),
)
return &imapServer{
server := &imapServer{
panicHandler: panicHandler,
server: s,
eventListener: eventListener,
debugClient: debugClient,
debugServer: debugServer,
port: port,
}
server.isRunning.Store(false)
return server
}
// Starts the server.
@ -114,6 +121,11 @@ func (s *imapServer) ListenAndServe() {
}
func (s *imapServer) listenAndServe() {
if s.isRunning.Load().(bool) {
return
}
s.isRunning.Store(true)
log.Info("IMAP server listening at ", s.server.Addr)
l, err := net.Listen("tcp", s.server.Addr)
if err != nil {
@ -126,19 +138,13 @@ func (s *imapServer) listenAndServe() {
Listener: l,
server: s,
})
if err != nil {
failed := true
if netErr, ok := err.(*net.OpError); ok {
originalErr := netErr.Unwrap()
if originalErr != nil && originalErr.Error() == "use of closed network connection" {
failed = false
}
}
if failed {
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
log.Error("IMAP failed: ", err)
return
}
// Serve returns error every time, even after closing the server.
// User shouldn't be notified about error if server shouldn't be running,
// but it should in case it was not closed by `s.Close()`.
if err != nil && s.isRunning.Load().(bool) {
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
log.Error("IMAP failed: ", err)
return
}
defer s.server.Close() //nolint[errcheck]
@ -147,6 +153,11 @@ func (s *imapServer) listenAndServe() {
// Stops the server.
func (s *imapServer) Close() {
if !s.isRunning.Load().(bool) {
return
}
s.isRunning.Store(false)
log.Info("Closing IMAP server")
if err := s.server.Close(); err != nil {
log.WithError(err).Error("Failed to close the connection")
@ -159,29 +170,32 @@ func (s *imapServer) monitorInternetConnection() {
off := make(chan string)
s.eventListener.Add(events.InternetOffEvent, off)
isOn := true
for {
var expectedIsPortFree bool
select {
case <-on:
if isOn {
continue
}
isOn = true
go func() {
defer s.panicHandler.HandlePanic()
s.listenAndServe()
}()
expectedIsPortFree = false
case <-off:
if !isOn {
continue
}
isOn = false
s.Close()
expectedIsPortFree = true
}
start := time.Now()
for {
if ports.IsPortFree(s.port) == expectedIsPortFree {
break
}
// Safety stop if something went wrong.
if time.Since(start) > 15*time.Second {
log.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted")
break
}
time.Sleep(100 * time.Millisecond)
}
// Give it some time to serve or close server before changing it again.
// E.g., if we get quickly off-on signal, starting or closing could
// fail because server is still running or not yet, respectively.
time.Sleep(10 * time.Second)
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package imap
import (
"fmt"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports"
imapserver "github.com/emersion/go-imap/server"
"github.com/stretchr/testify/require"
)
type testPanicHandler struct{}
func (ph *testPanicHandler) HandlePanic() {}
func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
panicHandler := &testPanicHandler{}
eventListener := listener.New()
port := ports.FindFreePortFrom(12345)
server := imapserver.New(nil)
server.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
s := &imapServer{
panicHandler: panicHandler,
server: server,
eventListener: eventListener,
}
s.isRunning.Store(false)
go s.ListenAndServe()
time.Sleep(5 * time.Second)
require.False(t, ports.IsPortFree(port))
eventListener.Emit(events.InternetOffEvent, "")
time.Sleep(10 * time.Second)
require.True(t, ports.IsPortFree(port))
eventListener.Emit(events.InternetOnEvent, "")
time.Sleep(10 * time.Second)
require.False(t, ports.IsPortFree(port))
}

View File

@ -24,7 +24,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/internal/store"
backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
@ -100,8 +100,8 @@ type storeMessageProvider interface {
SetSize(int64) error
SetContentTypeAndHeader(string, mail.Header) error
SetBodyStructure(*backendMessage.BodyStructure) error
GetBodyStructure() (*backendMessage.BodyStructure, error)
SetBodyStructure(*pkgMsg.BodyStructure) error
GetBodyStructure() (*pkgMsg.BodyStructure, error)
}
type storeUserWrap struct {

View File

@ -0,0 +1,60 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package imap
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestUpdatesCanDelete(t *testing.T) {
u := newIMAPUpdates()
can, _ := u.CanDelete("mbox")
require.True(t, can)
u.forbidExpunge("mbox")
u.allowExpunge("mbox")
can, _ = u.CanDelete("mbox")
require.True(t, can)
}
func TestUpdatesCannotDelete(t *testing.T) {
u := newIMAPUpdates()
u.forbidExpunge("mbox")
can, wait := u.CanDelete("mbox")
require.False(t, can)
ch := make(chan time.Duration)
go func() {
start := time.Now()
wait()
ch <- time.Since(start)
close(ch)
}()
time.Sleep(200 * time.Millisecond)
u.allowExpunge("mbox")
duration := <-ch
require.True(t, duration > 200*time.Millisecond)
}

View File

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./credits.sh at Mon Jan 4 03:19:07 PM CET 2021. DO NOT EDIT.
// Code generated by ./credits.sh at Fri Jan 22 11:28:55 CET 2021. DO NOT EDIT.
package importexport
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli/v2;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;github.com/urfave/cli/v2;github.com/vmihailenco/msgpack/v5;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"

View File

@ -31,7 +31,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/message/parser"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
goSMTPBackend "github.com/emersion/go-smtp"
@ -229,7 +229,7 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
err = errors.Wrap(err, "failed to create new parser")
return
}
message, plainBody, attReaders, err := pkgMessage.ParserWithParser(parser)
message, plainBody, attReaders, err := pkgMsg.ParserWithParser(parser)
if err != nil {
log.WithError(err).Error("Failed to parse message")
return
@ -273,10 +273,10 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
}
if attachedPublicKey != "" {
pkgMessage.AttachPublicKey(parser, attachedPublicKey, attachedPublicKeyName)
pkgMsg.AttachPublicKey(parser, attachedPublicKey, attachedPublicKeyName)
}
mimeBody, err := pkgMessage.BuildMIMEBody(parser)
mimeBody, err := pkgMsg.BuildMIMEBody(parser)
if err != nil {
log.WithError(err).Error("Failed to build message")
return

View File

@ -82,22 +82,92 @@ func TestEventLoopUpdateMessageFromLoop(t *testing.T) {
Subject: subject,
})
testEvent(t, m, &pmapi.Event{
EventID: "event1",
Messages: []*pmapi.EventMessage{{
EventItem: pmapi.EventItem{
ID: "msg1",
Action: pmapi.EventUpdate,
},
Updated: &pmapi.EventMessageUpdated{
ID: "msg1",
Subject: &newSubject,
},
}},
})
msg, err := m.store.getMessageFromDB("msg1")
require.NoError(t, err)
require.Equal(t, newSubject, msg.Subject)
}
func TestEventLoopDeletionNotPaused(t *testing.T) {
m, clear := initMocks(t)
defer clear()
m.newStoreNoEvents(true, &pmapi.Message{
ID: "msg1",
Subject: "subject",
LabelIDs: []string{"label"},
})
m.changeNotifier.EXPECT().CanDelete("label").Return(true, func() {})
m.store.SetChangeNotifier(m.changeNotifier)
testEvent(t, m, &pmapi.Event{
EventID: "event1",
Messages: []*pmapi.EventMessage{{
EventItem: pmapi.EventItem{
ID: "msg1",
Action: pmapi.EventDelete,
},
}},
})
_, err := m.store.getMessageFromDB("msg1")
require.Error(t, err)
}
func TestEventLoopDeletionPaused(t *testing.T) {
m, clear := initMocks(t)
defer clear()
m.newStoreNoEvents(true, &pmapi.Message{
ID: "msg1",
Subject: "subject",
LabelIDs: []string{"label"},
})
delay := 5 * time.Second
m.changeNotifier.EXPECT().CanDelete("label").Return(false, func() {
time.Sleep(delay)
})
m.changeNotifier.EXPECT().CanDelete("label").Return(true, func() {})
m.store.SetChangeNotifier(m.changeNotifier)
start := time.Now()
testEvent(t, m, &pmapi.Event{
EventID: "event1",
Messages: []*pmapi.EventMessage{{
EventItem: pmapi.EventItem{
ID: "msg1",
Action: pmapi.EventDelete,
},
}},
})
_, err := m.store.getMessageFromDB("msg1")
require.Error(t, err)
require.True(t, time.Since(start) > delay)
}
func testEvent(t *testing.T, m *mocksForStore, event *pmapi.Event) {
eventReceived := make(chan struct{})
m.client.EXPECT().GetEvent("latestEventID").DoAndReturn(func(eventID string) (*pmapi.Event, error) {
defer close(eventReceived)
return &pmapi.Event{
EventID: "event1",
Messages: []*pmapi.EventMessage{{
EventItem: pmapi.EventItem{
ID: "msg1",
Action: pmapi.EventUpdate,
},
Updated: &pmapi.EventMessageUpdated{
ID: "msg1",
Subject: &newSubject,
},
}},
}, nil
return event, nil
})
// Event loop runs in goroutine started during store creation (newStoreNoEvents).
@ -109,10 +179,6 @@ func TestEventLoopUpdateMessageFromLoop(t *testing.T) {
case <-time.After(5 * time.Second):
require.Fail(t, "latestEventID was not processed")
}
msg, err := m.store.getMessageFromDB("msg1")
require.NoError(t, err)
require.Equal(t, newSubject, msg.Subject)
}
func TestEventLoopUpdateMessage(t *testing.T) {

View File

@ -20,7 +20,7 @@ package store
import (
"net/mail"
backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
bolt "go.etcd.io/bbolt"
)
@ -121,8 +121,8 @@ func (message *Message) SetContentTypeAndHeader(mimeType string, header mail.Hea
return message.store.db.Update(txUpdate)
}
// SetBodyStructure stores serialized body structure in database
func (message *Message) SetBodyStructure(bs *backendMessage.BodyStructure) error {
// SetBodyStructure stores serialized body structure in database.
func (message *Message) SetBodyStructure(bs *pkgMsg.BodyStructure) error {
txUpdate := func(tx *bolt.Tx) error {
return message.store.txPutBodyStructure(
tx.Bucket(bodystructureBucket),
@ -132,10 +132,10 @@ func (message *Message) SetBodyStructure(bs *backendMessage.BodyStructure) error
return message.store.db.Update(txUpdate)
}
// GetBodyStructure deserialize body structure from database. If body structure
// GetBodyStructure deserializes body structure from database. If body structure
// is not in database it returns nil error and nil body structure. If error
// occurs it returns nil body structure.
func (message *Message) GetBodyStructure() (bs *backendMessage.BodyStructure, err error) {
func (message *Message) GetBodyStructure() (bs *pkgMsg.BodyStructure, err error) {
txRead := func(tx *bolt.Tx) error {
bs, err = message.store.txGetBodyStructure(
tx.Bucket(bodystructureBucket),

View File

@ -51,6 +51,8 @@ var (
// Database structure:
// * metadata
// * {messageID} -> message data (subject, from, to, time, headers, body size, ...)
// * bodystructure
// * {messageID} -> message body structure
// * counts
// * {mailboxID} -> mailboxCounts: totalOnAPI, unreadOnAPI, labelName, labelColor, labelIsExclusive
// * address_info

View File

@ -103,7 +103,7 @@ func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool, msgs ...*pmapi.M
{ID: addrID1, Email: addr1, Type: pmapi.OriginalAddress, Receive: pmapi.CanReceive},
{ID: addrID2, Email: addr2, Type: pmapi.AliasAddress, Receive: pmapi.CanReceive},
})
mocks.client.EXPECT().ListLabels()
mocks.client.EXPECT().ListLabels().AnyTimes()
mocks.client.EXPECT().CountMessages("")
// Call to get latest event ID and then to process first event.

View File

@ -27,7 +27,7 @@ import (
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -171,7 +171,7 @@ func (store *Store) txPutMessage(metaBucket *bolt.Bucket, onlyMeta *pmapi.Messag
return nil
}
func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *backendMessage.BodyStructure) error {
func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *pkgMsg.BodyStructure) error {
raw, err := bs.Serialize()
if err != nil {
return err
@ -183,12 +183,12 @@ func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *
return nil
}
func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*backendMessage.BodyStructure, error) {
func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*pkgMsg.BodyStructure, error) {
raw := bsBucket.Get([]byte(msgID))
if len(raw) == 0 {
return nil, nil
}
return backendMessage.DeserializeBodyStructure(raw)
return pkgMsg.DeserializeBodyStructure(raw)
}
// createOrUpdateMessageEvent is helper to create only one message with

View File

@ -21,7 +21,7 @@ import (
"fmt"
"sync"
pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -153,7 +153,7 @@ func (p *PMAPIProvider) exportMessage(rule *Rule, progress *Progress, pmapiMsgID
p.timeIt.start("build", msgID)
defer p.timeIt.stop("build", msgID)
msgBuilder := pkgMessage.NewBuilder(p.client(), msg)
msgBuilder := pkgMsg.NewBuilder(p.client(), msg)
msgBuilder.EncryptedToHTML = false
_, body, err := msgBuilder.BuildMessage()
if err != nil {

View File

@ -24,7 +24,7 @@ import (
"io/ioutil"
"sync"
pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
)
@ -246,7 +246,7 @@ func (p *PMAPIProvider) generateImportMsgReq(rules transferRules, progress *Prog
func (p *PMAPIProvider) parseMessage(msg Message) (m *pmapi.Message, r []io.Reader, err error) {
p.timeIt.start("parse", msg.ID)
defer p.timeIt.stop("parse", msg.ID)
message, _, _, attachmentReaders, err := pkgMessage.Parse(bytes.NewBuffer(msg.Body))
message, _, _, attachmentReaders, err := pkgMsg.Parse(bytes.NewBuffer(msg.Body))
return message, attachmentReaders, err
}
@ -254,7 +254,7 @@ func (p *PMAPIProvider) encryptMessage(msg *pmapi.Message, attachmentReaders []i
if msg.MIMEType == pmapi.ContentTypeMultipartEncrypted {
return []byte(msg.Body), nil
}
return pkgMessage.BuildEncrypted(msg, attachmentReaders, p.keyRing)
return pkgMsg.BuildEncrypted(msg, attachmentReaders, p.keyRing)
}
func computeMessageFlags(labels []string) (flag int64) {

View File

@ -152,7 +152,7 @@ func (u *User) authorizeIfNecessary(emitEvent bool) (err error) {
u.log.WithError(err).Error("Could not authorize and unlock user")
switch errors.Cause(err) {
case pmapi.ErrUpgradeApplication, pmapi.ErrAPINotReachable:
case pmapi.ErrUpgradeApplication, pmapi.ErrAPINotReachable: // Ignore these errors.
default:
if errLogout := u.credStorer.Logout(u.userID); errLogout != nil {
u.log.WithField("err", errLogout).Error("Could not log user out from credentials store")

View File

@ -132,7 +132,9 @@ func (cm *ClientManager) noConnection() {
}
cm.log.Warn("Connection lost")
cm.config.ConnectionOffHandler()
if cm.config.ConnectionOffHandler != nil {
cm.config.ConnectionOffHandler()
}
cm.connectionOff = true
go func() {
@ -141,7 +143,9 @@ func (cm *ClientManager) noConnection() {
if err := cm.CheckConnection(); err == nil {
cm.log.Info("Connection re-established")
cm.config.ConnectionOnHandler()
if cm.config.ConnectionOnHandler != nil {
cm.config.ConnectionOnHandler()
}
cm.connectionOff = false
return
}

View File

@ -32,9 +32,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-854 EXPUNGE and FETCH unilateral responses are returned before OK EXPUNGE or OK STORE, respectively.
* GODT-806 Changed GUI dialog on manual update. Added autoupdates checkbox. Simplifyed installation process GUI.
* Bump gopenpgp dependency to v2.1.3 for improved memory usage.
* GODT-912 Changed scroll bar behaviour in settings tab
* GODT-912 Changed scroll bar behaviour in settings tab.
* GODT-149 Send heartbeat ASAP on each new calendar day.
* GODT-792 GODT-908 Cache body structure in order to reduce network traffic.
* GODT-792 Stop IMAP server while no internet connection.
* GODT-792 Cache message size every time to reduce network traffic.
* GODT-792 Cache body structure in order to reduce network traffic.
* GODT-908 Do not unpause event loop if other mailbox is still fetching.
### Removed
* GODT-208 Remove deprecated use of BuildNameToCertificate.
@ -47,7 +51,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-898 Only set ContentID for inline attachments.
* GODT-773 Replace `INTERNALDATE` older than birthday of RFC822 by birthday of RFC822 to not crash Apple Mail.
* GODT-927 Avoid to call API with empty label name.
* GODT-732 Fix usage of fontawesome
* GODT-732 Fix usage of fontawesome.
* GODT-915 Bump go-imap dependency and remove go-imap-specialuse dependency.
* GODT-831 Cancel request of uploading attachment if reading/writing it fails.