mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 767628946f | |||
| d4efa7131f | |||
| 144cf6e40c | |||
| a205d8c046 | |||
| cccadaee42 | |||
| bbb365f8a5 | |||
| 1f18d9d917 | |||
| 59e0d63485 | |||
| 72fe5a636e | |||
| 45a83133ba | |||
| 215eb4d6eb | |||
| 479b951c50 | |||
| a94c8a943f | |||
| ea306f405e | |||
| 1b405506b8 | |||
| 38c6132f81 | |||
| b7351dfaf8 | |||
| 7e8f6943f2 | |||
| a0132e8440 | |||
| 27541784aa | |||
| 9e567f08b2 | |||
| bf274f984e | |||
| 3b60bbe13b | |||
| a73a1b623a | |||
| c0a8877018 | |||
| 904166c01c | |||
| 4761bc935a | |||
| 71301d891f | |||
| d47be3c4c0 |
65
Changelog.md
65
Changelog.md
@ -2,6 +2,71 @@
|
||||
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Bridge 1.6.9] HZM
|
||||
|
||||
### Fixed
|
||||
* GODT-1121 'Keep the application up to date' switches off after restarting Bridge.
|
||||
|
||||
|
||||
## [Bridge 1.6.8] HZM
|
||||
|
||||
### Fixed
|
||||
* GODT-1120 Use Info level in internal/app logs.
|
||||
|
||||
|
||||
## [IE 1.3.3] Farg
|
||||
|
||||
### Fixed
|
||||
* GODT-1120 Use Info level in internal/app logs.
|
||||
|
||||
|
||||
## [Bridge 1.6.7] HZM
|
||||
|
||||
### Added
|
||||
* GODT-1111 Add correct metadata to Windows executables.
|
||||
* GODT-1112 Add application to Windows Firewall exclusion list on install.
|
||||
* GODT-1077 Track how many times message is built to help understand re-syncs.
|
||||
|
||||
### Changed
|
||||
* GODT-247 Revise all storage locations (cache, config, local etc).
|
||||
|
||||
### Fixed
|
||||
* GODT-948 Parser does not handle embedding of Content-Type: message/rfc822.
|
||||
* GODT-1079 Correct 9001 error handling on login.
|
||||
|
||||
### Security
|
||||
* GODT-1105 Dylib Hijacking security fix.
|
||||
|
||||
|
||||
## [IE 1.3.2] Farg
|
||||
|
||||
### Added
|
||||
* GODT-1111 Add correct metadata to Windows executables.
|
||||
* GODT-1112 Add application to Windows Firewall exclusion list on install.
|
||||
|
||||
### Changed
|
||||
* GODT-247 Revise all storage locations (cache, config, local etc).
|
||||
|
||||
### Fixed
|
||||
* GODT-1079 Correct 9001 error handling on login.
|
||||
|
||||
### Security
|
||||
* GODT-1105 Dylib Hijacking security fix.
|
||||
|
||||
|
||||
## [IE 1.3.1] Farg
|
||||
|
||||
### Changed
|
||||
* GODT-1047 No silent updates for Import-Export app.
|
||||
* GODT-247 Cache and update files moved from user's cache to config.
|
||||
|
||||
### Fixed
|
||||
* Other: include latest go.mod/go.sum changes.
|
||||
* GODT-803 Fix import to wrong target address.
|
||||
* GODT-948 Embedded messages.
|
||||
* GODT-1043 Fix showing long login error in GUI dialog.
|
||||
|
||||
|
||||
## [Bridge 1.6.6] HZM
|
||||
|
||||
### Added
|
||||
|
||||
42
Makefile
42
Makefile
@ -10,8 +10,8 @@ TARGET_OS?=${GOOS}
|
||||
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=1.6.6+git
|
||||
IE_APP_VERSION?=1.3.0+git
|
||||
BRIDGE_APP_VERSION?=1.6.9+git
|
||||
IE_APP_VERSION?=1.3.3+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
SRC_ICO:=logo.ico
|
||||
SRC_ICNS:=Bridge.icns
|
||||
@ -19,6 +19,7 @@ SRC_SVG:=logo.svg
|
||||
TGT_ICNS:=Bridge.icns
|
||||
EXE_NAME:=proton-bridge
|
||||
CONFIGNAME:=bridge
|
||||
WINDRES_DEFINE:=BUILD_BRIDGE
|
||||
ifeq "${TARGET_CMD}" "Import-Export"
|
||||
APP_VERSION:=${IE_APP_VERSION}
|
||||
SRC_ICO:=ie.ico
|
||||
@ -27,6 +28,7 @@ ifeq "${TARGET_CMD}" "Import-Export"
|
||||
TGT_ICNS:=ImportExport.icns
|
||||
EXE_NAME:=proton-ie
|
||||
CONFIGNAME:=importExport
|
||||
WINDRES_DEFINE:=BUILD_IE
|
||||
endif
|
||||
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||
@ -56,7 +58,7 @@ EXE_QT:=${DIRNAME}
|
||||
ifeq "${TARGET_OS}" "windows"
|
||||
EXE:=${EXE}.exe
|
||||
EXE_QT:=${EXE_QT}.exe
|
||||
ICO_FILES:=${SRC_ICO} icon.rc icon_windows.syso
|
||||
RESOURCE_FILE:=resource.syso
|
||||
endif
|
||||
ifeq "${TARGET_OS}" "darwin"
|
||||
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
|
||||
@ -89,8 +91,14 @@ build-nogui: gofiles
|
||||
build-ie-nogui:
|
||||
TARGET_CMD=Import-Export $(MAKE) build-nogui
|
||||
|
||||
build-launcher:
|
||||
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${APP} cmd/launcher/main.go
|
||||
ifeq "${GOOS}" "windows"
|
||||
PRERESOURCECMD:=cp ./resource.syso ./cmd/launcher/resource.syso
|
||||
POSTRESOURCECMD:=rm -f ./cmd/launcher/resource.syso
|
||||
endif
|
||||
build-launcher: ${RESOURCE_FILE}
|
||||
${PRERESOURCECMD}
|
||||
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${EXE} ./cmd/launcher/
|
||||
${POSTRESOURCECMD}
|
||||
|
||||
build-launcher-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) build-launcher
|
||||
@ -134,7 +142,7 @@ ifneq "${GOOS}" "${TARGET_OS}"
|
||||
endif
|
||||
endif
|
||||
|
||||
${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} ${VENDOR_TARGET}
|
||||
${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
|
||||
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
|
||||
cp cmd/${TARGET_CMD}/main.go .
|
||||
qtdeploy ${BUILD_FLAGS_GUI} ${QT_BUILD_TARGET}
|
||||
@ -142,13 +150,12 @@ ${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} ${VENDOR_TARGET}
|
||||
if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi
|
||||
rm -rf ${TARGET_OS} main.go
|
||||
|
||||
logo.ico ie.ico: ./internal/frontend/share/icons/${SRC_ICO}
|
||||
cp $^ $@
|
||||
icon.rc: ./internal/frontend/share/icon.rc
|
||||
cp $^ .
|
||||
icon_windows.syso: icon.rc logo.ico
|
||||
windres --target=pe-x86-64 -o $@ $<
|
||||
|
||||
WINDRES_YEAR:=$(shell date +%Y)
|
||||
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/icons/${SRC_ICO} .FORCE
|
||||
rm -f ./*.syso
|
||||
windres --target=pe-x86-64 -I ./internal/frontend/share/icons/ -D ${WINDRES_DEFINE} -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
||||
|
||||
## Rules for therecipe/qt
|
||||
.PHONY: prepare-vendor update-vendor update-qt-docs
|
||||
@ -294,6 +301,7 @@ 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_FLAGS_IE?=-m -l=${LOG}
|
||||
|
||||
run: run-nogui-cli
|
||||
|
||||
@ -316,11 +324,11 @@ run-ie-qml-preview:
|
||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
|
||||
|
||||
run-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) run
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run
|
||||
run-ie-qt:
|
||||
TARGET_CMD=Import-Export $(MAKE) run-qt
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-qt
|
||||
run-ie-nogui:
|
||||
TARGET_CMD=Import-Export $(MAKE) run-nogui
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-nogui
|
||||
|
||||
clean-frontend-qt:
|
||||
$(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
||||
@ -337,7 +345,7 @@ clean: clean-vendor
|
||||
rm -rf cmd/Desktop-Bridge/deploy
|
||||
rm -rf cmd/Import-Export/deploy
|
||||
rm -f build last.log mem.pprof main.go
|
||||
rm -rf logo.ico icon.rc icon_windows.syso internal/frontend/qt/icon_windows.syso
|
||||
rm -f resource.syso
|
||||
rm -f release-notes/bridge.html
|
||||
rm -f release-notes/import-export.html
|
||||
|
||||
@ -345,3 +353,5 @@ clean: clean-vendor
|
||||
generate:
|
||||
go generate ./...
|
||||
$(MAKE) add-license
|
||||
|
||||
.FORCE:
|
||||
|
||||
3
go.mod
3
go.mod
@ -54,12 +54,13 @@ require (
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/math v0.0.0-20141027224758-f2ed9e40e245
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.1.3
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
|
||||
8
go.sum
8
go.sum
@ -223,8 +223,6 @@ github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTw
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/math v0.0.0-20141027224758-f2ed9e40e245 h1:gk/AF9SGRj+RafNCoDcS3RRscb8S4BVbvqODOgWA7/8=
|
||||
github.com/pkg/math v0.0.0-20141027224758-f2ed9e40e245/go.mod h1:2dhPPj2Li3DXrSY2U2ADdZy2B7sjQsT57lqENx1+FSE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
@ -264,6 +262,12 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
|
||||
github.com/therecipe/qt/internal/binding/files/docs v0.0.0-20191019224306-1097424d656c h1:/VhcwU7WuFEVgDHZ9V8PIYAyYqQ6KNxFUjBMOf2aFZM=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
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=
|
||||
|
||||
@ -29,10 +29,12 @@ import (
|
||||
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
|
||||
// We can remove this eventually.
|
||||
//
|
||||
// | entity | old location | new location |
|
||||
// |--------|-------------------------------------------|----------------------------------------|
|
||||
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
||||
// | c11 | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
||||
// | entity | old location | new location |
|
||||
// |-----------|-------------------------------------------|----------------------------------------|
|
||||
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
||||
// | c11 1.5.x | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
||||
// | c11 1.6.x | ~/.cache/protonmail/<app>/cache/c11 | ~/.config/protonmail/<app>/cache/c11 |
|
||||
// | updates | ~/.cache/protonmail/<app>/updates | ~/.config/protonmail/<app>/updates |
|
||||
func migrateFiles(configName string) error {
|
||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
||||
if err != nil {
|
||||
@ -41,43 +43,89 @@ func migrateFiles(configName string) error {
|
||||
|
||||
locations := locations.New(locationsProvider, configName)
|
||||
userCacheDir := locationsProvider.UserCache()
|
||||
|
||||
if err := migratePrefsFrom15x(locations, userCacheDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateCacheFromBoth15xAnd16x(locations, userCacheDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateUpdatesFrom16x(configName, locations); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migratePrefsFrom15x(locations *locations.Locations, userCacheDir string) error {
|
||||
newSettingsDir, err := locations.ProvideSettingsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := moveIfExists(
|
||||
return moveIfExists(
|
||||
filepath.Join(userCacheDir, "c11", "prefs.json"),
|
||||
filepath.Join(newSettingsDir, "prefs.json"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
newCacheDir, err := locations.ProvideCachePath()
|
||||
func migrateCacheFromBoth15xAnd16x(locations *locations.Locations, userCacheDir string) error {
|
||||
olderCacheDir := userCacheDir
|
||||
newerCacheDir := locations.GetOldCachePath()
|
||||
latestCacheDir, err := locations.ProvideCachePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Migration for versions before 1.6.x.
|
||||
if err := moveIfExists(
|
||||
filepath.Join(userCacheDir, "c11"),
|
||||
filepath.Join(newCacheDir, "c11"),
|
||||
filepath.Join(olderCacheDir, "c11"),
|
||||
filepath.Join(latestCacheDir, "c11"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// Migration for versions 1.6.x.
|
||||
return moveIfExists(
|
||||
filepath.Join(newerCacheDir, "c11"),
|
||||
filepath.Join(latestCacheDir, "c11"),
|
||||
)
|
||||
}
|
||||
|
||||
func migrateUpdatesFrom16x(configName string, locations *locations.Locations) error {
|
||||
// In order to properly update Bridge 1.6.X and higher we need to
|
||||
// change the launcher first. Since this is not part of automatic
|
||||
// updates the migration must wait until manual update. Until that
|
||||
// we need to keep old path.
|
||||
if configName == "bridge" {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldUpdatesPath := locations.GetOldUpdatesPath()
|
||||
// Do not use ProvideUpdatesPath, that creates dir right away.
|
||||
newUpdatesPath := locations.GetUpdatesPath()
|
||||
|
||||
return moveIfExists(oldUpdatesPath, newUpdatesPath)
|
||||
}
|
||||
|
||||
func moveIfExists(source, destination string) error {
|
||||
l := logrus.WithField("source", source).WithField("destination", destination)
|
||||
|
||||
if _, err := os.Stat(source); os.IsNotExist(err) {
|
||||
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
||||
l.Info("No need to migrate file, source doesn't exist")
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
||||
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
||||
// Once migrated, files should not stay in source anymore. Therefore
|
||||
// if some files are still in source location but target already exist,
|
||||
// it's suspicious. Could happen by installing new version, then the
|
||||
// old one because of some reason, and then the new one again.
|
||||
// Good to see as warning because it could be a reason why Bridge is
|
||||
// behaving weirdly, like wrong configuration, or db re-sync and so on.
|
||||
l.Warn("No need to migrate file, target already exists")
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Info("Migrating files")
|
||||
return os.Rename(source, destination)
|
||||
}
|
||||
|
||||
@ -189,9 +189,10 @@ func generateTLSCerts(b *base.Base) error {
|
||||
}
|
||||
|
||||
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) {
|
||||
log := logrus.WithField("pkg", "app/bridge")
|
||||
version, err := u.Check()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("An error occurred while checking for updates")
|
||||
log.WithError(err).Error("An error occurred while checking for updates")
|
||||
return
|
||||
}
|
||||
|
||||
@ -201,11 +202,11 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
|
||||
f.SetVersion(version)
|
||||
|
||||
if !u.IsUpdateApplicable(version) {
|
||||
logrus.Debug("No need to update")
|
||||
log.Info("No need to update")
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithField("version", version.Version).Info("An update is available")
|
||||
log.WithField("version", version.Version).Info("An update is available")
|
||||
|
||||
if !autoUpdate {
|
||||
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||
@ -213,16 +214,16 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
|
||||
}
|
||||
|
||||
if !u.CanInstall(version) {
|
||||
logrus.Info("A manual update is required")
|
||||
log.Info("A manual update is required")
|
||||
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.InstallUpdate(version); err != nil {
|
||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||
logrus.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
log.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
} else {
|
||||
logrus.WithError(err).Error("The update couldn't be installed")
|
||||
log.WithError(err).Error("The update couldn't be installed")
|
||||
f.NotifySilentUpdateError(err)
|
||||
}
|
||||
|
||||
|
||||
@ -28,8 +28,6 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -88,10 +86,11 @@ func run(b *base.Base, c *cli.Context) error {
|
||||
return f.Loop()
|
||||
}
|
||||
|
||||
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) {
|
||||
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) { //nolint[unparam]
|
||||
log := logrus.WithField("pkg", "app/ie")
|
||||
version, err := u.Check()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("An error occurred while checking for updates")
|
||||
log.WithError(err).Error("An error occurred while checking for updates")
|
||||
return
|
||||
}
|
||||
|
||||
@ -101,33 +100,11 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
|
||||
f.SetVersion(version)
|
||||
|
||||
if !u.IsUpdateApplicable(version) {
|
||||
logrus.Debug("No need to update")
|
||||
log.Info("No need to update")
|
||||
return
|
||||
}
|
||||
|
||||
logrus.WithField("version", version.Version).Info("An update is available")
|
||||
log.WithField("version", version.Version).Info("An update is available")
|
||||
|
||||
if !autoUpdate {
|
||||
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||
return
|
||||
}
|
||||
|
||||
if !u.CanInstall(version) {
|
||||
logrus.Info("A manual update is required")
|
||||
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.InstallUpdate(version); err != nil {
|
||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||
logrus.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
} else {
|
||||
logrus.WithError(err).Error("The update couldn't be installed")
|
||||
f.NotifySilentUpdateError(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
f.NotifySilentUpdateInstalled()
|
||||
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ func (f *frontendCLI) importLocalMessages(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetLocalImporter(user.GetPrimaryAddress(), path)
|
||||
t, err := f.ie.GetLocalImporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, false, true)
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ func (f *frontendCLI) importRemoteMessages(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetRemoteImporter(user.GetPrimaryAddress(), username, password, host, port)
|
||||
t, err := f.ie.GetRemoteImporter(user.Username(), user.GetPrimaryAddress(), username, password, host, port)
|
||||
f.transfer(t, err, false, true)
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ func (f *frontendCLI) exportMessagesToEML(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetEMLExporter(user.GetPrimaryAddress(), path)
|
||||
t, err := f.ie.GetEMLExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, true, false)
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func (f *frontendCLI) exportMessagesToMBOX(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetMBOXExporter(user.GetPrimaryAddress(), path)
|
||||
t, err := f.ie.GetMBOXExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, true, false)
|
||||
}
|
||||
|
||||
|
||||
@ -215,7 +215,7 @@ Item {
|
||||
}
|
||||
go.updateState = "updateRestart"
|
||||
winMain.dialogUpdate.finished(false)
|
||||
|
||||
|
||||
// after manual update - just retart immidiatly
|
||||
go.setToRestart()
|
||||
Qt.quit()
|
||||
@ -236,13 +236,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
onNotifySilentUpdateRestartNeeded: {
|
||||
go.updateState = "updateRestart"
|
||||
}
|
||||
|
||||
onNotifySilentUpdateError: {
|
||||
go.updateState = "updateError"
|
||||
}
|
||||
//onNotifySilentUpdateRestartNeeded: {
|
||||
// go.updateState = "updateRestart"
|
||||
//}
|
||||
//
|
||||
//onNotifySilentUpdateError: {
|
||||
// go.updateState = "updateError"
|
||||
//}
|
||||
|
||||
onNotifyLogout : {
|
||||
go.notifyBubble(0, qsTr("Account %1 has been disconnected. Please log in to continue to use the Import-Export app with this account.").arg(accname) )
|
||||
|
||||
@ -165,6 +165,7 @@ Column {
|
||||
textColor : Style.main.textBlue
|
||||
onClicked: {
|
||||
dialogExport.currentIndex = 0
|
||||
dialogExport.account = account
|
||||
dialogExport.address = account
|
||||
dialogExport.show()
|
||||
}
|
||||
@ -321,6 +322,7 @@ Column {
|
||||
textBold: true
|
||||
textColor: Style.main.textBlue
|
||||
onClicked: {
|
||||
dialogExport.account = account
|
||||
dialogExport.address = listalias[index]
|
||||
dialogExport.show()
|
||||
}
|
||||
@ -339,6 +341,7 @@ Column {
|
||||
textBold: true
|
||||
textColor: enabled ? Style.main.textBlue : Style.main.textDisabled
|
||||
onClicked: {
|
||||
dialogImport.account = account
|
||||
dialogImport.address = listalias[index]
|
||||
dialogImport.show()
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ Dialog {
|
||||
|
||||
title : set_title()
|
||||
|
||||
property string account
|
||||
property string address
|
||||
property alias finish: finish
|
||||
|
||||
@ -428,7 +429,7 @@ Dialog {
|
||||
onTriggered : {
|
||||
switch (currentIndex) {
|
||||
case 0:
|
||||
go.loadStructureForExport(root.address)
|
||||
go.loadStructureForExport(root.account, root.address)
|
||||
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0)
|
||||
break
|
||||
case 2:
|
||||
|
||||
@ -34,6 +34,7 @@ Dialog {
|
||||
|
||||
isDialogBusy: currentIndex==3 || currentIndex==4
|
||||
|
||||
property string account
|
||||
property string address
|
||||
property string inputPath : ""
|
||||
property bool isFromFile : inputEmail.text == "" && root.inputPath != ""
|
||||
@ -1032,6 +1033,7 @@ Dialog {
|
||||
root.isFromIMAP,
|
||||
root.inputPath,
|
||||
inputEmail.text, inputPassword.text, inputServer.text, inputPort.text,
|
||||
root.account,
|
||||
root.address
|
||||
)
|
||||
break
|
||||
|
||||
@ -96,6 +96,8 @@ Item {
|
||||
onClicked: bugreportWin.show()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
ButtonIconText {
|
||||
id: autoUpdates
|
||||
text: qsTr("Keep the application up to date", "label for toggle that activates and disables the automatic updates")
|
||||
@ -115,8 +117,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
ButtonIconText {
|
||||
id: cacheClear
|
||||
text: qsTr("Clear Cache")
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
// Dialog with adding new user
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import ProtonUI 1.0
|
||||
|
||||
@ -83,6 +84,9 @@ StackLayout {
|
||||
text : ""
|
||||
color: Style.main.textBlue
|
||||
visible: false
|
||||
width: root.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
// prevent any action below
|
||||
|
||||
@ -70,7 +70,8 @@ Dialog {
|
||||
id: topSep
|
||||
color : "transparent"
|
||||
width : Style.main.dummy
|
||||
height : root.height/2 - (dialogNameAndPassword.heightInputs)/2
|
||||
// Hacky hack: +10 is to make title of Dialog bigger so longer error can fit just fine.
|
||||
height : root.height/2 + 10 - (dialogNameAndPassword.heightInputs)/2
|
||||
}
|
||||
|
||||
InputField {
|
||||
|
||||
@ -107,7 +107,7 @@ Dialog {
|
||||
text: qsTr("Automatically update in the future", "Checkbox label for using autoupdates later on")
|
||||
checked: go.isAutoUpdate
|
||||
onToggled: go.toggleAutoUpdate()
|
||||
visible: !root.forceUpdate
|
||||
visible: !root.forceUpdate && (go.isAutoUpdate != undefined)
|
||||
}
|
||||
|
||||
Row {
|
||||
|
||||
@ -23,13 +23,13 @@ import QtQuick.Window 2.2
|
||||
|
||||
Window {
|
||||
id : testroot
|
||||
width : 100
|
||||
width : 150
|
||||
height : 600
|
||||
flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint
|
||||
visible : true
|
||||
title : "GUI test Window"
|
||||
color : "transparent"
|
||||
x : testgui.winMain.x - 120
|
||||
x : testgui.winMain.x - 170
|
||||
y : testgui.winMain.y
|
||||
|
||||
property bool newVersion : true
|
||||
@ -110,8 +110,8 @@ Window {
|
||||
ListElement { title: "NotifyManualUpdateRestart" }
|
||||
ListElement { title: "NotifyManualUpdateError" }
|
||||
ListElement { title: "ForceUpdate" }
|
||||
ListElement { title: "NotifySilentUpdateRestartNeeded" }
|
||||
ListElement { title: "NotifySilentUpdateError" }
|
||||
//ListElement { title: "NotifySilentUpdateRestartNeeded" }
|
||||
//ListElement { title: "NotifySilentUpdateError" }
|
||||
ListElement { title : "ImportStructure" }
|
||||
ListElement { title : "DraftImpFailed" }
|
||||
ListElement { title : "NoInterImp" }
|
||||
@ -183,12 +183,12 @@ Window {
|
||||
case "ForceUpdate" :
|
||||
go.notifyForceUpdate()
|
||||
break;
|
||||
case "NotifySilentUpdateRestartNeeded" :
|
||||
go.notifySilentUpdateRestartNeeded()
|
||||
break;
|
||||
case "NotifySilentUpdateError" :
|
||||
go.notifySilentUpdateError()
|
||||
break;
|
||||
//case "NotifySilentUpdateRestartNeeded" :
|
||||
//go.notifySilentUpdateRestartNeeded()
|
||||
//break;
|
||||
//case "NotifySilentUpdateError" :
|
||||
//go.notifySilentUpdateError()
|
||||
//break;
|
||||
case "ImportStructure" :
|
||||
testgui.winMain.dialogImport.address = "cuto@pm.com"
|
||||
testgui.winMain.dialogImport.show()
|
||||
@ -836,7 +836,7 @@ Window {
|
||||
id: go
|
||||
|
||||
property int isAutoStart : 1
|
||||
property bool isAutoUpdate : false
|
||||
//property bool isAutoUpdate : false
|
||||
property bool isFirstStart : false
|
||||
property string currentAddress : "none"
|
||||
//property string goos : "windows"
|
||||
@ -858,15 +858,15 @@ Window {
|
||||
|
||||
property string updateState
|
||||
property string updateVersion : "q0.1.0"
|
||||
property bool updateCanInstall: true
|
||||
property bool updateCanInstall: false
|
||||
property string updateLandingPage : "https://protonmail.com/import-export/download/"
|
||||
property string updateReleaseNotesLink : "https://protonmail.com/download/ie/release_notes.html"
|
||||
signal notifyManualUpdate()
|
||||
signal notifyManualUpdateRestartNeeded()
|
||||
signal notifyManualUpdateError()
|
||||
signal notifyForceUpdate()
|
||||
signal notifySilentUpdateRestartNeeded()
|
||||
signal notifySilentUpdateError()
|
||||
//signal notifySilentUpdateRestartNeeded()
|
||||
//signal notifySilentUpdateError()
|
||||
function checkForUpdates() {
|
||||
console.log("checkForUpdates")
|
||||
go.notifyVersionIsTheLatest()
|
||||
@ -1355,10 +1355,10 @@ Window {
|
||||
return !fname.includes("fail")
|
||||
}
|
||||
|
||||
onToggleAutoUpdate: {
|
||||
workAndClose()
|
||||
isAutoUpdate = (isAutoUpdate!=false) ? false : true
|
||||
console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
|
||||
}
|
||||
//onToggleAutoUpdate: {
|
||||
// workAndClose()
|
||||
// isAutoUpdate = (isAutoUpdate!=false) ? false : true
|
||||
// console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ const (
|
||||
TypeMBOX = "MBOX"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
||||
func (f *FrontendQt) LoadStructureForExport(username, addressOrID string) {
|
||||
errCode := errUnknownError
|
||||
var err error
|
||||
defer func() {
|
||||
@ -41,7 +41,7 @@ func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
||||
}
|
||||
}()
|
||||
|
||||
if f.transfer, err = f.ie.GetEMLExporter(addressOrID, ""); err != nil {
|
||||
if f.transfer, err = f.ie.GetEMLExporter(username, addressOrID, ""); err != nil {
|
||||
// The only error can be problem to load PM user and address.
|
||||
errCode = errPMLoadFailed
|
||||
return
|
||||
|
||||
@ -135,11 +135,11 @@ func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||
f.Qml.NotifySilentUpdateRestartNeeded()
|
||||
//f.Qml.NotifySilentUpdateRestartNeeded()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||
f.Qml.NotifySilentUpdateError()
|
||||
//f.Qml.NotifySilentUpdateError()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) watchEvents() {
|
||||
@ -245,11 +245,11 @@ func (f *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
||||
f.Qml.SetCredits(importexport.Credits)
|
||||
f.Qml.SetFullversion(f.buildVersion)
|
||||
|
||||
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||
f.Qml.SetIsAutoUpdate(true)
|
||||
} else {
|
||||
f.Qml.SetIsAutoUpdate(false)
|
||||
}
|
||||
//if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||
// f.Qml.SetIsAutoUpdate(true)
|
||||
//} else {
|
||||
// f.Qml.SetIsAutoUpdate(false)
|
||||
//}
|
||||
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
@ -339,17 +339,17 @@ func (f *FrontendQt) sendBug(description, emailClient, address string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleAutoUpdate() {
|
||||
defer f.Qml.ProcessFinished()
|
||||
|
||||
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||
f.settings.SetBool(settings.AutoUpdateKey, false)
|
||||
f.Qml.SetIsAutoUpdate(false)
|
||||
} else {
|
||||
f.settings.SetBool(settings.AutoUpdateKey, true)
|
||||
f.Qml.SetIsAutoUpdate(true)
|
||||
}
|
||||
}
|
||||
//func (f *FrontendQt) toggleAutoUpdate() {
|
||||
// defer f.Qml.ProcessFinished()
|
||||
//
|
||||
// if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||
// f.settings.SetBool(settings.AutoUpdateKey, false)
|
||||
// f.Qml.SetIsAutoUpdate(false)
|
||||
// } else {
|
||||
// f.settings.SetBool(settings.AutoUpdateKey, true)
|
||||
// f.Qml.SetIsAutoUpdate(true)
|
||||
// }
|
||||
//}
|
||||
|
||||
// checkInternet is almost idetical to bridge
|
||||
func (f *FrontendQt) checkInternet() {
|
||||
|
||||
@ -26,7 +26,7 @@ import (
|
||||
)
|
||||
|
||||
// wrapper for QML
|
||||
func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetAddress string) {
|
||||
func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetUsername, targetAddress string) {
|
||||
errCode := errUnknownError
|
||||
var err error
|
||||
defer func() {
|
||||
@ -39,7 +39,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
||||
}()
|
||||
|
||||
if isFromIMAP {
|
||||
f.transfer, err = f.ie.GetRemoteImporter(targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort)
|
||||
f.transfer, err = f.ie.GetRemoteImporter(targetUsername, targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, &transfer.ErrIMAPConnection{}):
|
||||
@ -54,7 +54,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
||||
return
|
||||
}
|
||||
} else {
|
||||
f.transfer, err = f.ie.GetLocalImporter(targetAddress, sourcePath)
|
||||
f.transfer, err = f.ie.GetLocalImporter(targetUsername, targetAddress, sourcePath)
|
||||
if err != nil {
|
||||
// The only error can be problem to load PM user and address.
|
||||
errCode = errPMLoadFailed
|
||||
|
||||
@ -33,7 +33,7 @@ type GoQMLInterface struct {
|
||||
|
||||
_ func() `constructor:"init"`
|
||||
|
||||
_ bool `property:"isAutoUpdate"`
|
||||
//_ bool `property:"isAutoUpdate"`
|
||||
_ string `property:"currentAddress"`
|
||||
_ string `property:"goos"`
|
||||
_ string `property:"credits"`
|
||||
@ -62,8 +62,8 @@ type GoQMLInterface struct {
|
||||
_ func() `signal:"notifyManualUpdateRestartNeeded"`
|
||||
_ func() `signal:"notifyManualUpdateError"`
|
||||
_ func() `signal:"notifyForceUpdate"`
|
||||
_ func() `signal:"notifySilentUpdateRestartNeeded"`
|
||||
_ func() `signal:"notifySilentUpdateError"`
|
||||
//_ func() `signal:"notifySilentUpdateRestartNeeded"`
|
||||
//_ func() `signal:"notifySilentUpdateError"`
|
||||
_ func() `slot:"checkForUpdates"`
|
||||
_ func() `slot:"checkAndOpenReleaseNotes"`
|
||||
_ func() `signal:"openReleaseNotesExternally"`
|
||||
@ -77,8 +77,8 @@ type GoQMLInterface struct {
|
||||
_ string `property:"credentialsNotRemoved"`
|
||||
_ string `property:"versionCheckFailed"`
|
||||
//
|
||||
_ func(isAvailable bool) `signal:"setConnectionStatus"`
|
||||
_ func() `slot:"checkInternet"`
|
||||
_ func(isAvailable bool) `signal:"setConnectionStatus"`
|
||||
_ func() `slot:"checkInternet"`
|
||||
|
||||
_ func() `slot:"setToRestart"`
|
||||
|
||||
@ -93,7 +93,7 @@ type GoQMLInterface struct {
|
||||
|
||||
_ func() `signal:"showWindow"`
|
||||
|
||||
_ func() `slot:"toggleAutoUpdate"`
|
||||
//_ func() `slot:"toggleAutoUpdate"`
|
||||
_ func() `slot:"quit"`
|
||||
_ func() `slot:"loadAccounts"`
|
||||
_ func() `slot:"openLogs"`
|
||||
@ -108,14 +108,14 @@ type GoQMLInterface struct {
|
||||
|
||||
_ func(description, client, address string) bool `slot:"sendBug"`
|
||||
_ func(address string) bool `slot:"sendImportReport"`
|
||||
_ func(address string) `slot:"loadStructureForExport"`
|
||||
_ func(username, address string) `slot:"loadStructureForExport"`
|
||||
_ func() string `slot:"leastUsedColor"`
|
||||
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
||||
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
|
||||
_ func(email string, importEncrypted bool) `slot:"startImport"`
|
||||
_ func() `slot:"resetSource"`
|
||||
|
||||
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetAddress string) `slot:"setupAndLoadForImport"`
|
||||
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetUsername, targetAddress string) `slot:"setupAndLoadForImport"`
|
||||
|
||||
_ string `property:"progressInit"`
|
||||
|
||||
@ -162,7 +162,7 @@ func (s *GoQMLInterface) init() {}
|
||||
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
||||
s.ConnectQuit(f.App.Quit)
|
||||
|
||||
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
|
||||
//s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
|
||||
s.ConnectLoadAccounts(f.Accounts.LoadAccounts)
|
||||
s.ConnectOpenLogs(f.openLogs)
|
||||
s.ConnectOpenDownloadLink(f.openDownloadLink)
|
||||
@ -207,4 +207,6 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
||||
s.ConnectCheckPathStatus(CheckPathStatus)
|
||||
|
||||
s.ConnectEmitEvent(f.emitEvent)
|
||||
|
||||
s.ConnectStartManualUpdate(f.startManualUpdate)
|
||||
}
|
||||
|
||||
@ -370,24 +370,15 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
||||
}
|
||||
s.Qml.SetIsAutoStart(s.autostart.IsEnabled())
|
||||
|
||||
if s.settings.GetBool(settings.AllowProxyKey) {
|
||||
s.Qml.SetIsProxyAllowed(true)
|
||||
} else {
|
||||
s.Qml.SetIsProxyAllowed(false)
|
||||
}
|
||||
|
||||
if updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.EarlyChannel {
|
||||
s.Qml.SetIsEarlyAccess(true)
|
||||
} else {
|
||||
s.Qml.SetIsEarlyAccess(false)
|
||||
}
|
||||
s.Qml.SetIsAutoUpdate(s.settings.GetBool(settings.AutoUpdateKey))
|
||||
s.Qml.SetIsProxyAllowed(s.settings.GetBool(settings.AllowProxyKey))
|
||||
s.Qml.SetIsEarlyAccess(updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.EarlyChannel)
|
||||
|
||||
availableKeychain := []string{}
|
||||
for chain := range keychain.Helpers {
|
||||
availableKeychain = append(availableKeychain, chain)
|
||||
}
|
||||
s.Qml.SetAvailableKeychain(availableKeychain)
|
||||
|
||||
s.Qml.SetSelectedKeychain(s.settings.Get(settings.PreferredKeychainKey))
|
||||
|
||||
// Set reporting of outgoing email without encryption.
|
||||
|
||||
@ -1 +0,0 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "logo.ico"
|
||||
45
internal/frontend/share/info.rc
Normal file
45
internal/frontend/share/info.rc
Normal file
@ -0,0 +1,45 @@
|
||||
#define STRINGIZE_(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_(x)
|
||||
|
||||
IDI_ICON1 ICON DISCARDABLE STRINGIZE(ICO_FILE)
|
||||
|
||||
#if defined BUILD_BRIDGE
|
||||
#define FILE_COMMENTS "The Bridge is an application that runs on your computer in the background and seamlessly encrypts and decrypts your mail as it enters and leaves your computer."
|
||||
#define FILE_DESCRIPTION "ProtonMail Bridge"
|
||||
#define INTERNAL_NAME STRINGIZE(EXE_NAME)
|
||||
#define PRODUCT_NAME "ProtonMail Bridge for Windows"
|
||||
#elif defined BUILD_IE
|
||||
#define FILE_COMMENTS "The Import-Export app helps you to migrate your emails from local files or remote IMAP servers to ProtonMail or simply export emails to local folder."
|
||||
#define FILE_DESCRIPTION "ProtonMail Import-Export app"
|
||||
#define INTERNAL_NAME STRINGIZE(EXE_NAME)
|
||||
#define PRODUCT_NAME "ProtonMail Import-Export app for Windows"
|
||||
#else
|
||||
#error No target specified
|
||||
#endif
|
||||
|
||||
#define LEGAL_COPYRIGHT "(C) " STRINGIZE(YEAR) " Proton Technologies AG"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION_COMMA,0
|
||||
PRODUCTVERSION FILE_VERSION_COMMA,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", FILE_COMMENTS
|
||||
VALUE "CompanyName", "Proton Technologies AG"
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", STRINGIZE(FILE_VERSION)
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", LEGAL_COPYRIGHT
|
||||
VALUE "OriginalFilename", STRINGIZE(ORIGINAL_FILE_NAME)
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", STRINGIZE(PRODUCT_VERSION)
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
@ -114,10 +114,10 @@ func (b *bridgeWrap) GetUser(query string) (User, error) {
|
||||
type ImportExporter interface {
|
||||
UserManager
|
||||
|
||||
GetLocalImporter(string, string) (*transfer.Transfer, error)
|
||||
GetRemoteImporter(string, string, string, string, string) (*transfer.Transfer, error)
|
||||
GetEMLExporter(string, string) (*transfer.Transfer, error)
|
||||
GetMBOXExporter(string, string) (*transfer.Transfer, error)
|
||||
GetLocalImporter(string, string, string) (*transfer.Transfer, error)
|
||||
GetRemoteImporter(string, string, string, string, string, string) (*transfer.Transfer, error)
|
||||
GetEMLExporter(string, string, string) (*transfer.Transfer, error)
|
||||
GetMBOXExporter(string, string, string) (*transfer.Transfer, error)
|
||||
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
|
||||
ReportFile(osType, osVersion, accountName, address string, logdata []byte) error
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ func (im *imapMailbox) createMessage(flags []string, date time.Time, body imap.L
|
||||
return err
|
||||
}
|
||||
|
||||
targetSeq := im.storeMailbox.GetUIDList([]string{m.ID})
|
||||
targetSeq := im.storeMailbox.GetUIDList(IDs)
|
||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
||||
}
|
||||
}
|
||||
@ -226,7 +226,7 @@ func (im *imapMailbox) importMessage(m *pmapi.Message, readers []io.Reader, kr *
|
||||
return im.storeMailbox.ImportMessage(m, body, labels)
|
||||
}
|
||||
|
||||
func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []imap.FetchItem) (msg *imap.Message, err error) {
|
||||
func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []imap.FetchItem, msgBuildCountHistogram *msgBuildCountHistogram) (msg *imap.Message, err error) { //nolint[funlen]
|
||||
im.log.WithField("msgID", storeMessage.ID()).Trace("Getting message")
|
||||
|
||||
seqNum, err := storeMessage.SequenceNumber()
|
||||
@ -268,7 +268,12 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
|
||||
// on our part and we need to compute "real" size of decrypted data.
|
||||
if m.Size <= 0 {
|
||||
im.log.WithField("msgID", storeMessage.ID()).Trace("Size unknown - downloading body")
|
||||
if _, _, err = im.getBodyAndStructure(storeMessage); err != nil {
|
||||
// We are sure the size is not a problem right now. Clients
|
||||
// might not first check sizes of all messages so we couldn't
|
||||
// be sure if seeing 1st or 2nd sync is all right or not.
|
||||
// Therefore, it's better to exclude getting size from the
|
||||
// counting and see build count as real message build.
|
||||
if _, _, err = im.getBodyAndStructure(storeMessage, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -279,7 +284,7 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
if err = im.getLiteralForSection(item, msg, storeMessage); err != nil {
|
||||
if err = im.getLiteralForSection(item, msg, storeMessage, msgBuildCountHistogram); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -288,14 +293,14 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func (im *imapMailbox) getLiteralForSection(itemSection imap.FetchItem, msg *imap.Message, storeMessage storeMessageProvider) error {
|
||||
func (im *imapMailbox) getLiteralForSection(itemSection imap.FetchItem, msg *imap.Message, storeMessage storeMessageProvider, msgBuildCountHistogram *msgBuildCountHistogram) error {
|
||||
section, err := imap.ParseBodySectionName(itemSection)
|
||||
if err != nil { // Ignore error
|
||||
return nil
|
||||
}
|
||||
|
||||
var literal imap.Literal
|
||||
if literal, err = im.getMessageBodySection(storeMessage, section); err != nil {
|
||||
if literal, err = im.getMessageBodySection(storeMessage, section, msgBuildCountHistogram); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -313,14 +318,19 @@ func (im *imapMailbox) getBodyStructure(storeMessage storeMessageProvider) (bs *
|
||||
im.log.WithError(err).Debug("Fail to retrieve bodystructure from database")
|
||||
}
|
||||
if bs == nil {
|
||||
if bs, _, err = im.getBodyAndStructure(storeMessage); err != nil {
|
||||
// We are sure the body structure is not a problem right now.
|
||||
// Clients might do first fetch body structure so we couldn't
|
||||
// be sure if seeing 1st or 2nd sync is all right or not.
|
||||
// Therefore, it's better to exclude first body structure fetch
|
||||
// from the counting and see build count as real message build.
|
||||
if bs, _, err = im.getBodyAndStructure(storeMessage, nil); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
|
||||
func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider, msgBuildCountHistogram *msgBuildCountHistogram) (
|
||||
structure *message.BodyStructure,
|
||||
bodyReader *bytes.Reader, err error,
|
||||
) {
|
||||
@ -352,6 +362,15 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
|
||||
WithField("msgID", m.ID).
|
||||
Warn("Cannot update header while building")
|
||||
}
|
||||
if msgBuildCountHistogram != nil {
|
||||
times, err := storeMessage.IncreaseBuildCount()
|
||||
if err != nil {
|
||||
im.log.WithError(err).
|
||||
WithField("msgID", m.ID).
|
||||
Warn("Cannot increase build count")
|
||||
}
|
||||
msgBuildCountHistogram.add(times)
|
||||
}
|
||||
// Drafts can change and we don't want to cache them.
|
||||
if !isMessageInDraftFolder(m) {
|
||||
cache.SaveMail(id, body, structure)
|
||||
@ -379,7 +398,7 @@ func isMessageInDraftFolder(m *pmapi.Message) bool {
|
||||
|
||||
// This will download message (or read from cache) and pick up the section,
|
||||
// extract data (header,body, both) and trim the output if needed.
|
||||
func (im *imapMailbox) getMessageBodySection(storeMessage storeMessageProvider, section *imap.BodySectionName) (literal imap.Literal, err error) { // nolint[funlen]
|
||||
func (im *imapMailbox) getMessageBodySection(storeMessage storeMessageProvider, section *imap.BodySectionName, msgBuildCountHistogram *msgBuildCountHistogram) (literal imap.Literal, err error) { // nolint[funlen]
|
||||
var (
|
||||
structure *message.BodyStructure
|
||||
bodyReader *bytes.Reader
|
||||
@ -410,7 +429,7 @@ func (im *imapMailbox) getMessageBodySection(storeMessage storeMessageProvider,
|
||||
}
|
||||
} else {
|
||||
// The rest of cases need download and decrypt.
|
||||
structure, bodyReader, err = im.getBodyAndStructure(storeMessage)
|
||||
structure, bodyReader, err = im.getBodyAndStructure(storeMessage, msgBuildCountHistogram)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -564,7 +583,7 @@ func (im *imapMailbox) writeRelatedPart(p io.Writer, m *pmapi.Message, inlines [
|
||||
return
|
||||
}
|
||||
|
||||
h := message.GetAttachmentHeader(inline)
|
||||
h := message.GetAttachmentHeader(inline, true)
|
||||
if p, err = related.CreatePart(h); err != nil {
|
||||
return
|
||||
}
|
||||
@ -738,7 +757,7 @@ func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (
|
||||
defer buf.Reset()
|
||||
att := atts[idx]
|
||||
|
||||
attachmentHeader := message.GetAttachmentHeader(att)
|
||||
attachmentHeader := message.GetAttachmentHeader(att, true)
|
||||
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -482,12 +482,13 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
|
||||
//
|
||||
// Messages must be sent to msgResponse. When the function returns, msgResponse must be closed.
|
||||
func (im *imapMailbox) ListMessages(isUID bool, seqSet *imap.SeqSet, items []imap.FetchItem, msgResponse chan<- *imap.Message) error {
|
||||
msgBuildCountHistogram := newMsgBuildCountHistogram()
|
||||
return im.logCommand(func() error {
|
||||
return im.listMessages(isUID, seqSet, items, msgResponse)
|
||||
}, "FETCH", isUID, seqSet, items)
|
||||
return im.listMessages(isUID, seqSet, items, msgResponse, msgBuildCountHistogram)
|
||||
}, "FETCH", isUID, seqSet, items, msgBuildCountHistogram)
|
||||
}
|
||||
|
||||
func (im *imapMailbox) listMessages(isUID bool, seqSet *imap.SeqSet, items []imap.FetchItem, msgResponse chan<- *imap.Message) (err error) { //nolint[funlen]
|
||||
func (im *imapMailbox) listMessages(isUID bool, seqSet *imap.SeqSet, items []imap.FetchItem, msgResponse chan<- *imap.Message, msgBuildCountHistogram *msgBuildCountHistogram) (err error) { //nolint[funlen]
|
||||
defer func() {
|
||||
close(msgResponse)
|
||||
if err != nil {
|
||||
@ -544,7 +545,7 @@ func (im *imapMailbox) listMessages(isUID bool, seqSet *imap.SeqSet, items []ima
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := im.getMessage(storeMessage, items)
|
||||
msg, err := im.getMessage(storeMessage, items, msgBuildCountHistogram)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("list message build: %v", err)
|
||||
l.WithField("metaID", storeMessage.ID()).Error(err)
|
||||
|
||||
65
internal/imap/msg_build_counts.go
Normal file
65
internal/imap/msg_build_counts.go
Normal 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"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// msgBuildCountHistogram is used to analyse and log the number of repetitive
|
||||
// downloads of requested messages per one fetch. The number of builds per each
|
||||
// messageID is stored in persistent database. The msgBuildCountHistogram will
|
||||
// take this number for each message in ongoing fetch and create histogram of
|
||||
// repeats.
|
||||
//
|
||||
// Example: During `fetch 1:300` there were
|
||||
// - 100 messages were downloaded first time
|
||||
// - 100 messages were downloaded second time
|
||||
// - 99 messages were downloaded 10th times
|
||||
// - 1 messages were downloaded 100th times
|
||||
type msgBuildCountHistogram struct {
|
||||
// Key represents how many times message was build.
|
||||
// Value stores how many messages are build X times based on the key.
|
||||
counts map[uint32]uint32
|
||||
lock sync.Locker
|
||||
}
|
||||
|
||||
func newMsgBuildCountHistogram() *msgBuildCountHistogram {
|
||||
return &msgBuildCountHistogram{
|
||||
counts: map[uint32]uint32{},
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *msgBuildCountHistogram) String() string {
|
||||
res := ""
|
||||
for nRebuild, counts := range c.counts {
|
||||
if res != "" {
|
||||
res += ", "
|
||||
}
|
||||
res += fmt.Sprintf("[%d]:%d", nRebuild, counts)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (c *msgBuildCountHistogram) add(nRebuild uint32) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.counts[nRebuild]++
|
||||
}
|
||||
@ -103,6 +103,7 @@ type storeMessageProvider interface {
|
||||
SetContentTypeAndHeader(string, mail.Header) error
|
||||
SetBodyStructure(*pkgMsg.BodyStructure) error
|
||||
GetBodyStructure() (*pkgMsg.BodyStructure, error)
|
||||
IncreaseBuildCount() (uint32, error)
|
||||
}
|
||||
|
||||
type storeUserWrap struct {
|
||||
|
||||
@ -118,9 +118,9 @@ func (ie *ImportExport) ReportFile(osType, osVersion, accountName, address strin
|
||||
}
|
||||
|
||||
// GetLocalImporter returns transferrer from local EML or MBOX structure to ProtonMail account.
|
||||
func (ie *ImportExport) GetLocalImporter(address, path string) (*transfer.Transfer, error) {
|
||||
func (ie *ImportExport) GetLocalImporter(username, address, path string) (*transfer.Transfer, error) {
|
||||
source := transfer.NewLocalProvider(path)
|
||||
target, err := ie.getPMAPIProvider(address)
|
||||
target, err := ie.getPMAPIProvider(username, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -132,12 +132,12 @@ func (ie *ImportExport) GetLocalImporter(address, path string) (*transfer.Transf
|
||||
}
|
||||
|
||||
// GetRemoteImporter returns transferrer from remote IMAP to ProtonMail account.
|
||||
func (ie *ImportExport) GetRemoteImporter(address, username, password, host, port string) (*transfer.Transfer, error) {
|
||||
source, err := transfer.NewIMAPProvider(username, password, host, port)
|
||||
func (ie *ImportExport) GetRemoteImporter(username, address, remoteUsername, remotePassword, host, port string) (*transfer.Transfer, error) {
|
||||
source, err := transfer.NewIMAPProvider(remoteUsername, remotePassword, host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target, err := ie.getPMAPIProvider(address)
|
||||
target, err := ie.getPMAPIProvider(username, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -149,8 +149,8 @@ func (ie *ImportExport) GetRemoteImporter(address, username, password, host, por
|
||||
}
|
||||
|
||||
// GetEMLExporter returns transferrer from ProtonMail account to local EML structure.
|
||||
func (ie *ImportExport) GetEMLExporter(address, path string) (*transfer.Transfer, error) {
|
||||
source, err := ie.getPMAPIProvider(address)
|
||||
func (ie *ImportExport) GetEMLExporter(username, address, path string) (*transfer.Transfer, error) {
|
||||
source, err := ie.getPMAPIProvider(username, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -163,8 +163,8 @@ func (ie *ImportExport) GetEMLExporter(address, path string) (*transfer.Transfer
|
||||
}
|
||||
|
||||
// GetMBOXExporter returns transferrer from ProtonMail account to local MBOX structure.
|
||||
func (ie *ImportExport) GetMBOXExporter(address, path string) (*transfer.Transfer, error) {
|
||||
source, err := ie.getPMAPIProvider(address)
|
||||
func (ie *ImportExport) GetMBOXExporter(username, address, path string) (*transfer.Transfer, error) {
|
||||
source, err := ie.getPMAPIProvider(username, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -176,8 +176,8 @@ func (ie *ImportExport) GetMBOXExporter(address, path string) (*transfer.Transfe
|
||||
return transfer.New(ie.panicHandler, newExportMetricsManager(ie), logsPath, ie.cache.GetTransferDir(), source, target)
|
||||
}
|
||||
|
||||
func (ie *ImportExport) getPMAPIProvider(address string) (*transfer.PMAPIProvider, error) {
|
||||
user, err := ie.Users.GetUser(address)
|
||||
func (ie *ImportExport) getPMAPIProvider(username, address string) (*transfer.PMAPIProvider, error) {
|
||||
user, err := ie.Users.GetUser(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -32,8 +32,8 @@ import (
|
||||
// On linux:
|
||||
// - settings: ~/.config/protonmail/<app>
|
||||
// - logs: ~/.cache/protonmail/<app>/logs
|
||||
// - cache: ~/.cache/protonmail/<app>/cache
|
||||
// - updates: ~/.cache/protonmail/<app>/updates
|
||||
// - cache: ~/.config/protonmail/<app>/cache
|
||||
// - updates: ~/.config/protonmail/<app>/updates
|
||||
// - lockfile: ~/.cache/protonmail/<app>/<app>.lock
|
||||
type Locations struct {
|
||||
userConfig, userCache string
|
||||
@ -129,7 +129,7 @@ func (l *Locations) ProvideLogsPath() (string, error) {
|
||||
return l.getLogsPath(), nil
|
||||
}
|
||||
|
||||
// ProvideCachePath returns a location for user cache dirs (e.g. ~/.cache/<company>/<app>/cache).
|
||||
// ProvideCachePath returns a location for user cache dirs (e.g. ~/.config/<company>/<app>/cache).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideCachePath() (string, error) {
|
||||
if err := os.MkdirAll(l.getCachePath(), 0700); err != nil {
|
||||
@ -139,6 +139,11 @@ func (l *Locations) ProvideCachePath() (string, error) {
|
||||
return l.getCachePath(), nil
|
||||
}
|
||||
|
||||
// GetOldCachePath returns a former location for user cache dirs used for migration scripts only.
|
||||
func (l *Locations) GetOldCachePath() string {
|
||||
return filepath.Join(l.userCache, "cache")
|
||||
}
|
||||
|
||||
// ProvideUpdatesPath returns a location for update files (e.g. ~/.cache/<company>/<app>/updates).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideUpdatesPath() (string, error) {
|
||||
@ -149,6 +154,16 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
|
||||
return l.getUpdatesPath(), nil
|
||||
}
|
||||
|
||||
// GetUpdatesPath returns a new location for update files used for migration scripts only.
|
||||
func (l *Locations) GetUpdatesPath() string {
|
||||
return l.getUpdatesPath()
|
||||
}
|
||||
|
||||
// GetOldUpdatesPath returns a former location for update files used for migration scripts only.
|
||||
func (l *Locations) GetOldUpdatesPath() string {
|
||||
return filepath.Join(l.userCache, "updates")
|
||||
}
|
||||
|
||||
func (l *Locations) getSettingsPath() string {
|
||||
return l.userConfig
|
||||
}
|
||||
@ -158,11 +173,33 @@ func (l *Locations) getLogsPath() string {
|
||||
}
|
||||
|
||||
func (l *Locations) getCachePath() string {
|
||||
return filepath.Join(l.userCache, "cache")
|
||||
// Bridge cache is not a typical cache which can be deleted with only
|
||||
// downside that the app has to download everything again.
|
||||
// Cache for bridge is database with IMAP UIDs and UIDVALIDITY, and also
|
||||
// other IMAP setup. Deleting such data leads to either re-sync of client,
|
||||
// or mix of headers and bodies. Both is caused because of need of re-sync
|
||||
// between Bridge and API which will happen in different order than before.
|
||||
// In the first case, UIDVALIDITY is also changed and causes the better
|
||||
// outcome to "just" re-sync everything; in the later, UIDVALIDITY stays
|
||||
// the same, causing the client to not re-sync but UIDs in the client does
|
||||
// not match UIDs in Bridge.
|
||||
// Because users might use tools to regularly clear caches, Bridge cache
|
||||
// cannot be located in a standard cache folder.
|
||||
return filepath.Join(l.userConfig, "cache")
|
||||
}
|
||||
|
||||
func (l *Locations) getUpdatesPath() string {
|
||||
return filepath.Join(l.userCache, "updates")
|
||||
// In order to properly update Bridge 1.6.X and higher we need to
|
||||
// change the launcher first. Since this is not part of automatic
|
||||
// updates the migration must wait until manual update. Until that
|
||||
// we need to keep old path.
|
||||
if l.configName == "bridge" {
|
||||
return l.GetOldUpdatesPath()
|
||||
}
|
||||
|
||||
// Users might use tools to regularly clear caches, which would mean always
|
||||
// removing updates, therefore Bridge updates have to be somewhere else.
|
||||
return filepath.Join(l.userConfig, "updates")
|
||||
}
|
||||
|
||||
// Clear removes everything except the lock and update files.
|
||||
|
||||
@ -45,7 +45,8 @@ func TestClearRemovesEverythingExceptLockAndUpdateFiles(t *testing.T) {
|
||||
assert.NoError(t, l.Clear())
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.NoDirExists(t, l.getSettingsPath())
|
||||
assert.DirExists(t, l.getSettingsPath())
|
||||
assert.NoFileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||
assert.NoDirExists(t, l.getLogsPath())
|
||||
assert.NoDirExists(t, l.getCachePath())
|
||||
assert.DirExists(t, l.getUpdatesPath())
|
||||
@ -58,6 +59,7 @@ func TestClearUpdateFiles(t *testing.T) {
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.DirExists(t, l.getSettingsPath())
|
||||
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||
assert.DirExists(t, l.getLogsPath())
|
||||
assert.DirExists(t, l.getCachePath())
|
||||
assert.NoDirExists(t, l.getUpdatesPath())
|
||||
@ -75,6 +77,7 @@ func TestCleanLeavesStandardLocationsUntouched(t *testing.T) {
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.DirExists(t, l.getSettingsPath())
|
||||
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||
assert.DirExists(t, l.getLogsPath())
|
||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt"))
|
||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log2.txt"))
|
||||
@ -138,6 +141,9 @@ func newTestLocations(t *testing.T) *Locations {
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, settings)
|
||||
|
||||
createFilesInDir(t, settings, "prefs.json")
|
||||
require.FileExists(t, filepath.Join(settings, "prefs.json"))
|
||||
|
||||
logs, err := l.ProvideLogsPath()
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, logs)
|
||||
|
||||
@ -69,6 +69,10 @@ func Init(logsPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLevel will change the level of logging and in case of Debug or Trace
|
||||
// level it will also prevent from writing to file. Setting level to Info or
|
||||
// higher will not set writing to file again if it was previously cancelled by
|
||||
// Debug or Trace.
|
||||
func SetLevel(level string) {
|
||||
if lvl, err := logrus.ParseLevel(level); err == nil {
|
||||
logrus.SetLevel(lvl)
|
||||
|
||||
@ -42,6 +42,7 @@ func NewSMTPServer(debug bool, port int, useSSL bool, tls *tls.Config, smtpBacke
|
||||
s.TLSConfig = tls
|
||||
s.Domain = bridge.Host
|
||||
s.AllowInsecureAuth = true
|
||||
s.MaxLineLength = 2 << 16
|
||||
|
||||
if debug {
|
||||
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||
|
||||
@ -268,8 +268,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
|
||||
return false, errors.New("received empty event")
|
||||
}
|
||||
|
||||
l = l.WithField("newEventID", event.EventID)
|
||||
|
||||
if err = loop.processEvent(event); err != nil {
|
||||
return false, errors.Wrap(err, "failed to process event")
|
||||
}
|
||||
|
||||
@ -67,10 +67,14 @@ func (storeMailbox *Mailbox) ImportMessage(msg *pmapi.Message, body []byte, labe
|
||||
}
|
||||
|
||||
res, err := storeMailbox.client().Import([]*pmapi.ImportMsgReq{importReqs})
|
||||
if err == nil && len(res) > 0 {
|
||||
msg.ID = res[0].MessageID
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
if len(res) == 0 {
|
||||
return errors.New("no import response")
|
||||
}
|
||||
msg.ID = res[0].MessageID
|
||||
return res[0].Error
|
||||
}
|
||||
|
||||
// LabelMessages adds the label by calling an API.
|
||||
|
||||
@ -148,3 +148,17 @@ func (message *Message) GetBodyStructure() (bs *pkgMsg.BodyStructure, err error)
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func (message *Message) IncreaseBuildCount() (times uint32, err error) {
|
||||
txUpdate := func(tx *bolt.Tx) error {
|
||||
times, err = message.store.txIncreaseMsgBuildCount(
|
||||
tx.Bucket(msgBuildCountBucket),
|
||||
message.ID(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
if err = message.store.db.Update(txUpdate); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
|
||||
@ -54,6 +54,8 @@ var (
|
||||
// * {messageID} -> message data (subject, from, to, time, headers, body size, ...)
|
||||
// * bodystructure
|
||||
// * {messageID} -> message body structure
|
||||
// * msgbuildcount
|
||||
// * {messageID} -> uint32 number of message builds to track re-sync issues
|
||||
// * counts
|
||||
// * {mailboxID} -> mailboxCounts: totalOnAPI, unreadOnAPI, labelName, labelColor, labelIsExclusive
|
||||
// * address_info
|
||||
@ -76,6 +78,7 @@ var (
|
||||
// * {messageID} -> true
|
||||
metadataBucket = []byte("metadata") //nolint[gochecknoglobals]
|
||||
bodystructureBucket = []byte("bodystructure") //nolint[gochecknoglobals]
|
||||
msgBuildCountBucket = []byte("msgbuildcount") //nolint[gochecknoglobals]
|
||||
countsBucket = []byte("counts") //nolint[gochecknoglobals]
|
||||
addressInfoBucket = []byte("address_info") //nolint[gochecknoglobals]
|
||||
addressModeBucket = []byte("address_mode") //nolint[gochecknoglobals]
|
||||
@ -204,6 +207,10 @@ func openBoltDatabase(filePath string) (db *bolt.DB, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = tx.CreateBucketIfNotExists(msgBuildCountBucket); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = tx.CreateBucketIfNotExists(countsBucket); err != nil {
|
||||
return
|
||||
}
|
||||
@ -263,7 +270,7 @@ func (store *Store) init(firstInit bool) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
store.log.WithField("mode", store.addressMode).Debug("Initialising store")
|
||||
store.log.WithField("mode", store.addressMode).Info("Initialising store")
|
||||
|
||||
labels, err := store.initCounts()
|
||||
if err != nil {
|
||||
|
||||
@ -191,6 +191,19 @@ func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*pk
|
||||
return pkgMsg.DeserializeBodyStructure(raw)
|
||||
}
|
||||
|
||||
func (store *Store) txIncreaseMsgBuildCount(b *bolt.Bucket, msgID string) (uint32, error) {
|
||||
key := []byte(msgID)
|
||||
count := uint32(0)
|
||||
|
||||
raw := b.Get(key)
|
||||
if raw != nil {
|
||||
count = btoi(raw)
|
||||
}
|
||||
|
||||
count++
|
||||
return count, b.Put(key, itob(count))
|
||||
}
|
||||
|
||||
// createOrUpdateMessageEvent is helper to create only one message with
|
||||
// createOrUpdateMessagesEvent.
|
||||
func (store *Store) createOrUpdateMessageEvent(msg *pmapi.Message) error {
|
||||
|
||||
@ -57,19 +57,23 @@ func WriteAttachmentBody(w io.Writer, kr *crypto.KeyRing, m *pmapi.Message, att
|
||||
att.Name += ".gpg"
|
||||
att.MIMEType = "application/pgp-encrypted" //nolint
|
||||
} else if err != nil && err != openpgperrors.ErrSignatureExpired {
|
||||
err = fmt.Errorf("cannot decrypt attachment: %v", err)
|
||||
return
|
||||
return fmt.Errorf("cannot decrypt attachment: %v", err)
|
||||
}
|
||||
|
||||
// Don't encode message/rfc822 attachments; they should be embedded and preserved.
|
||||
if att.MIMEType == rfc822Message {
|
||||
if n, err := io.Copy(w, dr); err != nil {
|
||||
return fmt.Errorf("cannot write attached message: %v (wrote %v bytes)", err, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode it.
|
||||
ww := textwrapper.NewRFC822(w)
|
||||
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
||||
|
||||
var n int64
|
||||
if n, err = io.Copy(bw, dr); err != nil {
|
||||
err = fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
|
||||
if n, err := io.Copy(bw, dr); err != nil {
|
||||
return fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
|
||||
}
|
||||
|
||||
_ = bw.Close()
|
||||
return
|
||||
return bw.Close()
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ func (bld *Builder) writeRelatedPart(p io.Writer, inlines []*pmapi.Attachment) e
|
||||
return err
|
||||
}
|
||||
|
||||
h := GetAttachmentHeader(inline)
|
||||
h := GetAttachmentHeader(inline, false)
|
||||
if p, err = related.CreatePart(h); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -194,7 +194,7 @@ func (bld *Builder) BuildMessage() (structure *BodyStructure, message []byte, er
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
attachmentHeader := GetAttachmentHeader(att)
|
||||
attachmentHeader := GetAttachmentHeader(att, false)
|
||||
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -311,16 +311,11 @@ func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (
|
||||
for i := 0; i < len(m.Attachments); i++ {
|
||||
att := m.Attachments[i]
|
||||
r := readers[i]
|
||||
h := GetAttachmentHeader(att)
|
||||
h := GetAttachmentHeader(att, false)
|
||||
p, err := mw.CreatePart(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create line wrapper writer.
|
||||
ww := textwrapper.NewRFC822(p)
|
||||
|
||||
// Create base64 writer.
|
||||
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
||||
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
@ -332,6 +327,9 @@ func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ww := textwrapper.NewRFC822(p)
|
||||
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
||||
if _, err := bw.Write(pgpMessage.GetBinary()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -107,12 +107,17 @@ func GetRelatedHeader(m *pmapi.Message) textproto.MIMEHeader {
|
||||
return h
|
||||
}
|
||||
|
||||
func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader {
|
||||
func GetAttachmentHeader(att *pmapi.Attachment, buildForIMAP bool) textproto.MIMEHeader {
|
||||
mediaType := att.MIMEType
|
||||
if mediaType == "application/pgp-encrypted" {
|
||||
mediaType = "application/octet-stream"
|
||||
}
|
||||
|
||||
transferEncoding := "base64"
|
||||
if mediaType == rfc822Message && buildForIMAP {
|
||||
transferEncoding = "8bit"
|
||||
}
|
||||
|
||||
encodedName := pmmime.EncodeHeader(att.Name)
|
||||
disposition := "attachment" //nolint[goconst]
|
||||
if strings.Contains(att.Header.Get("Content-Disposition"), "inline") {
|
||||
@ -121,7 +126,9 @@ func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader {
|
||||
|
||||
h := make(textproto.MIMEHeader)
|
||||
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName}))
|
||||
h.Set("Content-Transfer-Encoding", "base64")
|
||||
if transferEncoding != "" {
|
||||
h.Set("Content-Transfer-Encoding", transferEncoding)
|
||||
}
|
||||
h.Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": encodedName}))
|
||||
|
||||
// Forward some original header lines.
|
||||
|
||||
@ -26,6 +26,10 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
rfc822Message = "message/rfc822"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("pkg", "pkg/message") //nolint[gochecknoglobals]
|
||||
|
||||
func GetBoundary(m *pmapi.Message) string {
|
||||
|
||||
@ -201,7 +201,7 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
|
||||
mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type"))
|
||||
|
||||
// If multipart, call getAllParts, else read to count lines.
|
||||
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == "message/rfc822") && params["boundary"] != "" {
|
||||
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == rfc822Message) && params["boundary"] != "" {
|
||||
newPath := append(currentPath, 1)
|
||||
|
||||
var br *boundaryReader
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
## v1.6.6
|
||||
- 2021-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed update notifications
|
||||
- Fixed GUI freeze while switching to early update channel
|
||||
- Fixed Bridge autostart
|
||||
- Improved signing of update packages
|
||||
|
||||
## v1.6.5
|
||||
- 2021-02-22
|
||||
|
||||
|
||||
@ -1,3 +1,24 @@
|
||||
## v1.6.6
|
||||
- 2021-03-04
|
||||
|
||||
### New
|
||||
|
||||
- Allow to choose which keychain is used by Bridge on Linux
|
||||
- Added automatic update CLI commands
|
||||
- Improved performance during slow connection
|
||||
- Added IMAP requests to the logs for easier debugging
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed update notifications
|
||||
- Fixed GUI freeze while switching to early update channel
|
||||
- Fixed Bridge autostart
|
||||
- Improved signing of update packages
|
||||
- NoGUI bulid
|
||||
- Background of GUI welcome message
|
||||
- Incorrect total mailbox size displayed in Apple Mail
|
||||
|
||||
|
||||
## v1.6.3
|
||||
- 2021-02-16
|
||||
|
||||
|
||||
@ -1,51 +1,49 @@
|
||||
## v1.3.1
|
||||
- 2021-03-11
|
||||
|
||||
### New
|
||||
- Reduce the number of import errors by supporting malformed undisclosed-recipient and better handling of overly long headers
|
||||
- Improvements to how large attachments are processed
|
||||
- New format of the release notes
|
||||
|
||||
### Fixed
|
||||
- Linux font issues - Fedora specific
|
||||
- Fixed rare message misplacement
|
||||
- Ensure removal of the startup entry during uninstallation
|
||||
- Update errors
|
||||
|
||||
|
||||
## v1.2.2
|
||||
- 2020-11-27
|
||||
|
||||
### New
|
||||
Improvements to the import from large mbox files with multiple labels
|
||||
|
||||
Not allow to run multiple instances of the app or transfers at the same time
|
||||
|
||||
Better handling and displaying of skipped messages
|
||||
|
||||
Various enhancements of the import process related to parsing
|
||||
|
||||
Cosmetic GUI changes
|
||||
|
||||
Better error handling
|
||||
- Improvements to the import from large mbox files with multiple labels
|
||||
- Not allow to run multiple instances of the app or transfers at the same time
|
||||
- Better handling and displaying of skipped messages
|
||||
- Various enhancements of the import process related to parsing
|
||||
- Cosmetic GUI changes
|
||||
- Better error handling
|
||||
|
||||
### Fixed
|
||||
|
||||
Linux font issues - Fedora specific
|
||||
|
||||
App response to the user pausing and canceling import or export
|
||||
|
||||
Upgrade errors
|
||||
- Linux font issues - Fedora specific
|
||||
- App response to the user pausing and canceling import or export
|
||||
- Upgrade errors
|
||||
|
||||
|
||||
## v1.1.2
|
||||
- 2020-09-23
|
||||
|
||||
### New
|
||||
|
||||
Improving performance
|
||||
|
||||
* Speed up import by implementing parallel processing (parallel fetch, encrypt and upload of messages)
|
||||
* Optimising the initial fetch of messages from external accounts
|
||||
|
||||
Better message parsing
|
||||
|
||||
* Better handling of attachments and non-standard formatting
|
||||
* Improved stability of the message parser
|
||||
|
||||
Improved metrics
|
||||
|
||||
* Added persistent anonymous API cookies
|
||||
|
||||
- Improving performance
|
||||
- Speed up import by implementing parallel processing (parallel fetch, encrypt and upload of messages)
|
||||
- Optimising the initial fetch of messages from external accounts
|
||||
- Better message parsing
|
||||
- Better handling of attachments and non-standard formatting
|
||||
- Improved stability of the message parser
|
||||
- Improved metrics
|
||||
- Added persistent anonymous API cookies
|
||||
|
||||
### Fixed
|
||||
|
||||
Fixed issues causing failing of import
|
||||
|
||||
* Import from mbox files with long lines
|
||||
* Improvements to import from Yahoo accounts
|
||||
- Fixed issues causing failing of import
|
||||
- Import from mbox files with long lines
|
||||
- Improvements to import from Yahoo accounts
|
||||
|
||||
@ -117,3 +117,64 @@ Feature: IMAP import messages
|
||||
Then IMAP response is "OK"
|
||||
And API mailbox "INBOX" for "user" has 0 message
|
||||
And API mailbox "Sent" for "user" has 1 message
|
||||
|
||||
Scenario: Import embedded message
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: Foo <foo@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: Embedded message
|
||||
Content-Type: multipart/mixed; boundary="boundary"
|
||||
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--boundary
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
--boundary
|
||||
Content-Type: message/rfc822; name="embedded.eml"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="embedded.eml"
|
||||
|
||||
From: Bar <bar@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: (No Subject)
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
hello
|
||||
|
||||
--boundary--
|
||||
|
||||
"""
|
||||
Then IMAP response is "OK"
|
||||
|
||||
# We cannot control internal IDs on live server.
|
||||
@ignore-live
|
||||
Scenario: Import existing message
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: Foo <foo@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: Hello
|
||||
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||
X-Pm-Internal-Id: 1
|
||||
|
||||
Hello
|
||||
|
||||
"""
|
||||
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
|
||||
When IMAP client imports message to "INBOX"
|
||||
"""
|
||||
From: Foo <foo@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: Hello
|
||||
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||
X-Pm-Internal-Id: 1
|
||||
|
||||
Hello
|
||||
|
||||
"""
|
||||
Then IMAP response is "OK \[APPENDUID \d 1\] APPEND completed"
|
||||
|
||||
38
test/features/bridge/smtp/send/embedded_message.feature
Normal file
38
test/features/bridge/smtp/send/embedded_message.feature
Normal file
@ -0,0 +1,38 @@
|
||||
Feature: SMTP sending embedded message
|
||||
Scenario: Send it
|
||||
Given there is connected user "user"
|
||||
And there is SMTP client logged in as "user"
|
||||
When SMTP client sends message
|
||||
"""
|
||||
From: Bridge Test <[userAddress]>
|
||||
To: Internal Bridge <bridgetest@protonmail.com>
|
||||
Subject: Embedded message
|
||||
Content-Type: multipart/mixed; boundary="boundary"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--boundary
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
--boundary
|
||||
Content-Type: message/rfc822; name="embedded.eml"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="embedded.eml"
|
||||
|
||||
From: Bar <bar@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: (No Subject)
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
hello
|
||||
|
||||
--boundary--
|
||||
|
||||
|
||||
"""
|
||||
Then SMTP response is "OK"
|
||||
And mailbox "Sent" for "user" has messages
|
||||
| from | to | subject |
|
||||
| [userAddress] | bridgetest@protonmail.com | Embedded message |
|
||||
@ -294,3 +294,42 @@ Feature: SMTP sending of HTML messages
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario: HTML message with extremely long line (greater than default 2000 line limit) to external account
|
||||
When SMTP client sends message
|
||||
"""
|
||||
From: Bridge Test <[userAddress]>
|
||||
To: External Bridge <pm.bridge.qa@gmail.com>
|
||||
Subject: HTML text external
|
||||
Content-Disposition: inline
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/html; charset=utf-8
|
||||
In-Reply-To: <base64hashOfSomeMessage@protonmail.internalid>
|
||||
|
||||
<html><body>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<body></html>
|
||||
|
||||
"""
|
||||
Then SMTP response is "OK"
|
||||
And mailbox "Sent" for "user" has messages
|
||||
| from | to | subject |
|
||||
| [userAddress] | pm.bridge.qa@gmail.com | HTML text external |
|
||||
And message is sent with API call
|
||||
"""
|
||||
{
|
||||
"Message": {
|
||||
"Subject": "HTML text external",
|
||||
"Sender": {
|
||||
"Name": "Bridge Test"
|
||||
},
|
||||
"ToList": [
|
||||
{
|
||||
"Address": "pm.bridge.qa@gmail.com",
|
||||
"Name": "External Bridge"
|
||||
}
|
||||
],
|
||||
"CCList": [],
|
||||
"BCCList": [],
|
||||
"MIMEType": "text/html"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
43
test/features/ie/transfer/import_embedded.feature
Normal file
43
test/features/ie/transfer/import_embedded.feature
Normal file
@ -0,0 +1,43 @@
|
||||
Feature: Import embedded message
|
||||
Background:
|
||||
Given there is connected user "user"
|
||||
And there is EML file "Inbox/hello.eml"
|
||||
"""
|
||||
From: Foo <foo@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: Embedded message
|
||||
Content-Type: multipart/mixed; boundary="boundary"
|
||||
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--boundary
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
|
||||
--boundary
|
||||
Content-Type: message/rfc822; name="embedded.eml"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="embedded.eml"
|
||||
|
||||
From: Bar <bar@example.com>
|
||||
To: Bridge Test <bridgetest@pm.test>
|
||||
Subject: (No Subject)
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
hello
|
||||
|
||||
--boundary--
|
||||
|
||||
"""
|
||||
|
||||
Scenario: Import it
|
||||
When user "user" imports local files
|
||||
Then progress result is "OK"
|
||||
And transfer exported 1 messages
|
||||
And transfer imported 1 messages
|
||||
And transfer failed for 0 messages
|
||||
And API mailbox "INBOX" for "user" has messages
|
||||
| from | to | subject |
|
||||
| foo@example.com | bridgetest@pm.test | Embedded message |
|
||||
@ -35,6 +35,16 @@ Feature: Import to sent
|
||||
| foo@example.com | bridgetest@pm.test | one |
|
||||
| bar@example.com | bridgetest@pm.test | two |
|
||||
|
||||
# Messages imported to label only are added automatically to Archive folder.
|
||||
# Then it depends on the order: if the message is first imported to Sent
|
||||
# folder and later to that label with importing to Archive, message will not
|
||||
# be in Sent but Archive. The order is semi-random for the big messages,
|
||||
# e.g., it will do alphabetical order of mailboxes, but for under ten small
|
||||
# messages the order is random every time (because we are importing in
|
||||
# batches of up to ten messages and iterating through map we use to collect
|
||||
# messages is random). So we cannot for this test ensure the same output
|
||||
# every time.
|
||||
@ignore-live
|
||||
Scenario: Import to sent and custom label
|
||||
And there is EML file "Label/one.eml"
|
||||
"""
|
||||
|
||||
@ -19,6 +19,7 @@ package tests
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
@ -47,9 +48,12 @@ func imapResponseIs(expectedResponse string) error {
|
||||
|
||||
func imapResponseNamedIs(clientID, expectedResponse string) error {
|
||||
res := ctx.GetIMAPLastResponse(clientID)
|
||||
if expectedResponse == "OK" {
|
||||
switch {
|
||||
case expectedResponse == "OK":
|
||||
res.AssertOK()
|
||||
} else {
|
||||
case strings.HasPrefix(expectedResponse, "OK"):
|
||||
res.AssertResult(expectedResponse)
|
||||
default:
|
||||
res.AssertError(expectedResponse)
|
||||
}
|
||||
return ctx.GetTestingError()
|
||||
|
||||
@ -107,6 +107,13 @@ func (ir *IMAPResponse) AssertOK() *IMAPResponse {
|
||||
return ir
|
||||
}
|
||||
|
||||
func (ir *IMAPResponse) AssertResult(wantResult string) *IMAPResponse {
|
||||
ir.wait()
|
||||
a.NoError(ir.t, ir.err)
|
||||
a.Regexp(ir.t, wantResult, ir.result, "Expected result %s but got %s", wantResult, ir.result)
|
||||
return ir
|
||||
}
|
||||
|
||||
func (ir *IMAPResponse) AssertError(wantErrMsg string) *IMAPResponse {
|
||||
ir.wait()
|
||||
if ir.err == nil {
|
||||
|
||||
@ -60,9 +60,9 @@ func userImportsLocalFilesToAddress(bddUserID, bddAddressID string) error {
|
||||
}
|
||||
|
||||
func userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForImport()
|
||||
return ctx.GetImportExport().GetLocalImporter(address, path)
|
||||
return ctx.GetImportExport().GetLocalImporter(username, address, path)
|
||||
})
|
||||
}
|
||||
|
||||
@ -81,9 +81,9 @@ func userImportsRemoteMessagesToAddress(bddUserID, bddAddressID string) error {
|
||||
}
|
||||
|
||||
func userImportsRemoteMessagesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||
return ctx.GetImportExport().GetRemoteImporter(address, imapServer.Username, imapServer.Password, imapServer.Host, imapServer.Port)
|
||||
return ctx.GetImportExport().GetRemoteImporter(username, address, imapServer.Username, imapServer.Password, imapServer.Host, imapServer.Port)
|
||||
})
|
||||
}
|
||||
|
||||
@ -102,9 +102,9 @@ func userExportsAddressToEMLFiles(bddUserID, bddAddressID string) error {
|
||||
}
|
||||
|
||||
func userExportsAddressToEMLFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForExport()
|
||||
return ctx.GetImportExport().GetEMLExporter(address, path)
|
||||
return ctx.GetImportExport().GetEMLExporter(username, address, path)
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,20 +123,20 @@ func userExportsAddressToMBOXFiles(bddUserID, bddAddressID string) error {
|
||||
}
|
||||
|
||||
func userExportsAddressToMBOXFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
||||
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||
path := ctx.GetTransferLocalRootForExport()
|
||||
return ctx.GetImportExport().GetMBOXExporter(address, path)
|
||||
return ctx.GetImportExport().GetMBOXExporter(username, address, path)
|
||||
})
|
||||
}
|
||||
|
||||
// Helpers.
|
||||
|
||||
func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTransferrer func(string) (*transfer.Transfer, error)) error {
|
||||
func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTransferrer func(string, string) (*transfer.Transfer, error)) error {
|
||||
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
|
||||
if account == nil {
|
||||
return godog.ErrPending
|
||||
}
|
||||
transferrer, err := getTransferrer(account.Address())
|
||||
transferrer, err := getTransferrer(account.Username(), account.Address())
|
||||
if err != nil {
|
||||
return internalError(err, "failed to init transfer")
|
||||
}
|
||||
|
||||
@ -17,10 +17,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
## Make sure that mac exe will not contain broken library links
|
||||
# * remove absolute paths for Qt libs
|
||||
# * add relative part to app bundle Frameworks
|
||||
# The Qt libs are dynamically loaded with rules like: `@rpath/QtGui.framework/Versions/5/QtGui`
|
||||
# @rpath instructs the dynamic linker to search a list of paths in order to locate the framework
|
||||
# The rules can be listed using `otool -l "${path_to_binary}"`
|
||||
# The building process of therecipe/qt or qmake leaves the rules with additinal unwanted paths
|
||||
# + absolute path to build directory
|
||||
# + dummy replacement `/break_the_rpath`
|
||||
# We need to manually remove those and add the path relative to exectuable: `@executable_path/../Frameworks`
|
||||
|
||||
path_to_binary=$1
|
||||
|
||||
@ -29,11 +32,11 @@ if [ -z ${path_to_binary} ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
for remove_path_qt in $(otool -l "${path_to_binary}" | grep '/Users/' | awk '{print $2}');
|
||||
for path_to_remove in $(otool -l "${path_to_binary}" | egrep '/Users/|break_the_rpath' | awk '{print $2}');
|
||||
do
|
||||
if [ ! -z "${remove_path_qt}" ]; then
|
||||
printf "\e[0;32mRemove path to qt ${remove_path_qt} ...\033[0m\n\e[0;31m"
|
||||
install_name_tool -delete_rpath "${remove_path_qt}" "${path_to_binary}" || exit 1
|
||||
if [ ! -z "${path_to_remove}" ]; then
|
||||
printf "\e[0;32mRemove path to qt '${path_to_remove}' ...\033[0m\n\e[0;31m"
|
||||
install_name_tool -delete_rpath "${path_to_remove}" "${path_to_binary}" || exit 1
|
||||
fi
|
||||
done
|
||||
rpath_rule=$(otool -l "${path_to_binary}" | grep executable_path | awk '{print $2}')
|
||||
|
||||
Reference in New Issue
Block a user