Compare commits

..

36 Commits

Author SHA1 Message Date
cccadaee42 Other: Bridge HZM 1.6.7 & Import-Export Farg 1.3.2 2021-03-24 15:11:46 +01:00
bbb365f8a5 Merge branch 'release/farg' into devel 2021-03-24 14:55:55 +01:00
1f18d9d917 GODT-1117 Do not change updates location for Bridge now 2021-03-24 10:45:55 +01:00
59e0d63485 GODT-1105 Fix: Dylib hijack vulnerability found by https://objective-see.com/products/dhs.html 2021-03-24 08:37:30 +00:00
72fe5a636e GODT-1063: Add metainfo to launcher
Refactor metainfo file a bit
2021-03-24 07:04:28 +00:00
45a83133ba Other: increase SMTP line limit to 2^16 2021-03-17 11:45:54 +01:00
215eb4d6eb GODT-1085 Ignore live test of importing to sent and custom label 2021-03-17 08:10:50 +01:00
479b951c50 GODT-1076 Fix UIDPLUS response for importing existing message 2021-03-16 11:55:36 +00:00
a94c8a943f GODT-1077 IMAP sync counting 2021-03-16 12:35:36 +01:00
ea306f405e Other: print address mode in info level 2021-03-12 09:02:54 +01:00
1b405506b8 Merge remote-tracking branch 'remotes/origin/release-notes' into farg 2021-03-11 00:01:21 +01:00
38c6132f81 Other: Import-Export Farg 1.3.1 2021-03-11 00:00:40 +01:00
b7351dfaf8 Other 2021-03-10 21:52:52 +00:00
7e8f6943f2 Other 2021-03-10 21:35:31 +00:00
a0132e8440 GODT-1047 No silent updates for Import-Export app 2021-03-10 18:56:55 +00:00
27541784aa Merge master into devel 2021-03-10 14:52:45 +01:00
9e567f08b2 Other: release notes for 1.6.6 stable 2021-03-04 11:56:11 +00:00
bf274f984e Other: include latest go.mod/go.sum changes 2021-03-04 11:25:33 +00:00
3b60bbe13b Other 2021-03-04 09:50:29 +00:00
a73a1b623a GODT-803 Fix import to wrong target address 2021-03-02 16:02:23 +01:00
c0a8877018 Other: include latest go.mod/go.sum changes 2021-03-01 17:48:22 +01:00
904166c01c GODT-247 Cache and update files moved from user's cache to config 2021-03-01 14:06:58 +00:00
4761bc935a GODT-948 Embedded messages 2021-03-01 09:22:08 +00:00
71301d891f Other 2021-02-28 20:55:23 +01:00
d47be3c4c0 GODT-1043 Fix showing long login error in GUI dialog 2021-02-26 12:21:12 +00:00
199a4d1e3a Other: Release Bridge HZM 1.6.6 2021-02-25 16:28:17 +01:00
18668aafc9 GODT-1029: Fix tray icon not updating under certain conditions 2021-02-25 14:53:43 +00:00
fd73ec6861 GODT-1062: Fix lost notification bar when window is closed 2021-02-25 14:53:43 +00:00
feeb7179f5 GODT-1058 Install after chaning channel right away only in case of downgrade 2021-02-25 14:47:12 +00:00
0e5a45671f GODT-1073 Added: Re-write autostart link on every start if turned on in preferences. 2021-02-24 19:32:59 +00:00
2beb0d298e Other: QA build checks for update every 5 minutes 2021-02-24 20:34:13 +01:00
22a6fcd87f Other: add debug message dump when sending 2021-02-23 10:38:15 +00:00
f499252444 GODT-1055 Fix flaky empty trash test 2021-02-23 08:37:07 +00:00
b27e3fdb28 Merge release/hzm in devel 2021-02-22 17:36:31 +01:00
415e56d928 Other Update bridge_early.md 2021-02-22 15:42:30 +01:00
2a078b76e6 GODT-1045 build without Qt by default 2021-02-18 09:45:18 +00:00
90 changed files with 1180 additions and 377 deletions

View File

@ -2,6 +2,70 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/) Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [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
* Other: QA build checks for update every 5 minutes.
* Other: QA build adds debug message dump when sending.
### Changed
* GODT-1045 build without Qt by default.
### Fixed
* GODT-1029 Fix tray icon not updating under certain conditions.
* GODT-1062 Fix lost notification bar when window is closed.
* GODT-1058 Install version after chaning channel right away only in case of downgrade.
* GODT-1073 Re-write autostart link on every start if turned on in preferences.
* GODT-1055 Fix flaky empty trash test.
## [Bridge 1.6.5] HZM ## [Bridge 1.6.5] HZM
### Changed ### Changed

View File

@ -10,8 +10,8 @@ TARGET_OS?=${GOOS}
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher .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. # Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=1.6.5+git BRIDGE_APP_VERSION?=1.6.7+git
IE_APP_VERSION?=1.3.0+git IE_APP_VERSION?=1.3.2+git
APP_VERSION:=${BRIDGE_APP_VERSION} APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico SRC_ICO:=logo.ico
SRC_ICNS:=Bridge.icns SRC_ICNS:=Bridge.icns
@ -19,6 +19,7 @@ SRC_SVG:=logo.svg
TGT_ICNS:=Bridge.icns TGT_ICNS:=Bridge.icns
EXE_NAME:=proton-bridge EXE_NAME:=proton-bridge
CONFIGNAME:=bridge CONFIGNAME:=bridge
WINDRES_DEFINE:=BUILD_BRIDGE
ifeq "${TARGET_CMD}" "Import-Export" ifeq "${TARGET_CMD}" "Import-Export"
APP_VERSION:=${IE_APP_VERSION} APP_VERSION:=${IE_APP_VERSION}
SRC_ICO:=ie.ico SRC_ICO:=ie.ico
@ -27,13 +28,14 @@ ifeq "${TARGET_CMD}" "Import-Export"
TGT_ICNS:=ImportExport.icns TGT_ICNS:=ImportExport.icns
EXE_NAME:=proton-ie EXE_NAME:=proton-ie
CONFIGNAME:=importExport CONFIGNAME:=importExport
WINDRES_DEFINE:=BUILD_IE
endif endif
REVISION:=$(shell git rev-parse --short=10 HEAD) REVISION:=$(shell git rev-parse --short=10 HEAD)
BUILD_TIME:=$(shell date +%FT%T%z) BUILD_TIME:=$(shell date +%FT%T%z)
BUILD_FLAGS:=-tags='${BUILD_TAGS}' BUILD_FLAGS:=-tags='${BUILD_TAGS}'
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS} BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui' BUILD_FLAGS_GUI:=-tags='${BUILD_TAGS} build_qt'
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/internal/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME}) GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/internal/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
ifneq "${BUILD_LDFLAGS}" "" ifneq "${BUILD_LDFLAGS}" ""
GO_LDFLAGS+=${BUILD_LDFLAGS} GO_LDFLAGS+=${BUILD_LDFLAGS}
@ -45,7 +47,7 @@ ifeq "${TARGET_OS}" "windows"
endif endif
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}' BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS_NOGUI+=-ldflags '${GO_LDFLAGS}' BUILD_FLAGS_GUI+=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}' BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
@ -56,7 +58,7 @@ EXE_QT:=${DIRNAME}
ifeq "${TARGET_OS}" "windows" ifeq "${TARGET_OS}" "windows"
EXE:=${EXE}.exe EXE:=${EXE}.exe
EXE_QT:=${EXE_QT}.exe EXE_QT:=${EXE_QT}.exe
ICO_FILES:=${SRC_ICO} icon.rc icon_windows.syso RESOURCE_FILE:=resource.syso
endif endif
ifeq "${TARGET_OS}" "darwin" ifeq "${TARGET_OS}" "darwin"
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
@ -84,13 +86,19 @@ build-ie:
TARGET_CMD=Import-Export $(MAKE) build TARGET_CMD=Import-Export $(MAKE) build
build-nogui: gofiles build-nogui: gofiles
go build ${BUILD_FLAGS_NOGUI} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go go build ${BUILD_FLAGS} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
build-ie-nogui: build-ie-nogui:
TARGET_CMD=Import-Export $(MAKE) build-nogui TARGET_CMD=Import-Export $(MAKE) build-nogui
build-launcher: ifeq "${GOOS}" "windows"
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${APP} cmd/launcher/main.go 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: build-launcher-ie:
TARGET_CMD=Import-Export $(MAKE) build-launcher TARGET_CMD=Import-Export $(MAKE) build-launcher
@ -134,21 +142,20 @@ ifneq "${GOOS}" "${TARGET_OS}"
endif endif
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} rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
cp cmd/${TARGET_CMD}/main.go . cp cmd/${TARGET_CMD}/main.go .
qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET} qtdeploy ${BUILD_FLAGS_GUI} ${QT_BUILD_TARGET}
mv deploy cmd/${TARGET_CMD} mv deploy cmd/${TARGET_CMD}
if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi
rm -rf ${TARGET_OS} main.go 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 ## Rules for therecipe/qt
.PHONY: prepare-vendor update-vendor update-qt-docs .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_IMAP?=client # client/server/all, or empty to turn it off
LOG_SMTP?=--log-smtp # 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?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
RUN_FLAGS_IE?=-m -l=${LOG}
run: run-nogui-cli run: run-nogui-cli
@ -303,12 +311,12 @@ run-qt-cli: ${EXE_TARGET}
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
run-nogui: clean-vendor gofiles run-nogui: clean-vendor gofiles
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log
run-nogui-cli: clean-vendor gofiles run-nogui-cli: clean-vendor gofiles
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c
run-debug: run-debug:
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS_NOGUI}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS} PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS}
run-qml-preview: run-qml-preview:
$(MAKE) -C internal/frontend/qt -f Makefile.local qmlpreview $(MAKE) -C internal/frontend/qt -f Makefile.local qmlpreview
@ -316,11 +324,11 @@ run-ie-qml-preview:
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview $(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
run-ie: run-ie:
TARGET_CMD=Import-Export $(MAKE) run TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run
run-ie-qt: 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: 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: clean-frontend-qt:
$(MAKE) -C internal/frontend/qt -f Makefile.local clean $(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/Desktop-Bridge/deploy
rm -rf cmd/Import-Export/deploy rm -rf cmd/Import-Export/deploy
rm -f build last.log mem.pprof main.go 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/bridge.html
rm -f release-notes/import-export.html rm -f release-notes/import-export.html
@ -345,3 +353,5 @@ clean: clean-vendor
generate: generate:
go generate ./... go generate ./...
$(MAKE) add-license $(MAKE) add-license
.FORCE:

3
go.mod
View File

@ -54,12 +54,13 @@ require (
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/pkg/errors v0.9.1 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/sirupsen/logrus v1.7.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e 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/urfave/cli/v2 v2.2.0
github.com/vmihailenco/msgpack/v5 v5.1.3 github.com/vmihailenco/msgpack/v5 v5.1.3
go.etcd.io/bbolt v1.3.5 go.etcd.io/bbolt v1.3.5

8
go.sum
View File

@ -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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= 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/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 h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us= 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.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 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 v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=

View File

@ -136,6 +136,7 @@ func New( // nolint[funlen]
if err := logging.Init(logsPath); err != nil { if err := logging.Init(logsPath); err != nil {
return nil, err return nil, err
} }
logging.SetLevel("debug") // Proper level is set later in run.
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath)) crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
if err := migrateFiles(configName); err != nil { if err := migrateFiles(configName); err != nil {

View File

@ -29,10 +29,12 @@ import (
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations. // migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
// We can remove this eventually. // We can remove this eventually.
// //
// | entity | old location | new location | // | entity | old location | new location |
// |--------|-------------------------------------------|----------------------------------------| // |-----------|-------------------------------------------|----------------------------------------|
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json | // | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
// | c11 | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 | // | 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 { func migrateFiles(configName string) error {
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName)) locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
if err != nil { if err != nil {
@ -41,43 +43,89 @@ func migrateFiles(configName string) error {
locations := locations.New(locationsProvider, configName) locations := locations.New(locationsProvider, configName)
userCacheDir := locationsProvider.UserCache() 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() newSettingsDir, err := locations.ProvideSettingsPath()
if err != nil { if err != nil {
return err return err
} }
if err := moveIfExists( return moveIfExists(
filepath.Join(userCacheDir, "c11", "prefs.json"), filepath.Join(userCacheDir, "c11", "prefs.json"),
filepath.Join(newSettingsDir, "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 { if err != nil {
return err return err
} }
// Migration for versions before 1.6.x.
if err := moveIfExists( if err := moveIfExists(
filepath.Join(userCacheDir, "c11"), filepath.Join(olderCacheDir, "c11"),
filepath.Join(newCacheDir, "c11"), filepath.Join(latestCacheDir, "c11"),
); err != nil { ); err != nil {
return err 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 { func moveIfExists(source, destination string) error {
l := logrus.WithField("source", source).WithField("destination", destination)
if _, err := os.Stat(source); os.IsNotExist(err) { if _, err := os.Stat(source); os.IsNotExist(err) {
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file") l.Debug("No need to migrate file, source doesn't exist")
return nil return nil
} }
if _, err := os.Stat(destination); !os.IsNotExist(err) { 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 return nil
} }
l.Info("Migrating files")
return os.Rename(source, destination) return os.Rename(source, destination)
} }

View File

@ -139,7 +139,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
// Watch for updates routine // Watch for updates routine
go func() { go func() {
ticker := time.NewTicker(time.Hour) ticker := time.NewTicker(constants.UpdateCheckInterval)
for { for {
checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey)) checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey))

View File

@ -28,8 +28,6 @@ import (
"github.com/ProtonMail/proton-bridge/internal/frontend" "github.com/ProtonMail/proton-bridge/internal/frontend"
"github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/importexport" "github.com/ProtonMail/proton-bridge/internal/importexport"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -88,7 +86,7 @@ func run(b *base.Base, c *cli.Context) error {
return f.Loop() 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]
version, err := u.Check() version, err := u.Check()
if err != nil { if err != nil {
logrus.WithError(err).Error("An error occurred while checking for updates") logrus.WithError(err).Error("An error occurred while checking for updates")
@ -107,27 +105,5 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
logrus.WithField("version", version.Version).Info("An update is available") logrus.WithField("version", version.Version).Info("An update is available")
if !autoUpdate { f.NotifyManualUpdate(version, u.CanInstall(version))
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()
} }

View File

@ -151,28 +151,33 @@ func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
// Downgrading to previous version (by switching from early to stable, for example) // Downgrading to previous version (by switching from early to stable, for example)
// requires clearing all data including update files due to possibility of // requires clearing all data including update files due to possibility of
// inconsistency between versions and absence of backwards migration scripts. // inconsistency between versions and absence of backwards migration scripts.
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) error { func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) (needRestart bool, err error) {
b.settings.Set(settings.UpdateChannelKey, string(channel)) b.settings.Set(settings.UpdateChannelKey, string(channel))
version, err := b.updater.Check() version, err := b.updater.Check()
if err != nil { if err != nil {
return err return false, err
} }
if b.updater.IsDowngrade(version) { // We have to deal right away only with downgrade - that action needs to
if err := b.Users.ClearData(); err != nil { // clear data and updates, and install bridge right away. But regular
log.WithError(err).Error("Failed to clear data while downgrading channel") // upgrade can be leaved out for periodic check.
} if !b.updater.IsDowngrade(version) {
if err := b.locations.ClearUpdates(); err != nil { return false, nil
log.WithError(err).Error("Failed to clear updates while downgrading channel") }
}
if err := b.Users.ClearData(); err != nil {
log.WithError(err).Error("Failed to clear data while downgrading channel")
}
if err := b.locations.ClearUpdates(); err != nil {
log.WithError(err).Error("Failed to clear updates while downgrading channel")
} }
if err := b.updater.InstallUpdate(version); err != nil { if err := b.updater.InstallUpdate(version); err != nil {
return err return false, err
} }
return b.versioner.RemoveOtherVersions(version.Version) return true, b.versioner.RemoveOtherVersions(version.Version)
} }
// GetKeychainApp returns current keychain helper. // GetKeychainApp returns current keychain helper.

View File

@ -0,0 +1,28 @@
// 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/>.
// +build !build_qa
package constants
import "time"
// nolint[gochecknoglobals]
var (
// UpdateCheckInterval defines how often we check for new version
UpdateCheckInterval = time.Hour //nolint[gochecknoglobals]
)

View File

@ -0,0 +1,28 @@
// 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/>.
// +build build_qa
package constants
import "time"
// nolint[gochecknoglobals]
var (
// UpdateCheckInterval defines how often we check for new version
UpdateCheckInterval = time.Duration(5 * time.Minute)
)

View File

@ -38,7 +38,7 @@ func (f *frontendCLI) importLocalMessages(c *ishell.Context) {
return 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) f.transfer(t, err, false, true)
} }
@ -68,7 +68,7 @@ func (f *frontendCLI) importRemoteMessages(c *ishell.Context) {
return 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) f.transfer(t, err, false, true)
} }
@ -81,7 +81,7 @@ func (f *frontendCLI) exportMessagesToEML(c *ishell.Context) {
return 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) f.transfer(t, err, true, false)
} }
@ -94,7 +94,7 @@ func (f *frontendCLI) exportMessagesToMBOX(c *ishell.Context) {
return 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) f.transfer(t, err, true, false)
} }

View File

@ -81,9 +81,13 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
f.Println("Bridge is currently on the stable update channel.") f.Println("Bridge is currently on the stable update channel.")
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") { if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
if err := f.bridge.SetUpdateChannel(updater.EarlyChannel); err != nil { needRestart, err := f.bridge.SetUpdateChannel(updater.EarlyChannel)
if err != nil {
f.Println("There was a problem switching update channel.") f.Println("There was a problem switching update channel.")
} }
if needRestart {
f.restarter.SetToRestart()
}
} }
} }
@ -97,9 +101,12 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
f.Println("Switching to the stable channel may reset all data!") f.Println("Switching to the stable channel may reset all data!")
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") { if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
if err := f.bridge.SetUpdateChannel(updater.StableChannel); err != nil { needRestart, err := f.bridge.SetUpdateChannel(updater.StableChannel)
if err != nil {
f.Println("There was a problem switching update channel.") f.Println("There was a problem switching update channel.")
} }
f.restarter.SetToRestart() if needRestart {
f.restarter.SetToRestart()
}
} }
} }

View File

@ -229,7 +229,7 @@ Dialog {
currentIndex : 0 currentIndex : 0
title : qsTr("Clear cache", "title of page that displays during cache clearing") title : qsTr("Clear cache", "title of page that displays during cache clearing")
question : qsTr("Are you sure you want to clear your local cache?", "displays during cache clearing") question : qsTr("Are you sure you want to clear your local cache?", "displays during cache clearing")
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, temporarily slowing down the email download process significantly.", "displays during cache clearing") note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.", "displays during cache clearing")
answer : qsTr("Clearing the cache ...", "displays during cache clearing") answer : qsTr("Clearing the cache ...", "displays during cache clearing")
} }
}, },
@ -310,7 +310,7 @@ Dialog {
target: root target: root
currentIndex : 0 currentIndex : 0
question : qsTr("Are you sure you want to leave early access? Please keep in mind this operation clears the cache and restarts Bridge.") question : qsTr("Are you sure you want to leave early access? Please keep in mind this operation clears the cache and restarts Bridge.")
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, temporarily slowing down the email download process significantly.") note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.")
title : qsTr("Disable early access") title : qsTr("Disable early access")
answer : qsTr("Disabling early access...") answer : qsTr("Disabling early access...")
} }

View File

@ -107,17 +107,53 @@ Item {
gui.openMainWindow(false) gui.openMainWindow(false)
if (go.isConnectionOK) { if (go.isConnectionOK) {
if( winMain.updateState=="noInternet") { if( winMain.updateState=="noInternet") {
go.setUpdateState("upToDate") go.updateState = "upToDate"
} }
} else { } else {
go.setUpdateState("noInternet") go.updateState = "noInternet"
} }
} }
onSetUpdateState : { onUpdateStateChanged : {
// Update tray icon if needed
switch (go.updateState) {
case "internetCheck":
break;
case "noInternet" :
gui.warningFlags |= Style.warnInfoBar
break;
case "oldVersion":
gui.warningFlags |= Style.warnInfoBar
break;
case "forceUpdate":
// Force update should presist once it happened and never be overwritten.
// That means that tray icon should allways remain in error state.
// But since we have only two sources of error icon in tray (force update
// + installation fail) and both are unrecoverable and we do not ever remove
// error flag from gui.warningFlags - it is ok to rely on gui.warningFlags and
// not on winMain.updateState (which presist forceUpdate)
gui.warningFlags |= Style.errorInfoBar
break;
case "upToDate":
gui.warningFlags &= ~Style.warnInfoBar
break;
case "updateRestart":
gui.warningFlags |= Style.warnInfoBar
break;
case "updateError":
gui.warningFlags |= Style.errorInfoBar
break;
default :
break;
}
// if main window is closed - most probably it is destroyed (see closeMainWindow())
if (winMain == null) {
return
}
// once app is outdated prevent from state change // once app is outdated prevent from state change
if (winMain.updateState != "forceUpdate") { if (winMain.updateState != "forceUpdate") {
winMain.updateState = updateState winMain.updateState = go.updateState
} }
} }
@ -129,15 +165,14 @@ Item {
} }
onNotifyManualUpdate: { onNotifyManualUpdate: {
go.setUpdateState("oldVersion") go.updateState = "oldVersion"
} }
onNotifyManualUpdateRestartNeeded: { onNotifyManualUpdateRestartNeeded: {
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
gui.openMainWindow(true)
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
go.setUpdateState("updateRestart") go.updateState = "updateRestart"
winMain.dialogUpdate.finished(false) winMain.dialogUpdate.finished(false)
// after manual update - just retart immidiatly // after manual update - just retart immidiatly
@ -147,28 +182,25 @@ Item {
onNotifyManualUpdateError: { onNotifyManualUpdateError: {
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
gui.openMainWindow(true)
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
go.setUpdateState("updateError") go.updateState = "updateError"
winMain.dialogUpdate.finished(true) winMain.dialogUpdate.finished(true)
} }
onNotifyForceUpdate : { onNotifyForceUpdate : {
go.setUpdateState("forceUpdate") go.updateState = "forceUpdate"
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
gui.openMainWindow(true)
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
} }
onNotifySilentUpdateRestartNeeded: { onNotifySilentUpdateRestartNeeded: {
go.setUpdateState("updateRestart") go.updateState = "updateRestart"
} }
onNotifySilentUpdateError: { onNotifySilentUpdateError: {
go.setUpdateState("updateError") go.updateState = "updateError"
gui.openMainWindow(true)
} }
onNotifyLogout : { onNotifyLogout : {
@ -287,9 +319,17 @@ Item {
if (showAndRise) { if (showAndRise) {
gui.winMain.showAndRise() gui.winMain.showAndRise()
} }
// restore update notification bar: trigger updateStateChanged
var tmp = go.updateState
go.updateState = ""
go.updateState = tmp
} }
function closeMainWindow () { function closeMainWindow () {
// Historical reasons: once upon a time there was a report about high GPU
// usage on MacOS while bridge is closed. Legends say that destroying
// MainWindow solved this.
gui.winMain.hide() gui.winMain.hide()
gui.winMain.destroy(5000) gui.winMain.destroy(5000)
gui.winMain = null gui.winMain = null

View File

@ -30,7 +30,6 @@ Item {
id: gui id: gui
property alias winMain: winMain property alias winMain: winMain
property bool isFirstWindow: true property bool isFirstWindow: true
property int warningFlags: 0
property var locale : Qt.locale("en_US") property var locale : Qt.locale("en_US")
property date netBday : new Date("1989-03-13T00:00:00") property date netBday : new Date("1989-03-13T00:00:00")
@ -96,17 +95,17 @@ Item {
go.isConnectionOK = isAvailable go.isConnectionOK = isAvailable
if (go.isConnectionOK) { if (go.isConnectionOK) {
if( winMain.updateState==gui.enums.statusNoInternet) { if( winMain.updateState==gui.enums.statusNoInternet) {
go.setUpdateState(gui.enums.statusUpToDate) go.updateState = gui.enums.statusUpToDate
} }
} else { } else {
go.setUpdateState(gui.enums.statusNoInternet) go.updateState = gui.enums.statusNoInternet
} }
} }
onSetUpdateState : { onUpdateStateChanged : {
// once app is outdated prevent from state change // once app is outdated prevent from state change
if (winMain.updateState != "forceUpdate") { if (winMain.updateState != "forceUpdate") {
winMain.updateState = updateState winMain.updateState = go.updateState
} }
} }
@ -207,16 +206,16 @@ Item {
} }
onNotifyManualUpdate: { onNotifyManualUpdate: {
go.setUpdateState("oldVersion") go.updateState = "oldVersion"
} }
onNotifyManualUpdateRestartNeeded: { onNotifyManualUpdateRestartNeeded: {
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
go.setUpdateState("updateRestart") go.updateState = "updateRestart"
winMain.dialogUpdate.finished(false) winMain.dialogUpdate.finished(false)
// after manual update - just retart immidiatly // after manual update - just retart immidiatly
go.setToRestart() go.setToRestart()
Qt.quit() Qt.quit()
@ -226,24 +225,24 @@ Item {
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
go.setUpdateState("updateError") go.updateState = "updateError"
winMain.dialogUpdate.finished(true) winMain.dialogUpdate.finished(true)
} }
onNotifyForceUpdate : { onNotifyForceUpdate : {
go.setUpdateState("forceUpdate") go.updateState = "forceUpdate"
if (!winMain.dialogUpdate.visible) { if (!winMain.dialogUpdate.visible) {
winMain.dialogUpdate.show() winMain.dialogUpdate.show()
} }
} }
onNotifySilentUpdateRestartNeeded: { //onNotifySilentUpdateRestartNeeded: {
go.setUpdateState("updateRestart") // go.updateState = "updateRestart"
} //}
//
onNotifySilentUpdateError: { //onNotifySilentUpdateError: {
go.setUpdateState("updateError") // go.updateState = "updateError"
} //}
onNotifyLogout : { 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) ) 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) )

View File

@ -165,6 +165,7 @@ Column {
textColor : Style.main.textBlue textColor : Style.main.textBlue
onClicked: { onClicked: {
dialogExport.currentIndex = 0 dialogExport.currentIndex = 0
dialogExport.account = account
dialogExport.address = account dialogExport.address = account
dialogExport.show() dialogExport.show()
} }
@ -321,6 +322,7 @@ Column {
textBold: true textBold: true
textColor: Style.main.textBlue textColor: Style.main.textBlue
onClicked: { onClicked: {
dialogExport.account = account
dialogExport.address = listalias[index] dialogExport.address = listalias[index]
dialogExport.show() dialogExport.show()
} }
@ -339,6 +341,7 @@ Column {
textBold: true textBold: true
textColor: enabled ? Style.main.textBlue : Style.main.textDisabled textColor: enabled ? Style.main.textBlue : Style.main.textDisabled
onClicked: { onClicked: {
dialogImport.account = account
dialogImport.address = listalias[index] dialogImport.address = listalias[index]
dialogImport.show() dialogImport.show()
} }

View File

@ -35,6 +35,7 @@ Dialog {
title : set_title() title : set_title()
property string account
property string address property string address
property alias finish: finish property alias finish: finish
@ -428,7 +429,7 @@ Dialog {
onTriggered : { onTriggered : {
switch (currentIndex) { switch (currentIndex) {
case 0: case 0:
go.loadStructureForExport(root.address) go.loadStructureForExport(root.account, root.address)
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0) sourceFoldersInput.hasItems = (transferRules.rowCount() > 0)
break break
case 2: case 2:

View File

@ -34,6 +34,7 @@ Dialog {
isDialogBusy: currentIndex==3 || currentIndex==4 isDialogBusy: currentIndex==3 || currentIndex==4
property string account
property string address property string address
property string inputPath : "" property string inputPath : ""
property bool isFromFile : inputEmail.text == "" && root.inputPath != "" property bool isFromFile : inputEmail.text == "" && root.inputPath != ""
@ -1032,6 +1033,7 @@ Dialog {
root.isFromIMAP, root.isFromIMAP,
root.inputPath, root.inputPath,
inputEmail.text, inputPassword.text, inputServer.text, inputPort.text, inputEmail.text, inputPassword.text, inputServer.text, inputPort.text,
root.account,
root.address root.address
) )
break break

View File

@ -96,6 +96,8 @@ Item {
onClicked: bugreportWin.show() onClicked: bugreportWin.show()
} }
/*
ButtonIconText { ButtonIconText {
id: autoUpdates id: autoUpdates
text: qsTr("Keep the application up to date", "label for toggle that activates and disables the automatic updates") text: qsTr("Keep the application up to date", "label for toggle that activates and disables the automatic updates")
@ -115,8 +117,6 @@ Item {
} }
} }
/*
ButtonIconText { ButtonIconText {
id: cacheClear id: cacheClear
text: qsTr("Clear Cache") text: qsTr("Clear Cache")

View File

@ -18,6 +18,7 @@
// Dialog with adding new user // Dialog with adding new user
import QtQuick 2.8 import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import ProtonUI 1.0 import ProtonUI 1.0
@ -83,6 +84,9 @@ StackLayout {
text : "" text : ""
color: Style.main.textBlue color: Style.main.textBlue
visible: false visible: false
width: root.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
} }
// prevent any action below // prevent any action below

View File

@ -70,7 +70,8 @@ Dialog {
id: topSep id: topSep
color : "transparent" color : "transparent"
width : Style.main.dummy 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 { InputField {

View File

@ -107,7 +107,7 @@ Dialog {
text: qsTr("Automatically update in the future", "Checkbox label for using autoupdates later on") text: qsTr("Automatically update in the future", "Checkbox label for using autoupdates later on")
checked: go.isAutoUpdate checked: go.isAutoUpdate
onToggled: go.toggleAutoUpdate() onToggled: go.toggleAutoUpdate()
visible: !root.forceUpdate visible: !root.forceUpdate && (go.isAutoUpdate != undefined)
} }
Row { Row {

View File

@ -108,31 +108,25 @@ Rectangle {
onStateChanged : { onStateChanged : {
switch (root.state) { switch (root.state) {
case "internetCheck": case "internetCheck":
break; break;
case "noInternet" : case "noInternet" :
gui.warningFlags |= Style.warnInfoBar retryInternet.start()
retryInternet.start() secLeft=checkInterval[iTry]
secLeft=checkInterval[iTry] break;
break;
case "oldVersion": case "oldVersion":
gui.warningFlags |= Style.warnInfoBar break;
break;
case "forceUpdate": case "forceUpdate":
gui.warningFlags |= Style.errorInfoBar break;
break;
case "upToDate": case "upToDate":
gui.warningFlags &= ~Style.warnInfoBar iTry = 0
iTry = 0 secLeft=checkInterval[iTry]
secLeft=checkInterval[iTry] break;
break;
case "updateRestart": case "updateRestart":
gui.warningFlags |= Style.warnInfoBar break;
break;
case "updateError": case "updateError":
gui.warningFlags |= Style.errorInfoBar break;
break;
default : default :
break; break;
} }
if (root.state!="noInternet") { if (root.state!="noInternet") {
@ -271,7 +265,7 @@ Rectangle {
target: closeSign target: closeSign
visible: true visible: true
onClicked: { onClicked: {
root.state = "upToDate" go.updateState = "upToDate"
} }
} }
}, },

View File

@ -24,7 +24,7 @@ import QtQuick.Window 2.2
Window { Window {
id: testroot id: testroot
width : 150 width : 250
height : 600 height : 600
flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint
visible : true visible : true
@ -60,7 +60,7 @@ Window {
Text { Text {
id: systrText id: systrText
anchors { anchors {
right : test_systray.right horizontalCenter: parent.horizontalCenter
verticalCenter: test_systray.verticalCenter verticalCenter: test_systray.verticalCenter
} }
text: "unset" text: "unset"
@ -299,6 +299,7 @@ Window {
property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00" property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00"
property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;" property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;"
property string updateState
property string updateVersion : "QA.1.0" property string updateVersion : "QA.1.0"
property bool updateCanInstall: true property bool updateCanInstall: true
property string updateLandingPage : "https://protonmail.com/bridge/download/" property string updateLandingPage : "https://protonmail.com/bridge/download/"
@ -340,7 +341,6 @@ Window {
signal notifyPortIssue(bool busyPortIMAP, bool busyPortSMTP) signal notifyPortIssue(bool busyPortIMAP, bool busyPortSMTP)
signal notifyVersionIsTheLatest() signal notifyVersionIsTheLatest()
signal setUpdateState(string updateState)
signal notifyKeychainRebuild() signal notifyKeychainRebuild()
signal notifyHasNoKeychain() signal notifyHasNoKeychain()

View File

@ -23,13 +23,13 @@ import QtQuick.Window 2.2
Window { Window {
id : testroot id : testroot
width : 100 width : 150
height : 600 height : 600
flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint
visible : true visible : true
title : "GUI test Window" title : "GUI test Window"
color : "transparent" color : "transparent"
x : testgui.winMain.x - 120 x : testgui.winMain.x - 170
y : testgui.winMain.y y : testgui.winMain.y
property bool newVersion : true property bool newVersion : true
@ -110,8 +110,8 @@ Window {
ListElement { title: "NotifyManualUpdateRestart" } ListElement { title: "NotifyManualUpdateRestart" }
ListElement { title: "NotifyManualUpdateError" } ListElement { title: "NotifyManualUpdateError" }
ListElement { title: "ForceUpdate" } ListElement { title: "ForceUpdate" }
ListElement { title: "NotifySilentUpdateRestartNeeded" } //ListElement { title: "NotifySilentUpdateRestartNeeded" }
ListElement { title: "NotifySilentUpdateError" } //ListElement { title: "NotifySilentUpdateError" }
ListElement { title : "ImportStructure" } ListElement { title : "ImportStructure" }
ListElement { title : "DraftImpFailed" } ListElement { title : "DraftImpFailed" }
ListElement { title : "NoInterImp" } ListElement { title : "NoInterImp" }
@ -183,12 +183,12 @@ Window {
case "ForceUpdate" : case "ForceUpdate" :
go.notifyForceUpdate() go.notifyForceUpdate()
break; break;
case "NotifySilentUpdateRestartNeeded" : //case "NotifySilentUpdateRestartNeeded" :
go.notifySilentUpdateRestartNeeded() //go.notifySilentUpdateRestartNeeded()
break; //break;
case "NotifySilentUpdateError" : //case "NotifySilentUpdateError" :
go.notifySilentUpdateError() //go.notifySilentUpdateError()
break; //break;
case "ImportStructure" : case "ImportStructure" :
testgui.winMain.dialogImport.address = "cuto@pm.com" testgui.winMain.dialogImport.address = "cuto@pm.com"
testgui.winMain.dialogImport.show() testgui.winMain.dialogImport.show()
@ -836,7 +836,7 @@ Window {
id: go id: go
property int isAutoStart : 1 property int isAutoStart : 1
property bool isAutoUpdate : false //property bool isAutoUpdate : false
property bool isFirstStart : false property bool isFirstStart : false
property string currentAddress : "none" property string currentAddress : "none"
//property string goos : "windows" //property string goos : "windows"
@ -856,16 +856,17 @@ Window {
property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00" property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00"
property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;" property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;"
property string updateState
property string updateVersion : "q0.1.0" 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 updateLandingPage : "https://protonmail.com/import-export/download/"
property string updateReleaseNotesLink : "https://protonmail.com/download/ie/release_notes.html" property string updateReleaseNotesLink : "https://protonmail.com/download/ie/release_notes.html"
signal notifyManualUpdate() signal notifyManualUpdate()
signal notifyManualUpdateRestartNeeded() signal notifyManualUpdateRestartNeeded()
signal notifyManualUpdateError() signal notifyManualUpdateError()
signal notifyForceUpdate() signal notifyForceUpdate()
signal notifySilentUpdateRestartNeeded() //signal notifySilentUpdateRestartNeeded()
signal notifySilentUpdateError() //signal notifySilentUpdateError()
function checkForUpdates() { function checkForUpdates() {
console.log("checkForUpdates") console.log("checkForUpdates")
go.notifyVersionIsTheLatest() go.notifyVersionIsTheLatest()
@ -900,7 +901,6 @@ Window {
signal showQuit() signal showQuit()
signal notifyVersionIsTheLatest() signal notifyVersionIsTheLatest()
signal setUpdateState(string updateState)
signal showMainWin() signal showMainWin()
signal hideMainWin() signal hideMainWin()
@ -1355,10 +1355,10 @@ Window {
return !fname.includes("fail") return !fname.includes("fail")
} }
onToggleAutoUpdate: { //onToggleAutoUpdate: {
workAndClose() // workAndClose()
isAutoUpdate = (isAutoUpdate!=false) ? false : true // isAutoUpdate = (isAutoUpdate!=false) ? false : true
console.log (" Test: onToggleAutoUpdate "+isAutoUpdate) // console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
} //}
} }
} }

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtcommon package qtcommon

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtcommon package qtcommon

View File

@ -1,4 +1,4 @@
// +build !nogui // +build build_qt
#include "common.h" #include "common.h"
#include "_cgo_export.h" #include "_cgo_export.h"

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtcommon package qtcommon

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtcommon package qtcommon

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie
@ -29,7 +29,7 @@ const (
TypeMBOX = "MBOX" TypeMBOX = "MBOX"
) )
func (f *FrontendQt) LoadStructureForExport(addressOrID string) { func (f *FrontendQt) LoadStructureForExport(username, addressOrID string) {
errCode := errUnknownError errCode := errUnknownError
var err error var err error
defer func() { 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. // The only error can be problem to load PM user and address.
errCode = errPMLoadFailed errCode = errPMLoadFailed
return return

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie
@ -135,11 +135,11 @@ func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
} }
func (f *FrontendQt) NotifySilentUpdateInstalled() { func (f *FrontendQt) NotifySilentUpdateInstalled() {
f.Qml.NotifySilentUpdateRestartNeeded() //f.Qml.NotifySilentUpdateRestartNeeded()
} }
func (f *FrontendQt) NotifySilentUpdateError(err error) { func (f *FrontendQt) NotifySilentUpdateError(err error) {
f.Qml.NotifySilentUpdateError() //f.Qml.NotifySilentUpdateError()
} }
func (f *FrontendQt) watchEvents() { func (f *FrontendQt) watchEvents() {
@ -245,11 +245,11 @@ func (f *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
f.Qml.SetCredits(importexport.Credits) f.Qml.SetCredits(importexport.Credits)
f.Qml.SetFullversion(f.buildVersion) f.Qml.SetFullversion(f.buildVersion)
if f.settings.GetBool(settings.AutoUpdateKey) { //if f.settings.GetBool(settings.AutoUpdateKey) {
f.Qml.SetIsAutoUpdate(true) // f.Qml.SetIsAutoUpdate(true)
} else { //} else {
f.Qml.SetIsAutoUpdate(false) // f.Qml.SetIsAutoUpdate(false)
} //}
go func() { go func() {
defer f.panicHandler.HandlePanic() defer f.panicHandler.HandlePanic()
@ -339,17 +339,17 @@ func (f *FrontendQt) sendBug(description, emailClient, address string) bool {
return true return true
} }
func (f *FrontendQt) toggleAutoUpdate() { //func (f *FrontendQt) toggleAutoUpdate() {
defer f.Qml.ProcessFinished() // defer f.Qml.ProcessFinished()
//
if f.settings.GetBool(settings.AutoUpdateKey) { // if f.settings.GetBool(settings.AutoUpdateKey) {
f.settings.SetBool(settings.AutoUpdateKey, false) // f.settings.SetBool(settings.AutoUpdateKey, false)
f.Qml.SetIsAutoUpdate(false) // f.Qml.SetIsAutoUpdate(false)
} else { // } else {
f.settings.SetBool(settings.AutoUpdateKey, true) // f.settings.SetBool(settings.AutoUpdateKey, true)
f.Qml.SetIsAutoUpdate(true) // f.Qml.SetIsAutoUpdate(true)
} // }
} //}
// checkInternet is almost idetical to bridge // checkInternet is almost idetical to bridge
func (f *FrontendQt) checkInternet() { func (f *FrontendQt) checkInternet() {

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build nogui // +build !build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie
@ -26,7 +26,7 @@ import (
) )
// wrapper for QML // 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 errCode := errUnknownError
var err error var err error
defer func() { defer func() {
@ -39,7 +39,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
}() }()
if isFromIMAP { 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 { if err != nil {
switch { switch {
case errors.Is(err, &transfer.ErrIMAPConnection{}): case errors.Is(err, &transfer.ErrIMAPConnection{}):
@ -54,7 +54,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
return return
} }
} else { } else {
f.transfer, err = f.ie.GetLocalImporter(targetAddress, sourcePath) f.transfer, err = f.ie.GetLocalImporter(targetUsername, targetAddress, sourcePath)
if err != nil { if err != nil {
// The only error can be problem to load PM user and address. // The only error can be problem to load PM user and address.
errCode = errPMLoadFailed errCode = errPMLoadFailed

View File

@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qtie package qtie
@ -33,7 +33,7 @@ type GoQMLInterface struct {
_ func() `constructor:"init"` _ func() `constructor:"init"`
_ bool `property:"isAutoUpdate"` //_ bool `property:"isAutoUpdate"`
_ string `property:"currentAddress"` _ string `property:"currentAddress"`
_ string `property:"goos"` _ string `property:"goos"`
_ string `property:"credits"` _ string `property:"credits"`
@ -53,6 +53,7 @@ type GoQMLInterface struct {
_ string `property:"fullversion"` _ string `property:"fullversion"`
_ string `property:"downloadLink"` _ string `property:"downloadLink"`
_ string `property:"updateState"`
_ string `property:"updateVersion"` _ string `property:"updateVersion"`
_ bool `property:"updateCanInstall"` _ bool `property:"updateCanInstall"`
_ string `property:"updateLandingPage"` _ string `property:"updateLandingPage"`
@ -61,8 +62,8 @@ type GoQMLInterface struct {
_ func() `signal:"notifyManualUpdateRestartNeeded"` _ func() `signal:"notifyManualUpdateRestartNeeded"`
_ func() `signal:"notifyManualUpdateError"` _ func() `signal:"notifyManualUpdateError"`
_ func() `signal:"notifyForceUpdate"` _ func() `signal:"notifyForceUpdate"`
_ func() `signal:"notifySilentUpdateRestartNeeded"` //_ func() `signal:"notifySilentUpdateRestartNeeded"`
_ func() `signal:"notifySilentUpdateError"` //_ func() `signal:"notifySilentUpdateError"`
_ func() `slot:"checkForUpdates"` _ func() `slot:"checkForUpdates"`
_ func() `slot:"checkAndOpenReleaseNotes"` _ func() `slot:"checkAndOpenReleaseNotes"`
_ func() `signal:"openReleaseNotesExternally"` _ func() `signal:"openReleaseNotesExternally"`
@ -76,9 +77,8 @@ type GoQMLInterface struct {
_ string `property:"credentialsNotRemoved"` _ string `property:"credentialsNotRemoved"`
_ string `property:"versionCheckFailed"` _ string `property:"versionCheckFailed"`
// //
_ func(isAvailable bool) `signal:"setConnectionStatus"` _ func(isAvailable bool) `signal:"setConnectionStatus"`
_ func(updateState string) `signal:"setUpdateState"` _ func() `slot:"checkInternet"`
_ func() `slot:"checkInternet"`
_ func() `slot:"setToRestart"` _ func() `slot:"setToRestart"`
@ -93,7 +93,7 @@ type GoQMLInterface struct {
_ func() `signal:"showWindow"` _ func() `signal:"showWindow"`
_ func() `slot:"toggleAutoUpdate"` //_ func() `slot:"toggleAutoUpdate"`
_ func() `slot:"quit"` _ func() `slot:"quit"`
_ func() `slot:"loadAccounts"` _ func() `slot:"loadAccounts"`
_ func() `slot:"openLogs"` _ func() `slot:"openLogs"`
@ -108,14 +108,14 @@ type GoQMLInterface struct {
_ func(description, client, address string) bool `slot:"sendBug"` _ func(description, client, address string) bool `slot:"sendBug"`
_ func(address string) bool `slot:"sendImportReport"` _ func(address string) bool `slot:"sendImportReport"`
_ func(address string) `slot:"loadStructureForExport"` _ func(username, address string) `slot:"loadStructureForExport"`
_ func() string `slot:"leastUsedColor"` _ func() string `slot:"leastUsedColor"`
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"` _ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"` _ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
_ func(email string, importEncrypted bool) `slot:"startImport"` _ func(email string, importEncrypted bool) `slot:"startImport"`
_ func() `slot:"resetSource"` _ 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"` _ string `property:"progressInit"`
@ -162,7 +162,7 @@ func (s *GoQMLInterface) init() {}
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) { func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
s.ConnectQuit(f.App.Quit) s.ConnectQuit(f.App.Quit)
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate) //s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
s.ConnectLoadAccounts(f.Accounts.LoadAccounts) s.ConnectLoadAccounts(f.Accounts.LoadAccounts)
s.ConnectOpenLogs(f.openLogs) s.ConnectOpenLogs(f.openLogs)
s.ConnectOpenDownloadLink(f.openDownloadLink) s.ConnectOpenDownloadLink(f.openDownloadLink)
@ -207,4 +207,6 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
s.ConnectCheckPathStatus(CheckPathStatus) s.ConnectCheckPathStatus(CheckPathStatus)
s.ConnectEmitEvent(f.emitEvent) s.ConnectEmitEvent(f.emitEvent)
s.ConnectStartManualUpdate(f.startManualUpdate)
} }

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt
@ -84,7 +84,7 @@ func (s *FrontendQt) clearCache() {
channel := s.bridge.GetUpdateChannel() channel := s.bridge.GetUpdateChannel()
if channel == updater.EarlyChannel { if channel == updater.EarlyChannel {
if err := s.bridge.SetUpdateChannel(updater.StableChannel); err != nil { if _, err := s.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
s.Qml.NotifyManualUpdateError() s.Qml.NotifyManualUpdateError()
return return
} }

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
// Package qt is the Qt User interface for Desktop bridge. // Package qt is the Qt User interface for Desktop bridge.
// //
@ -340,27 +340,35 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
s.Qml.SetCredits(bridge.Credits) s.Qml.SetCredits(bridge.Credits)
s.Qml.SetFullversion(s.buildVersion) s.Qml.SetFullversion(s.buildVersion)
// Autostart. // Autostart: rewrite the current definition of autostart
if s.Qml.IsFirstStart() { // - when it is the first time
if s.autostart.IsEnabled() { // - when starting after clear cache
// - when there is already autostart file from past
//
// This will make sure that autostart will use the latest path to
// launcher or bridge.
isAutoStartEnabled := s.autostart.IsEnabled()
if s.Qml.IsFirstStart() || isAutoStartEnabled {
if isAutoStartEnabled {
if err := s.autostart.Disable(); err != nil { if err := s.autostart.Disable(); err != nil {
log.Error("First disable ", err) log.
WithField("first", s.Qml.IsFirstStart()).
WithField("wasEnabled", isAutoStartEnabled).
WithError(err).
Error("Disable on start failed.")
s.autostartError(err) s.autostartError(err)
} }
} }
s.toggleAutoStart() if err := s.autostart.Enable(); err != nil {
} log.
if s.autostart.IsEnabled() { WithField("first", s.Qml.IsFirstStart()).
s.Qml.SetIsAutoStart(true) WithField("wasEnabled", isAutoStartEnabled).
} else { WithError(err).
s.Qml.SetIsAutoStart(false) Error("Enable on start failed.")
} s.autostartError(err)
}
if s.settings.GetBool(settings.AutoUpdateKey) {
s.Qml.SetIsAutoUpdate(true)
} else {
s.Qml.SetIsAutoUpdate(false)
} }
s.Qml.SetIsAutoStart(s.autostart.IsEnabled())
if s.settings.GetBool(settings.AllowProxyKey) { if s.settings.GetBool(settings.AllowProxyKey) {
s.Qml.SetIsProxyAllowed(true) s.Qml.SetIsProxyAllowed(true)
@ -557,20 +565,22 @@ func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {
func (s *FrontendQt) toggleAutoStart() { func (s *FrontendQt) toggleAutoStart() {
defer s.Qml.ProcessFinished() defer s.Qml.ProcessFinished()
var err error var err error
if s.autostart.IsEnabled() { wasEnabled := s.autostart.IsEnabled()
if wasEnabled {
err = s.autostart.Disable() err = s.autostart.Disable()
} else { } else {
err = s.autostart.Enable() err = s.autostart.Enable()
} }
isEnabled := s.autostart.IsEnabled()
if err != nil { if err != nil {
log.Error("Enable autostart: ", err) log.
WithField("wasEnabled", wasEnabled).
WithField("isEnabled", isEnabled).
WithError(err).
Error("Autostart change failed.")
s.autostartError(err) s.autostartError(err)
} }
if s.autostart.IsEnabled() { s.Qml.SetIsAutoStart(isEnabled)
s.Qml.SetIsAutoStart(true)
} else {
s.Qml.SetIsAutoStart(false)
}
} }
func (s *FrontendQt) toggleAutoUpdate() { func (s *FrontendQt) toggleAutoUpdate() {
@ -595,14 +605,16 @@ func (s *FrontendQt) toggleEarlyAccess() {
channel = updater.EarlyChannel channel = updater.EarlyChannel
} }
err := s.bridge.SetUpdateChannel(channel) needRestart, err := s.bridge.SetUpdateChannel(channel)
s.Qml.SetIsEarlyAccess(channel == updater.EarlyChannel) s.Qml.SetIsEarlyAccess(channel == updater.EarlyChannel)
if err != nil { if err != nil {
s.Qml.NotifyManualUpdateError() s.Qml.NotifyManualUpdateError()
return return
} }
s.restarter.SetToRestart() if needRestart {
s.App.Quit() s.restarter.SetToRestart()
s.App.Quit()
}
} }
func (s *FrontendQt) toggleAllowProxy() { func (s *FrontendQt) toggleAllowProxy() {
@ -733,4 +745,4 @@ func (s *FrontendQt) setKeychain(keychain string) {
s.restarter.SetToRestart() s.restarter.SetToRestart()
s.App.Quit() s.App.Quit()
} }
} }

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build nogui // +build !build_qt
package qt package qt

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt

View File

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build !nogui // +build build_qt
package qt package qt
@ -50,6 +50,7 @@ type GoQMLInterface struct {
_ string `property:"fullversion"` _ string `property:"fullversion"`
_ string `property:"downloadLink"` _ string `property:"downloadLink"`
_ string `property:"updateState"`
_ string `property:"updateVersion"` _ string `property:"updateVersion"`
_ bool `property:"updateCanInstall"` _ bool `property:"updateCanInstall"`
_ string `property:"updateLandingPage"` _ string `property:"updateLandingPage"`
@ -82,9 +83,8 @@ type GoQMLInterface struct {
_ float32 `property:"progress"` _ float32 `property:"progress"`
_ string `property:"progressDescription"` _ string `property:"progressDescription"`
_ func(isAvailable bool) `signal:"setConnectionStatus"` _ func(isAvailable bool) `signal:"setConnectionStatus"`
_ func(updateState string) `signal:"setUpdateState"` _ func() `slot:"checkInternet"`
_ func() `slot:"checkInternet"`
_ func() `slot:"setToRestart"` _ func() `slot:"setToRestart"`

View File

@ -1 +0,0 @@
IDI_ICON1 ICON DISCARDABLE "logo.ico"

View 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

View File

@ -79,7 +79,7 @@ type Bridger interface {
AllowProxy() AllowProxy()
DisallowProxy() DisallowProxy()
GetUpdateChannel() updater.UpdateChannel GetUpdateChannel() updater.UpdateChannel
SetUpdateChannel(updater.UpdateChannel) error SetUpdateChannel(updater.UpdateChannel) (needRestart bool, err error)
GetKeychainApp() string GetKeychainApp() string
SetKeychainApp(keychain string) SetKeychainApp(keychain string)
} }
@ -114,10 +114,10 @@ func (b *bridgeWrap) GetUser(query string) (User, error) {
type ImportExporter interface { type ImportExporter interface {
UserManager UserManager
GetLocalImporter(string, string) (*transfer.Transfer, error) GetLocalImporter(string, string, string) (*transfer.Transfer, error)
GetRemoteImporter(string, string, string, string, string) (*transfer.Transfer, error) GetRemoteImporter(string, string, string, string, string, string) (*transfer.Transfer, error)
GetEMLExporter(string, string) (*transfer.Transfer, error) GetEMLExporter(string, string, string) (*transfer.Transfer, error)
GetMBOXExporter(string, string) (*transfer.Transfer, error) GetMBOXExporter(string, string, string) (*transfer.Transfer, error)
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
ReportFile(osType, osVersion, accountName, address string, logdata []byte) error ReportFile(osType, osVersion, accountName, address string, logdata []byte) error
} }

View File

@ -181,7 +181,7 @@ func (im *imapMailbox) createMessage(flags []string, date time.Time, body imap.L
return err return err
} }
targetSeq := im.storeMailbox.GetUIDList([]string{m.ID}) targetSeq := im.storeMailbox.GetUIDList(IDs)
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq) 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) 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") im.log.WithField("msgID", storeMessage.ID()).Trace("Getting message")
seqNum, err := storeMessage.SequenceNumber() 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. // on our part and we need to compute "real" size of decrypted data.
if m.Size <= 0 { if m.Size <= 0 {
im.log.WithField("msgID", storeMessage.ID()).Trace("Size unknown - downloading body") 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 return
} }
} }
@ -279,7 +284,7 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
return nil, err return nil, err
} }
default: default:
if err = im.getLiteralForSection(item, msg, storeMessage); err != nil { if err = im.getLiteralForSection(item, msg, storeMessage, msgBuildCountHistogram); err != nil {
return return
} }
} }
@ -288,14 +293,14 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
return msg, err 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) section, err := imap.ParseBodySectionName(itemSection)
if err != nil { // Ignore error if err != nil { // Ignore error
return nil return nil
} }
var literal imap.Literal 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 return err
} }
@ -313,14 +318,19 @@ func (im *imapMailbox) getBodyStructure(storeMessage storeMessageProvider) (bs *
im.log.WithError(err).Debug("Fail to retrieve bodystructure from database") im.log.WithError(err).Debug("Fail to retrieve bodystructure from database")
} }
if bs == nil { 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
} }
} }
return return
} }
func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) ( func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider, msgBuildCountHistogram *msgBuildCountHistogram) (
structure *message.BodyStructure, structure *message.BodyStructure,
bodyReader *bytes.Reader, err error, bodyReader *bytes.Reader, err error,
) { ) {
@ -352,6 +362,15 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
WithField("msgID", m.ID). WithField("msgID", m.ID).
Warn("Cannot update header while building") 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. // Drafts can change and we don't want to cache them.
if !isMessageInDraftFolder(m) { if !isMessageInDraftFolder(m) {
cache.SaveMail(id, body, structure) 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, // This will download message (or read from cache) and pick up the section,
// extract data (header,body, both) and trim the output if needed. // 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 ( var (
structure *message.BodyStructure structure *message.BodyStructure
bodyReader *bytes.Reader bodyReader *bytes.Reader
@ -410,7 +429,7 @@ func (im *imapMailbox) getMessageBodySection(storeMessage storeMessageProvider,
} }
} else { } else {
// The rest of cases need download and decrypt. // The rest of cases need download and decrypt.
structure, bodyReader, err = im.getBodyAndStructure(storeMessage) structure, bodyReader, err = im.getBodyAndStructure(storeMessage, msgBuildCountHistogram)
if err != nil { if err != nil {
return return
} }
@ -564,7 +583,7 @@ func (im *imapMailbox) writeRelatedPart(p io.Writer, m *pmapi.Message, inlines [
return return
} }
h := message.GetAttachmentHeader(inline) h := message.GetAttachmentHeader(inline, true)
if p, err = related.CreatePart(h); err != nil { if p, err = related.CreatePart(h); err != nil {
return return
} }
@ -738,7 +757,7 @@ func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (
defer buf.Reset() defer buf.Reset()
att := atts[idx] att := atts[idx]
attachmentHeader := message.GetAttachmentHeader(att) attachmentHeader := message.GetAttachmentHeader(att, true)
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil { if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
return err return err
} }

View File

@ -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. // 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 { 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.logCommand(func() error {
return im.listMessages(isUID, seqSet, items, msgResponse) return im.listMessages(isUID, seqSet, items, msgResponse, msgBuildCountHistogram)
}, "FETCH", isUID, seqSet, items) }, "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() { defer func() {
close(msgResponse) close(msgResponse)
if err != nil { if err != nil {
@ -544,7 +545,7 @@ func (im *imapMailbox) listMessages(isUID bool, seqSet *imap.SeqSet, items []ima
return nil, err return nil, err
} }
msg, err := im.getMessage(storeMessage, items) msg, err := im.getMessage(storeMessage, items, msgBuildCountHistogram)
if err != nil { if err != nil {
err = fmt.Errorf("list message build: %v", err) err = fmt.Errorf("list message build: %v", err)
l.WithField("metaID", storeMessage.ID()).Error(err) l.WithField("metaID", storeMessage.ID()).Error(err)

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"
"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]++
}

View File

@ -103,6 +103,7 @@ type storeMessageProvider interface {
SetContentTypeAndHeader(string, mail.Header) error SetContentTypeAndHeader(string, mail.Header) error
SetBodyStructure(*pkgMsg.BodyStructure) error SetBodyStructure(*pkgMsg.BodyStructure) error
GetBodyStructure() (*pkgMsg.BodyStructure, error) GetBodyStructure() (*pkgMsg.BodyStructure, error)
IncreaseBuildCount() (uint32, error)
} }
type storeUserWrap struct { type storeUserWrap struct {

View File

@ -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. // 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) source := transfer.NewLocalProvider(path)
target, err := ie.getPMAPIProvider(address) target, err := ie.getPMAPIProvider(username, address)
if err != nil { if err != nil {
return nil, err 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. // GetRemoteImporter returns transferrer from remote IMAP to ProtonMail account.
func (ie *ImportExport) GetRemoteImporter(address, username, password, host, port string) (*transfer.Transfer, error) { func (ie *ImportExport) GetRemoteImporter(username, address, remoteUsername, remotePassword, host, port string) (*transfer.Transfer, error) {
source, err := transfer.NewIMAPProvider(username, password, host, port) source, err := transfer.NewIMAPProvider(remoteUsername, remotePassword, host, port)
if err != nil { if err != nil {
return nil, err return nil, err
} }
target, err := ie.getPMAPIProvider(address) target, err := ie.getPMAPIProvider(username, address)
if err != nil { if err != nil {
return nil, err 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. // GetEMLExporter returns transferrer from ProtonMail account to local EML structure.
func (ie *ImportExport) GetEMLExporter(address, path string) (*transfer.Transfer, error) { func (ie *ImportExport) GetEMLExporter(username, address, path string) (*transfer.Transfer, error) {
source, err := ie.getPMAPIProvider(address) source, err := ie.getPMAPIProvider(username, address)
if err != nil { if err != nil {
return nil, err 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. // GetMBOXExporter returns transferrer from ProtonMail account to local MBOX structure.
func (ie *ImportExport) GetMBOXExporter(address, path string) (*transfer.Transfer, error) { func (ie *ImportExport) GetMBOXExporter(username, address, path string) (*transfer.Transfer, error) {
source, err := ie.getPMAPIProvider(address) source, err := ie.getPMAPIProvider(username, address)
if err != nil { if err != nil {
return nil, err 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) return transfer.New(ie.panicHandler, newExportMetricsManager(ie), logsPath, ie.cache.GetTransferDir(), source, target)
} }
func (ie *ImportExport) getPMAPIProvider(address string) (*transfer.PMAPIProvider, error) { func (ie *ImportExport) getPMAPIProvider(username, address string) (*transfer.PMAPIProvider, error) {
user, err := ie.Users.GetUser(address) user, err := ie.Users.GetUser(username)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -32,8 +32,8 @@ import (
// On linux: // On linux:
// - settings: ~/.config/protonmail/<app> // - settings: ~/.config/protonmail/<app>
// - logs: ~/.cache/protonmail/<app>/logs // - logs: ~/.cache/protonmail/<app>/logs
// - cache: ~/.cache/protonmail/<app>/cache // - cache: ~/.config/protonmail/<app>/cache
// - updates: ~/.cache/protonmail/<app>/updates // - updates: ~/.config/protonmail/<app>/updates
// - lockfile: ~/.cache/protonmail/<app>/<app>.lock // - lockfile: ~/.cache/protonmail/<app>/<app>.lock
type Locations struct { type Locations struct {
userConfig, userCache string userConfig, userCache string
@ -129,7 +129,7 @@ func (l *Locations) ProvideLogsPath() (string, error) {
return l.getLogsPath(), nil 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. // It creates it if it doesn't already exist.
func (l *Locations) ProvideCachePath() (string, error) { func (l *Locations) ProvideCachePath() (string, error) {
if err := os.MkdirAll(l.getCachePath(), 0700); err != nil { if err := os.MkdirAll(l.getCachePath(), 0700); err != nil {
@ -139,6 +139,11 @@ func (l *Locations) ProvideCachePath() (string, error) {
return l.getCachePath(), nil 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). // ProvideUpdatesPath returns a location for update files (e.g. ~/.cache/<company>/<app>/updates).
// It creates it if it doesn't already exist. // It creates it if it doesn't already exist.
func (l *Locations) ProvideUpdatesPath() (string, error) { func (l *Locations) ProvideUpdatesPath() (string, error) {
@ -149,6 +154,16 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
return l.getUpdatesPath(), nil 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 { func (l *Locations) getSettingsPath() string {
return l.userConfig return l.userConfig
} }
@ -158,11 +173,33 @@ func (l *Locations) getLogsPath() string {
} }
func (l *Locations) getCachePath() 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 { 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. // Clear removes everything except the lock and update files.

View File

@ -45,7 +45,8 @@ func TestClearRemovesEverythingExceptLockAndUpdateFiles(t *testing.T) {
assert.NoError(t, l.Clear()) assert.NoError(t, l.Clear())
assert.FileExists(t, l.GetLockFile()) 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.getLogsPath())
assert.NoDirExists(t, l.getCachePath()) assert.NoDirExists(t, l.getCachePath())
assert.DirExists(t, l.getUpdatesPath()) assert.DirExists(t, l.getUpdatesPath())
@ -58,6 +59,7 @@ func TestClearUpdateFiles(t *testing.T) {
assert.FileExists(t, l.GetLockFile()) assert.FileExists(t, l.GetLockFile())
assert.DirExists(t, l.getSettingsPath()) assert.DirExists(t, l.getSettingsPath())
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
assert.DirExists(t, l.getLogsPath()) assert.DirExists(t, l.getLogsPath())
assert.DirExists(t, l.getCachePath()) assert.DirExists(t, l.getCachePath())
assert.NoDirExists(t, l.getUpdatesPath()) assert.NoDirExists(t, l.getUpdatesPath())
@ -75,6 +77,7 @@ func TestCleanLeavesStandardLocationsUntouched(t *testing.T) {
assert.FileExists(t, l.GetLockFile()) assert.FileExists(t, l.GetLockFile())
assert.DirExists(t, l.getSettingsPath()) assert.DirExists(t, l.getSettingsPath())
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
assert.DirExists(t, l.getLogsPath()) assert.DirExists(t, l.getLogsPath())
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt")) assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt"))
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log2.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.NoError(t, err)
require.DirExists(t, settings) require.DirExists(t, settings)
createFilesInDir(t, settings, "prefs.json")
require.FileExists(t, filepath.Join(settings, "prefs.json"))
logs, err := l.ProvideLogsPath() logs, err := l.ProvideLogsPath()
require.NoError(t, err) require.NoError(t, err)
require.DirExists(t, logs) require.DirExists(t, logs)

View File

@ -0,0 +1,22 @@
// 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/>.
// +build !build_qa
package smtp
func dumpMessageData([]byte, string) {}

60
internal/smtp/dump_qa.go Normal file
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/>.
// +build build_qa
package smtp
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/sirupsen/logrus"
)
func dumpMessageData(b []byte, subject string) {
home, err := os.UserHomeDir()
if err != nil {
logrus.WithError(err).Error("Failed to dump raw message data")
return
}
path := filepath.Join(home, "bridge-qa")
if err := os.MkdirAll(path, 0700); err != nil {
logrus.WithError(err).Error("Failed to dump raw message data")
return
}
if len(subject) > 16 {
subject = subject[:16]
}
if err := ioutil.WriteFile(
filepath.Join(path, fmt.Sprintf("%v-%v.eml", subject, time.Now().Format(time.RFC3339Nano))),
b,
0600,
); err != nil {
logrus.WithError(err).Error("Failed to dump raw message data")
return
}
logrus.WithField("path", path).Info("Dumped raw message data")
}

View File

@ -42,6 +42,7 @@ func NewSMTPServer(debug bool, port int, useSSL bool, tls *tls.Config, smtpBacke
s.TLSConfig = tls s.TLSConfig = tls
s.Domain = bridge.Host s.Domain = bridge.Host
s.AllowInsecureAuth = true s.AllowInsecureAuth = true
s.MaxLineLength = 2 << 16
if debug { if debug {
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")

View File

@ -215,6 +215,10 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
// Called from go-smtp in goroutines - we need to handle panics for each function. // Called from go-smtp in goroutines - we need to handle panics for each function.
defer su.panicHandler.HandlePanic() defer su.panicHandler.HandlePanic()
b := new(bytes.Buffer)
messageReader = io.TeeReader(messageReader, b)
mailSettings, err := su.client().GetMailSettings() mailSettings, err := su.client().GetMailSettings()
if err != nil { if err != nil {
return err return err
@ -405,6 +409,8 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
req.PreparePackages() req.PreparePackages()
dumpMessageData(b.Bytes(), message.Subject)
return su.storeUser.SendMessage(message.ID, req) return su.storeUser.SendMessage(message.ID, req)
} }

View File

@ -268,8 +268,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
return false, errors.New("received empty event") return false, errors.New("received empty event")
} }
l = l.WithField("newEventID", event.EventID)
if err = loop.processEvent(event); err != nil { if err = loop.processEvent(event); err != nil {
return false, errors.Wrap(err, "failed to process event") return false, errors.Wrap(err, "failed to process event")
} }

View File

@ -67,10 +67,14 @@ func (storeMailbox *Mailbox) ImportMessage(msg *pmapi.Message, body []byte, labe
} }
res, err := storeMailbox.client().Import([]*pmapi.ImportMsgReq{importReqs}) res, err := storeMailbox.client().Import([]*pmapi.ImportMsgReq{importReqs})
if err == nil && len(res) > 0 { if err != nil {
msg.ID = res[0].MessageID 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. // LabelMessages adds the label by calling an API.

View File

@ -148,3 +148,17 @@ func (message *Message) GetBodyStructure() (bs *pkgMsg.BodyStructure, err error)
} }
return bs, nil 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
}

View File

@ -54,6 +54,8 @@ var (
// * {messageID} -> message data (subject, from, to, time, headers, body size, ...) // * {messageID} -> message data (subject, from, to, time, headers, body size, ...)
// * bodystructure // * bodystructure
// * {messageID} -> message body structure // * {messageID} -> message body structure
// * msgbuildcount
// * {messageID} -> uint32 number of message builds to track re-sync issues
// * counts // * counts
// * {mailboxID} -> mailboxCounts: totalOnAPI, unreadOnAPI, labelName, labelColor, labelIsExclusive // * {mailboxID} -> mailboxCounts: totalOnAPI, unreadOnAPI, labelName, labelColor, labelIsExclusive
// * address_info // * address_info
@ -76,6 +78,7 @@ var (
// * {messageID} -> true // * {messageID} -> true
metadataBucket = []byte("metadata") //nolint[gochecknoglobals] metadataBucket = []byte("metadata") //nolint[gochecknoglobals]
bodystructureBucket = []byte("bodystructure") //nolint[gochecknoglobals] bodystructureBucket = []byte("bodystructure") //nolint[gochecknoglobals]
msgBuildCountBucket = []byte("msgbuildcount") //nolint[gochecknoglobals]
countsBucket = []byte("counts") //nolint[gochecknoglobals] countsBucket = []byte("counts") //nolint[gochecknoglobals]
addressInfoBucket = []byte("address_info") //nolint[gochecknoglobals] addressInfoBucket = []byte("address_info") //nolint[gochecknoglobals]
addressModeBucket = []byte("address_mode") //nolint[gochecknoglobals] addressModeBucket = []byte("address_mode") //nolint[gochecknoglobals]
@ -204,6 +207,10 @@ func openBoltDatabase(filePath string) (db *bolt.DB, err error) {
return return
} }
if _, err = tx.CreateBucketIfNotExists(msgBuildCountBucket); err != nil {
return
}
if _, err = tx.CreateBucketIfNotExists(countsBucket); err != nil { if _, err = tx.CreateBucketIfNotExists(countsBucket); err != nil {
return 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() labels, err := store.initCounts()
if err != nil { if err != nil {

View File

@ -191,6 +191,19 @@ func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*pk
return pkgMsg.DeserializeBodyStructure(raw) 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 // createOrUpdateMessageEvent is helper to create only one message with
// createOrUpdateMessagesEvent. // createOrUpdateMessagesEvent.
func (store *Store) createOrUpdateMessageEvent(msg *pmapi.Message) error { func (store *Store) createOrUpdateMessageEvent(msg *pmapi.Message) error {

View File

@ -57,19 +57,23 @@ func WriteAttachmentBody(w io.Writer, kr *crypto.KeyRing, m *pmapi.Message, att
att.Name += ".gpg" att.Name += ".gpg"
att.MIMEType = "application/pgp-encrypted" //nolint att.MIMEType = "application/pgp-encrypted" //nolint
} else if err != nil && err != openpgperrors.ErrSignatureExpired { } else if err != nil && err != openpgperrors.ErrSignatureExpired {
err = fmt.Errorf("cannot decrypt attachment: %v", err) return fmt.Errorf("cannot decrypt attachment: %v", err)
return }
// 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. // Encode it.
ww := textwrapper.NewRFC822(w) ww := textwrapper.NewRFC822(w)
bw := base64.NewEncoder(base64.StdEncoding, ww) bw := base64.NewEncoder(base64.StdEncoding, ww)
var n int64 if n, err := io.Copy(bw, dr); err != nil {
if n, err = io.Copy(bw, dr); err != nil { return fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
err = fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
} }
return bw.Close()
_ = bw.Close()
return
} }

View File

@ -124,7 +124,7 @@ func (bld *Builder) writeRelatedPart(p io.Writer, inlines []*pmapi.Attachment) e
return err return err
} }
h := GetAttachmentHeader(inline) h := GetAttachmentHeader(inline, false)
if p, err = related.CreatePart(h); err != nil { if p, err = related.CreatePart(h); err != nil {
return err return err
} }
@ -194,7 +194,7 @@ func (bld *Builder) BuildMessage() (structure *BodyStructure, message []byte, er
return nil, nil, err return nil, nil, err
} }
attachmentHeader := GetAttachmentHeader(att) attachmentHeader := GetAttachmentHeader(att, false)
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil { if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
return nil, nil, err 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++ { for i := 0; i < len(m.Attachments); i++ {
att := m.Attachments[i] att := m.Attachments[i]
r := readers[i] r := readers[i]
h := GetAttachmentHeader(att) h := GetAttachmentHeader(att, false)
p, err := mw.CreatePart(h) p, err := mw.CreatePart(h)
if err != nil { if err != nil {
return nil, err 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) data, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
@ -332,6 +327,9 @@ func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
ww := textwrapper.NewRFC822(p)
bw := base64.NewEncoder(base64.StdEncoding, ww)
if _, err := bw.Write(pgpMessage.GetBinary()); err != nil { if _, err := bw.Write(pgpMessage.GetBinary()); err != nil {
return nil, err return nil, err
} }

View File

@ -107,12 +107,17 @@ func GetRelatedHeader(m *pmapi.Message) textproto.MIMEHeader {
return h return h
} }
func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader { func GetAttachmentHeader(att *pmapi.Attachment, buildForIMAP bool) textproto.MIMEHeader {
mediaType := att.MIMEType mediaType := att.MIMEType
if mediaType == "application/pgp-encrypted" { if mediaType == "application/pgp-encrypted" {
mediaType = "application/octet-stream" mediaType = "application/octet-stream"
} }
transferEncoding := "base64"
if mediaType == rfc822Message && buildForIMAP {
transferEncoding = "8bit"
}
encodedName := pmmime.EncodeHeader(att.Name) encodedName := pmmime.EncodeHeader(att.Name)
disposition := "attachment" //nolint[goconst] disposition := "attachment" //nolint[goconst]
if strings.Contains(att.Header.Get("Content-Disposition"), "inline") { 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 := make(textproto.MIMEHeader)
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName})) 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})) h.Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": encodedName}))
// Forward some original header lines. // Forward some original header lines.

View File

@ -26,6 +26,10 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const (
rfc822Message = "message/rfc822"
)
var log = logrus.WithField("pkg", "pkg/message") //nolint[gochecknoglobals] var log = logrus.WithField("pkg", "pkg/message") //nolint[gochecknoglobals]
func GetBoundary(m *pmapi.Message) string { func GetBoundary(m *pmapi.Message) string {

View File

@ -201,7 +201,7 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type")) mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type"))
// If multipart, call getAllParts, else read to count lines. // 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) newPath := append(currentPath, 1)
var br *boundaryReader var br *boundaryReader

View File

@ -1,3 +1,29 @@
## 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
### 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
- NoGUI bulid
- Background of GUI welcome message
- Incorrect total mailbox size displayed in Apple Mail
## v1.6.3 ## v1.6.3
- 2021-02-16 - 2021-02-16

View File

@ -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 ## v1.6.3
- 2021-02-16 - 2021-02-16

View File

@ -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 ## v1.2.2
- 2020-11-27 - 2020-11-27
### New ### New
Improvements to the import from large mbox files with multiple labels - 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
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
Better handling and displaying of skipped messages - Cosmetic GUI changes
- Better error handling
Various enhancements of the import process related to parsing
Cosmetic GUI changes
Better error handling
### Fixed ### Fixed
- Linux font issues - Fedora specific
Linux font issues - Fedora specific - App response to the user pausing and canceling import or export
- Upgrade errors
App response to the user pausing and canceling import or export
Upgrade errors
## v1.1.2 ## v1.1.2
- 2020-09-23 - 2020-09-23
### New ### New
- Improving performance
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
* Speed up import by implementing parallel processing (parallel fetch, encrypt and upload of messages) - Better message parsing
* Optimising the initial fetch of messages from external accounts - Better handling of attachments and non-standard formatting
- Improved stability of the message parser
Better message parsing - Improved metrics
- Added persistent anonymous API cookies
* Better handling of attachments and non-standard formatting
* Improved stability of the message parser
Improved metrics
* Added persistent anonymous API cookies
### Fixed ### Fixed
- Fixed issues causing failing of import
Fixed issues causing failing of import - Import from mbox files with long lines
- Improvements to import from Yahoo accounts
* Import from mbox files with long lines
* Improvements to import from Yahoo accounts

View File

@ -117,3 +117,64 @@ Feature: IMAP import messages
Then IMAP response is "OK" Then IMAP response is "OK"
And API mailbox "INBOX" for "user" has 0 message And API mailbox "INBOX" for "user" has 0 message
And API mailbox "Sent" for "user" has 1 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"

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

View File

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

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

View File

@ -35,6 +35,16 @@ Feature: Import to sent
| foo@example.com | bridgetest@pm.test | one | | foo@example.com | bridgetest@pm.test | one |
| bar@example.com | bridgetest@pm.test | two | | 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 Scenario: Import to sent and custom label
And there is EML file "Label/one.eml" And there is EML file "Label/one.eml"
""" """

View File

@ -18,6 +18,8 @@
package tests package tests
import ( import (
"time"
"github.com/cucumber/godog" "github.com/cucumber/godog"
) )
@ -44,6 +46,16 @@ func imapClientRenamesMailboxTo(mailboxName, newMailboxName string) error {
} }
func imapClientDeletesMailbox(mailboxName string) error { func imapClientDeletesMailbox(mailboxName string) error {
if mailboxName == "Trash" {
// Delete of Trash mailbox calls empty label on API.
// Empty label means delete all messages in that label with time
// creation before time of execution. But creation time is in
// seconds, not miliseconds. That's why message created at the
// same second as emptying label is called is not deleted.
// Tests might be that fast and therefore we need to sleep for
// a second to make sure test doesn't produce fake failure.
time.Sleep(time.Second)
}
res := ctx.GetIMAPClient("imap").DeleteMailbox(mailboxName) res := ctx.GetIMAPClient("imap").DeleteMailbox(mailboxName)
ctx.SetIMAPLastResponse("imap", res) ctx.SetIMAPLastResponse("imap", res)
return nil return nil

View File

@ -19,6 +19,7 @@ package tests
import ( import (
"strconv" "strconv"
"strings"
"time" "time"
"github.com/cucumber/godog" "github.com/cucumber/godog"
@ -47,9 +48,12 @@ func imapResponseIs(expectedResponse string) error {
func imapResponseNamedIs(clientID, expectedResponse string) error { func imapResponseNamedIs(clientID, expectedResponse string) error {
res := ctx.GetIMAPLastResponse(clientID) res := ctx.GetIMAPLastResponse(clientID)
if expectedResponse == "OK" { switch {
case expectedResponse == "OK":
res.AssertOK() res.AssertOK()
} else { case strings.HasPrefix(expectedResponse, "OK"):
res.AssertResult(expectedResponse)
default:
res.AssertError(expectedResponse) res.AssertError(expectedResponse)
} }
return ctx.GetTestingError() return ctx.GetTestingError()

View File

@ -107,6 +107,13 @@ func (ir *IMAPResponse) AssertOK() *IMAPResponse {
return ir 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 { func (ir *IMAPResponse) AssertError(wantErrMsg string) *IMAPResponse {
ir.wait() ir.wait()
if ir.err == nil { if ir.err == nil {

View File

@ -60,9 +60,9 @@ func userImportsLocalFilesToAddress(bddUserID, bddAddressID string) error {
} }
func userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) 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() 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 { 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() 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 { 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() 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 { 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() path := ctx.GetTransferLocalRootForExport()
return ctx.GetImportExport().GetMBOXExporter(address, path) return ctx.GetImportExport().GetMBOXExporter(username, address, path)
}) })
} }
// Helpers. // 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) account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
if account == nil { if account == nil {
return godog.ErrPending return godog.ErrPending
} }
transferrer, err := getTransferrer(account.Address()) transferrer, err := getTransferrer(account.Username(), account.Address())
if err != nil { if err != nil {
return internalError(err, "failed to init transfer") return internalError(err, "failed to init transfer")
} }

View File

@ -17,10 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. # along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
# The Qt libs are dynamically loaded with rules like: `@rpath/QtGui.framework/Versions/5/QtGui`
## Make sure that mac exe will not contain broken library links # @rpath instructs the dynamic linker to search a list of paths in order to locate the framework
# * remove absolute paths for Qt libs # The rules can be listed using `otool -l "${path_to_binary}"`
# * add relative part to app bundle Frameworks # 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 path_to_binary=$1
@ -29,11 +32,11 @@ if [ -z ${path_to_binary} ]; then
exit 2 exit 2
fi 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 do
if [ ! -z "${remove_path_qt}" ]; then if [ ! -z "${path_to_remove}" ]; then
printf "\e[0;32mRemove path to qt ${remove_path_qt} ...\033[0m\n\e[0;31m" printf "\e[0;32mRemove path to qt '${path_to_remove}' ...\033[0m\n\e[0;31m"
install_name_tool -delete_rpath "${remove_path_qt}" "${path_to_binary}" || exit 1 install_name_tool -delete_rpath "${path_to_remove}" "${path_to_binary}" || exit 1
fi fi
done done
rpath_rule=$(otool -l "${path_to_binary}" | grep executable_path | awk '{print $2}') rpath_rule=$(otool -l "${path_to_binary}" | grep executable_path | awk '{print $2}')