diff --git a/.gitattributes b/.gitattributes
index d6c5853d..c03a430f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1 @@
-Changelog.md merge=union
+unreleased.md merge=union
diff --git a/.github/ISSUE_TEMPLATE/general-issue-template.md b/.github/ISSUE_TEMPLATE/general-issue-template.md
index 7217b269..6eaaefe3 100644
--- a/.github/ISSUE_TEMPLATE/general-issue-template.md
+++ b/.github/ISSUE_TEMPLATE/general-issue-template.md
@@ -27,6 +27,9 @@ Issue tracker is ONLY used for reporting bugs with technical details. "It doesn'
3.
4.
+## Version Information
+
+
## Context (Environment)
diff --git a/Changelog.md b/Changelog.md
index 89fe96e1..15c110e5 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,30 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
+## [Bridge 1.5.2] Golden Gate
+
+### Changed
+* GODT-883 Use `ClearPacket` for `text/plain` with signature
+
+
+## [Bridge 1.5.1] Golden Gate
+
+### Added
+* GODT-701 Try load messages one-by-one if IMAP server errors with batch load
+ and not interrupt the transfer.
+* GODT-878 Tests for send packet creation logic.
+
+### Changed
+* GODT-180 Updated Sentry client.
+* GODT-651 Build creates proper binary names.
+* GODT-878 Fix an issue where the random session key is inadvertently sent to
+ the Proton server. The data payload is always encrypted within TLS, but this
+ is still a potential privacy problem. Discovered by Proton's internal
+ security audit team.
+* GODT-878 Refactor and move the send packet creation logic to `pmapi.SendMessageReq`.
+* GODT-878 Encryption of session keys moved to pmapi.
+
+
## [IE 1.2.1, 1.2.2] Elbe
### Added
@@ -16,6 +40,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [Bridge 1.5.0] Golden Gate
+
### Changed
* Updated go-mbox dependency back to upstream.
diff --git a/Makefile b/Makefile
index 0cb6ab86..537bbaba 100644
--- a/Makefile
+++ b/Makefile
@@ -10,19 +10,21 @@ TARGET_OS?=${GOOS}
.PHONY: build build-ie build-nogui build-ie-nogui check-has-go
# Keep version hardcoded so app build works also without Git repository.
-BRIDGE_APP_VERSION?=1.5.0-git
+BRIDGE_APP_VERSION?=1.5.2-git
IE_APP_VERSION?=1.2.2-git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico
SRC_ICNS:=Bridge.icns
SRC_SVG:=logo.svg
TGT_ICNS:=Bridge.icns
+EXE_NAME:=proton-bridge
ifeq "${TARGET_CMD}" "Import-Export"
APP_VERSION:=${IE_APP_VERSION}
SRC_ICO:=ie.ico
SRC_ICNS:=ie.icns
SRC_SVG:=ie.svg
TGT_ICNS:=ImportExport.icns
+ EXE_NAME:=proton-ie
endif
REVISION:=$(shell git rev-parse --short=10 HEAD)
BUILD_TIME:=$(shell date +%FT%T%z)
@@ -40,17 +42,22 @@ BUILD_FLAGS_NOGUI+= ${GO_LDFLAGS}
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
ICO_FILES:=
-EXE:=$(shell basename ${CURDIR})
-
+DIRNAME:=$(shell basename ${CURDIR})
+EXE:=${EXE_NAME}
+EXE_QT:=${DIRNAME}
ifeq "${TARGET_OS}" "windows"
EXE:=${EXE}.exe
+ EXE_QT:=${EXE_QT}.exe
ICO_FILES:=${SRC_ICO} icon.rc icon_windows.syso
endif
ifeq "${TARGET_OS}" "darwin"
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
- EXE:=${EXE}.app/Contents/MacOS/${EXE}
+ EXE:=${EXE}.app
+ EXE_QT:=${EXE_QT}.app
+ EXE_BINARY_DARWIN:=/Contents/MacOS/${EXE_NAME}
endif
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
+EXE_QT_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE_QT}
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
ifeq "${TARGET_CMD}" "Import-Export"
@@ -62,7 +69,7 @@ build-ie:
TARGET_CMD=Import-Export $(MAKE) build
build-nogui:
- go build ${BUILD_FLAGS_NOGUI} -o ${TARGET_CMD} cmd/${TARGET_CMD}/main.go
+ go build ${BUILD_FLAGS_NOGUI} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
build-ie-nogui:
TARGET_CMD=Import-Export $(MAKE) build-nogui
@@ -77,12 +84,16 @@ ${DEPLOY_DIR}/linux: ${EXE_TARGET}
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
+ if [ "${DIRNAME}" != "${EXE_NAME}" ]; then \
+ mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
+ perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
+ fi
cp ./internal/frontend/share/icons/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${TGT_ICNS}
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework"
- ./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}"
+ ./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
${DEPLOY_DIR}/windows: ${EXE_TARGET}
cp ./internal/frontend/share/icons/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
@@ -100,6 +111,7 @@ ${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} update-vendor
cp cmd/${TARGET_CMD}/main.go .
qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET}
mv deploy cmd/${TARGET_CMD}
+ if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi
rm -rf ${TARGET_OS} main.go
logo.ico ie.ico: ./internal/frontend/share/icons/${SRC_ICO}
@@ -196,7 +208,7 @@ coverage: test
mocks:
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Configer,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
- mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager > internal/transfer/mocks/mocks.go
+ mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager,IMAPClientProvider > internal/transfer/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser > internal/store/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
diff --git a/go.mod b/go.mod
index e1ac9ae2..06851867 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,6 @@ require (
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
- github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/cucumber/godog v0.8.1
@@ -43,7 +42,7 @@ require (
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
github.com/fatih/color v1.9.0
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
- github.com/getsentry/raven-go v0.2.0
+ github.com/getsentry/sentry-go v0.8.0
github.com/go-resty/resty/v2 v2.3.0
github.com/golang/mock v1.4.4
github.com/google/go-cmp v0.5.1
diff --git a/go.sum b/go.sum
index 9db92e54..bd9323b0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,10 @@
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
+github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
+github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
+github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
@@ -31,22 +35,30 @@ github.com/ProtonMail/gopenpgp/v2 v2.0.1 h1:x0uvDhry5WzoHeJO4J3dgMLhG4Z9PeBJ2O+s
github.com/ProtonMail/gopenpgp/v2 v2.0.1/go.mod h1:wQQCJo7DURO6S9VwH+kSDEYs/B63yZnAEfGlOg8YNBY=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
-github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
-github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -57,6 +69,11 @@ github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7h
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4 h1:/JIALzmCduf5o8TWJSiOBzTb9+R0SChwElUrJLlp2po=
@@ -81,69 +98,145 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
+github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
+github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
-github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
-github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
+github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
+github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
+github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
+github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
+github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
+github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
+github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
+github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
+github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
+github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
+github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
+github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
+github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
+github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
+github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@@ -152,6 +245,14 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -168,29 +269,58 @@ github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLw
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
+github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
+github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -201,6 +331,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -210,9 +343,17 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/bridge/credits.go b/internal/bridge/credits.go
index f6fb4c16..9977b19f 100644
--- a/internal/bridge/credits.go
+++ b/internal/bridge/credits.go
@@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see .
-// Code generated by ./credits.sh at Wed Nov 4 13:57:47 CET 2020. DO NOT EDIT.
+// Code generated by ./credits.sh at Tue Nov 24 08:56:01 AM CET 2020. DO NOT EDIT.
package bridge
-const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/antlr/antlr4;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
+const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
diff --git a/internal/bridge/release_notes.go b/internal/bridge/release_notes.go
index d2a782a0..ccc4bc4f 100644
--- a/internal/bridge/release_notes.go
+++ b/internal/bridge/release_notes.go
@@ -15,17 +15,18 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see .
-// Code generated by ./release-notes.sh at 'Wed Nov 4 12:24:35 PM CET 2020'. DO NOT EDIT.
+// Code generated by ./release-notes.sh at 'Mon Nov 23 07:38:53 AM CET 2020'. DO NOT EDIT.
package bridge
-const ReleaseNotes = `• Ensured better message flow by refactoring both address and date parsing
-• Improved secure connectivity checks
-• Better deb packaging
-• More robust error handling
+const ReleaseNotes = `• Improved package creation logic
+• Refactor of sending functions to simplify code maintenance
+• Added tests for package creation
+• For more detailed summary of the changes see https://github.com/ProtonMail/proton-bridge/blob/master/Changelog.md
`
-const ReleaseFixedBugs = `• Ensured that conversations are properly threaded
-• Fixed Linux font issues (Fedora)
-• Better handling of Mime encrypted messages
+const ReleaseFixedBugs = `• Bridge crashes related to labels handling
+• GUI popup related to TLS connection error
+• An issue where a random session key is included in the data payload
+• Error handling (including improved detection)
`
diff --git a/internal/cmd/main.go b/internal/cmd/main.go
index e83d806b..fa15636d 100644
--- a/internal/cmd/main.go
+++ b/internal/cmd/main.go
@@ -22,7 +22,7 @@ import (
"runtime"
"github.com/ProtonMail/proton-bridge/pkg/constants"
- "github.com/getsentry/raven-go"
+ "github.com/getsentry/sentry-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
@@ -51,10 +51,18 @@ var (
// Main sets up Sentry, filters out unwanted args, creates app and runs it.
func Main(appName, usage string, extraFlags []cli.Flag, run func(*cli.Context) error) {
- if err := raven.SetDSN(constants.DSNSentry); err != nil {
+ err := sentry.Init(sentry.ClientOptions{
+ Dsn: constants.DSNSentry,
+ Release: constants.Revision,
+ })
+
+ sentry.ConfigureScope(func(scope *sentry.Scope) {
+ scope.SetFingerprint([]string{"{{ default }}"})
+ })
+
+ if err != nil {
log.WithError(err).Errorln("Can not setup sentry DSN")
}
- raven.SetRelease(constants.Revision)
filterProcessSerialNumberFromArgs()
filterRestartNumberFromArgs()
diff --git a/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml b/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml
index e7ee4680..f63f7a83 100644
--- a/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml
+++ b/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml
@@ -45,7 +45,7 @@ Row {
}
InfoToolTip {
- info: qsTr( "When master import lablel is selected then all imported email will have this label.", "Tooltip text for master import label")
+ info: qsTr( "When master import label is selected then all imported emails will have this label.", "Tooltip text for master import label")
anchors.verticalCenter: parent.verticalCenter
}
diff --git a/internal/importexport/credits.go b/internal/importexport/credits.go
index a7520e34..2f5febee 100644
--- a/internal/importexport/credits.go
+++ b/internal/importexport/credits.go
@@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see .
-// Code generated by ./credits.sh at Wed Nov 4 13:57:47 CET 2020. DO NOT EDIT.
+// Code generated by ./credits.sh at Tue Nov 24 08:56:01 AM CET 2020. DO NOT EDIT.
package importexport
-const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/antlr/antlr4;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
+const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
diff --git a/internal/smtp/preferences.go b/internal/smtp/preferences.go
index 15b78583..2def010e 100644
--- a/internal/smtp/preferences.go
+++ b/internal/smtp/preferences.go
@@ -45,7 +45,7 @@ type SendPreferences struct {
// internal emails (including the so-called encrypted-to-outside emails,
// which even though meant for external users, they don't really get out of
// our platform). If the email is sent unencrypted, no PGP scheme is needed.
- Scheme int
+ Scheme pmapi.PackageFlag
// MIMEType is the MIME type to use for formatting the body of the email
// (before encryption/after decryption). The standard possibilities are the
@@ -191,8 +191,12 @@ func (b *sendPreferencesBuilder) build() (p SendPreferences) {
p.Scheme = pmapi.PGPMIMEPackage
}
- case b.shouldSign() && !b.shouldEncrypt() && b.getScheme() == pgpMIME:
- p.Scheme = pmapi.ClearMIMEPackage
+ case b.shouldSign() && !b.shouldEncrypt():
+ if b.getScheme() == pgpInline {
+ p.Scheme = pmapi.ClearPackage
+ } else {
+ p.Scheme = pmapi.ClearMIMEPackage
+ }
default:
p.Scheme = pmapi.ClearPackage
diff --git a/internal/smtp/preferences_test.go b/internal/smtp/preferences_test.go
index c0e3446e..e79f366a 100644
--- a/internal/smtp/preferences_test.go
+++ b/internal/smtp/preferences_test.go
@@ -41,7 +41,7 @@ func TestPreferencesBuilder(t *testing.T) {
wantEncrypt bool
wantSign bool
- wantScheme int
+ wantScheme pmapi.PackageFlag
wantMIMEType string
wantPublicKey string
}{
@@ -254,6 +254,20 @@ func TestPreferencesBuilder(t *testing.T) {
wantMIMEType: "multipart/mixed",
},
+ {
+ name: "external with contact sign enabled and plain text",
+
+ contactMeta: &ContactMetadata{MIMEType: "text/plain", Scheme: pgpInline, Sign: true, SignIsSet: true},
+ receivedKeys: []pmapi.PublicKey{},
+ isInternal: false,
+ mailSettings: pmapi.MailSettings{PGPScheme: pmapi.PGPMIMEPackage, DraftMIMEType: "text/html"},
+
+ wantEncrypt: false,
+ wantSign: true,
+ wantScheme: pmapi.ClearPackage,
+ wantMIMEType: "text/plain",
+ },
+
{
name: "external with sign enabled, sending plaintext, should still send as ClearMIME",
diff --git a/internal/smtp/user.go b/internal/smtp/user.go
index 37957249..b438256c 100644
--- a/internal/smtp/user.go
+++ b/internal/smtp/user.go
@@ -187,7 +187,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
log.WithError(err).Error("Failed to parse message")
return
}
- clearBody := message.Body
+ richBody := message.Body
externalID := message.Header.Get("Message-Id")
externalID = strings.Trim(externalID, "<>")
@@ -256,7 +256,6 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
atts = append(atts, message.Attachments...)
// Decrypt attachment keys, because we will need to re-encrypt them with the recipients' public keys.
attkeys := make(map[string]*crypto.SessionKey)
- attkeysEncoded := make(map[string]pmapi.AlgoKey)
for _, att := range atts {
var keyPackets []byte
@@ -266,23 +265,9 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
if attkeys[att.ID], err = kr.DecryptSessionKey(keyPackets); err != nil {
return errors.Wrap(err, "decrypting attachment session key")
}
- attkeysEncoded[att.ID] = pmapi.AlgoKey{
- Key: attkeys[att.ID].GetBase64Key(),
- Algorithm: attkeys[att.ID].Algo,
- }
}
- plainSharedScheme := 0
- htmlSharedScheme := 0
- mimeSharedType := 0
-
- plainAddressMap := make(map[string]*pmapi.MessageAddress)
- htmlAddressMap := make(map[string]*pmapi.MessageAddress)
- mimeAddressMap := make(map[string]*pmapi.MessageAddress)
-
- var plainKey, htmlKey, mimeKey *crypto.SessionKey
- var plainData, htmlData, mimeData []byte
-
+ req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
containsUnencryptedRecipients := false
for _, email := range to {
@@ -298,61 +283,15 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
return err
}
- var signature int
+ var signature pmapi.SignatureFlag
if sendPreferences.Sign {
- signature = pmapi.YesSignature
+ signature = pmapi.SignatureDetached
} else {
- signature = pmapi.NoSignature
+ signature = pmapi.SignatureNone
}
- if sendPreferences.Scheme == pmapi.PGPMIMEPackage || sendPreferences.Scheme == pmapi.ClearMIMEPackage {
- if mimeKey == nil {
- if mimeKey, mimeData, err = encryptSymmetric(kr, mimeBody, true); err != nil {
- return err
- }
- }
- if sendPreferences.Scheme == pmapi.PGPMIMEPackage {
- mimeBodyPacket, _, err := createPackets(sendPreferences.PublicKey, mimeKey, map[string]*crypto.SessionKey{})
- if err != nil {
- return err
- }
- mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, BodyKeyPacket: mimeBodyPacket, Signature: signature}
- } else {
- mimeAddressMap[email] = &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- }
- mimeSharedType |= sendPreferences.Scheme
- } else {
- switch sendPreferences.MIMEType {
- case pmapi.ContentTypePlainText:
- if plainKey == nil {
- if plainKey, plainData, err = encryptSymmetric(kr, plainBody, true); err != nil {
- return err
- }
- }
- newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
- newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, plainKey, attkeys)
- if err != nil {
- return err
- }
- }
- plainAddressMap[email] = newAddress
- plainSharedScheme |= sendPreferences.Scheme
- case pmapi.ContentTypeHTML:
- if htmlKey == nil {
- if htmlKey, htmlData, err = encryptSymmetric(kr, clearBody, true); err != nil {
- return err
- }
- }
- newAddress := &pmapi.MessageAddress{Type: sendPreferences.Scheme, Signature: signature}
- if sendPreferences.Encrypt && sendPreferences.PublicKey != nil {
- newAddress.BodyKeyPacket, newAddress.AttachmentKeyPackets, err = createPackets(sendPreferences.PublicKey, htmlKey, attkeys)
- if err != nil {
- return err
- }
- }
- htmlAddressMap[email] = newAddress
- htmlSharedScheme |= sendPreferences.Scheme
- }
+
+ if err := req.AddRecipient(email, sendPreferences.Scheme, sendPreferences.PublicKey, signature, sendPreferences.MIMEType, sendPreferences.Encrypt); err != nil {
+ return errors.Wrap(err, "failed to add recipient")
}
}
@@ -370,31 +309,7 @@ func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err
}
}
- req := &pmapi.SendMessageReq{}
-
- plainPkg := buildPackage(plainAddressMap, plainSharedScheme, pmapi.ContentTypePlainText, plainData, plainKey, attkeysEncoded)
- if plainPkg != nil {
- req.Packages = append(req.Packages, plainPkg)
- }
-
- htmlPkg := buildPackage(htmlAddressMap, htmlSharedScheme, pmapi.ContentTypeHTML, htmlData, htmlKey, attkeysEncoded)
- if htmlPkg != nil {
- req.Packages = append(req.Packages, htmlPkg)
- }
-
- if len(mimeAddressMap) > 0 {
- pkg := &pmapi.MessagePackage{
- Body: base64.StdEncoding.EncodeToString(mimeData),
- Addresses: mimeAddressMap,
- MIMEType: pmapi.ContentTypeMultipartMixed,
- Type: mimeSharedType,
- BodyKey: pmapi.AlgoKey{
- Key: mimeKey.GetBase64Key(),
- Algorithm: mimeKey.Algo,
- },
- }
- req.Packages = append(req.Packages, pkg)
- }
+ req.PreparePackages()
return su.storeUser.SendMessage(message.ID, req)
}
diff --git a/internal/smtp/utils.go b/internal/smtp/utils.go
index 36b98171..745ff2dc 100644
--- a/internal/smtp/utils.go
+++ b/internal/smtp/utils.go
@@ -18,11 +18,7 @@
package smtp
import (
- "encoding/base64"
"regexp"
-
- "github.com/ProtonMail/gopenpgp/v2/crypto"
- "github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
//nolint:gochecknoglobals // Used like a constant
@@ -35,85 +31,3 @@ var mailFormat = regexp.MustCompile(`.+@.+\..+`)
func looksLikeEmail(e string) bool {
return mailFormat.MatchString(e)
}
-
-func createPackets(
- pubkey *crypto.KeyRing,
- bodyKey *crypto.SessionKey,
- attkeys map[string]*crypto.SessionKey,
-) (bodyPacket string, attachmentPackets map[string]string, err error) {
- // Encrypt message body keys.
- packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
- if err != nil {
- return
- }
- bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
-
- // Encrypt attachment keys.
- attachmentPackets = make(map[string]string)
- for id, attkey := range attkeys {
- var packets []byte
- if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
- return
- }
- attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
- }
- return
-}
-
-func encryptSymmetric(
- kr *crypto.KeyRing,
- textToEncrypt string,
- canonicalizeText bool, // nolint[unparam]
-) (key *crypto.SessionKey, symEncryptedData []byte, err error) {
- // We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
- firstKey, err := kr.FirstKey()
- if err != nil {
- return
- }
-
- pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
- if err != nil {
- return
- }
-
- pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
- if err != nil {
- return
- }
-
- key, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
- if err != nil {
- return
- }
-
- symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
-
- return
-}
-
-func buildPackage(
- addressMap map[string]*pmapi.MessageAddress,
- sharedScheme int,
- mimeType string,
- bodyData []byte,
- bodyKey *crypto.SessionKey,
- attKeys map[string]pmapi.AlgoKey,
-) (pkg *pmapi.MessagePackage) {
- if len(addressMap) == 0 {
- return nil
- }
-
- pkg = &pmapi.MessagePackage{
- Body: base64.StdEncoding.EncodeToString(bodyData),
- Addresses: addressMap,
- MIMEType: mimeType,
- Type: sharedScheme,
- }
-
- if sharedScheme|pmapi.ClearPackage > 0 {
- pkg.BodyKey.Key = bodyKey.GetBase64Key()
- pkg.BodyKey.Algorithm = bodyKey.Algo
- pkg.AttachmentKeys = attKeys
- }
- return pkg
-}
diff --git a/internal/transfer/mocks/mocks.go b/internal/transfer/mocks/mocks.go
index 560303bf..8c78a87a 100644
--- a/internal/transfer/mocks/mocks.go
+++ b/internal/transfer/mocks/mocks.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/ProtonMail/proton-bridge/internal/transfer (interfaces: PanicHandler,ClientManager)
+// Source: github.com/ProtonMail/proton-bridge/internal/transfer (interfaces: PanicHandler,ClientManager,IMAPClientProvider)
// Package mocks is a generated GoMock package.
package mocks
@@ -8,6 +8,8 @@ import (
reflect "reflect"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
+ imap "github.com/emersion/go-imap"
+ sasl "github.com/emersion/go-sasl"
gomock "github.com/golang/mock/gomock"
)
@@ -96,3 +98,170 @@ func (mr *MockClientManagerMockRecorder) GetClient(arg0 interface{}) *gomock.Cal
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockClientManager)(nil).GetClient), arg0)
}
+
+// MockIMAPClientProvider is a mock of IMAPClientProvider interface
+type MockIMAPClientProvider struct {
+ ctrl *gomock.Controller
+ recorder *MockIMAPClientProviderMockRecorder
+}
+
+// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider
+type MockIMAPClientProviderMockRecorder struct {
+ mock *MockIMAPClientProvider
+}
+
+// NewMockIMAPClientProvider creates a new mock instance
+func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider {
+ mock := &MockIMAPClientProvider{ctrl: ctrl}
+ mock.recorder = &MockIMAPClientProviderMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder {
+ return m.recorder
+}
+
+// Authenticate mocks base method
+func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Authenticate", arg0)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Authenticate indicates an expected call of Authenticate
+func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0)
+}
+
+// Capability mocks base method
+func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Capability")
+ ret0, _ := ret[0].(map[string]bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Capability indicates an expected call of Capability
+func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability))
+}
+
+// Fetch mocks base method
+func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Fetch indicates an expected call of Fetch
+func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2)
+}
+
+// List mocks base method
+func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "List", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// List indicates an expected call of List
+func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2)
+}
+
+// Login mocks base method
+func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Login", arg0, arg1)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Login indicates an expected call of Login
+func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1)
+}
+
+// Select mocks base method
+func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Select", arg0, arg1)
+ ret0, _ := ret[0].(*imap.MailboxStatus)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Select indicates an expected call of Select
+func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1)
+}
+
+// State mocks base method
+func (m *MockIMAPClientProvider) State() imap.ConnState {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "State")
+ ret0, _ := ret[0].(imap.ConnState)
+ return ret0
+}
+
+// State indicates an expected call of State
+func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State))
+}
+
+// Support mocks base method
+func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Support", arg0)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Support indicates an expected call of Support
+func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0)
+}
+
+// SupportAuth mocks base method
+func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SupportAuth", arg0)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SupportAuth indicates an expected call of SupportAuth
+func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0)
+}
+
+// UidFetch mocks base method
+func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// UidFetch indicates an expected call of UidFetch
+func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2)
+}
diff --git a/internal/transfer/provider_imap.go b/internal/transfer/provider_imap.go
index c2669c49..19cc5214 100644
--- a/internal/transfer/provider_imap.go
+++ b/internal/transfer/provider_imap.go
@@ -21,28 +21,49 @@ import (
"net"
"strings"
- imapClient "github.com/emersion/go-imap/client"
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-sasl"
)
+type IMAPClientProvider interface {
+ Capability() (map[string]bool, error)
+ Support(cap string) (bool, error)
+ State() imap.ConnState
+ SupportAuth(mech string) (bool, error)
+ Authenticate(auth sasl.Client) error
+ Login(username, password string) error
+ List(ref, name string, ch chan *imap.MailboxInfo) error
+ Select(name string, readOnly bool) (*imap.MailboxStatus, error)
+ Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error
+ UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error
+}
+
// IMAPProvider implements export from IMAP server.
type IMAPProvider struct {
username string
password string
addr string
- client *imapClient.Client
+ clientDialer func(addr string) (IMAPClientProvider, error)
+ client IMAPClientProvider
timeIt *timeIt
}
// NewIMAPProvider returns new IMAPProvider.
func NewIMAPProvider(username, password, host, port string) (*IMAPProvider, error) {
+ return newIMAPProvider(imapClientDial, username, password, host, port)
+}
+
+func newIMAPProvider(clientDialer func(string) (IMAPClientProvider, error), username, password, host, port string) (*IMAPProvider, error) {
p := &IMAPProvider{
username: username,
password: password,
addr: net.JoinHostPort(host, port),
timeIt: newTimeIt("imap"),
+
+ clientDialer: clientDialer,
}
if err := p.auth(); err != nil {
diff --git a/internal/transfer/provider_imap_source.go b/internal/transfer/provider_imap_source.go
index 9f669a0c..04b68179 100644
--- a/internal/transfer/provider_imap_source.go
+++ b/internal/transfer/provider_imap_source.go
@@ -84,12 +84,37 @@ func (p *IMAPProvider) loadMessagesInfo(rule *Rule, progress *Progress, uidValid
p.timeIt.start("load", rule.SourceMailbox.Name)
defer p.timeIt.stop("load", rule.SourceMailbox.Name)
+ log := log.WithField("mailbox", rule.SourceMailbox.Name)
messagesInfo := map[string]imapMessageInfo{}
+ fetchItems := []imap.FetchItem{imap.FetchUid, imap.FetchRFC822Size}
+ if rule.HasTimeLimit() {
+ fetchItems = append(fetchItems, imap.FetchEnvelope)
+ }
+
+ processMessageCallback := func(imapMessage *imap.Message) {
+ if rule.HasTimeLimit() {
+ t := imapMessage.Envelope.Date.Unix()
+ if t != 0 && !rule.isTimeInRange(t) {
+ log.WithField("uid", imapMessage.Uid).Debug("Message skipped due to time")
+ return
+ }
+ }
+ id := getUniqueMessageID(rule.SourceMailbox.Name, uidValidity, imapMessage.Uid)
+ // We use ID as key to ensure we have every unique message only once.
+ // Some IMAP servers responded twice the same message...
+ messagesInfo[id] = imapMessageInfo{
+ id: id,
+ uid: imapMessage.Uid,
+ size: imapMessage.Size,
+ }
+ progress.addMessage(id, []string{rule.SourceMailbox.Name}, rule.TargetMailboxNames())
+ }
+
pageStart := uint32(1)
pageEnd := imapPageSize
for {
- if progress.shouldStop() {
+ if progress.shouldStop() || pageStart > count {
break
}
@@ -100,45 +125,21 @@ func (p *IMAPProvider) loadMessagesInfo(rule *Rule, progress *Progress, uidValid
seqSet := &imap.SeqSet{}
seqSet.AddRange(pageStart, pageEnd)
-
- items := []imap.FetchItem{imap.FetchUid, imap.FetchRFC822Size}
- if rule.HasTimeLimit() {
- items = append(items, imap.FetchEnvelope)
- }
-
- pageMsgCount := uint32(0)
- processMessageCallback := func(imapMessage *imap.Message) {
- pageMsgCount++
- if rule.HasTimeLimit() {
- t := imapMessage.Envelope.Date.Unix()
- if t != 0 && !rule.isTimeInRange(t) {
- log.WithField("uid", imapMessage.Uid).Debug("Message skipped due to time")
- return
+ err := p.fetch(rule.SourceMailbox.Name, seqSet, fetchItems, processMessageCallback)
+ if err != nil {
+ log.WithError(err).WithField("idx", seqSet).Warning("Load batch fetch failed, trying one by one")
+ for ; pageStart <= pageEnd; pageStart++ {
+ seqSet := &imap.SeqSet{}
+ seqSet.AddNum(pageStart)
+ if err := p.fetch(rule.SourceMailbox.Name, seqSet, fetchItems, processMessageCallback); err != nil {
+ log.WithError(err).WithField("idx", seqSet).Warning("Load fetch failed, skipping the message")
}
}
- id := getUniqueMessageID(rule.SourceMailbox.Name, uidValidity, imapMessage.Uid)
- // We use ID as key to ensure we have every unique message only once.
- // Some IMAP servers responded twice the same message...
- messagesInfo[id] = imapMessageInfo{
- id: id,
- uid: imapMessage.Uid,
- size: imapMessage.Size,
- }
- progress.addMessage(id, []string{rule.SourceMailbox.Name}, rule.TargetMailboxNames())
}
- progress.callWrap(func() error {
- return p.fetch(rule.SourceMailbox.Name, seqSet, items, processMessageCallback)
- })
-
- if pageMsgCount < imapPageSize {
- break
- }
-
- pageStart = pageEnd
+ pageStart = pageEnd + 1
pageEnd += imapPageSize
}
-
return messagesInfo
}
diff --git a/internal/transfer/provider_imap_test.go b/internal/transfer/provider_imap_test.go
new file mode 100644
index 00000000..55443d4c
--- /dev/null
+++ b/internal/transfer/provider_imap_test.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2020 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 .
+
+package transfer
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/emersion/go-imap"
+ gomock "github.com/golang/mock/gomock"
+ "github.com/pkg/errors"
+ r "github.com/stretchr/testify/require"
+)
+
+func newTestIMAPProvider(t *testing.T, m mocks) *IMAPProvider {
+ m.imapClientProvider.EXPECT().State().Return(imap.ConnectedState).AnyTimes()
+ m.imapClientProvider.EXPECT().Capability().Return(map[string]bool{
+ "AUTH": true,
+ }, nil).AnyTimes()
+
+ dialer := func(string) (IMAPClientProvider, error) {
+ return m.imapClientProvider, nil
+ }
+ provider, err := newIMAPProvider(dialer, "user", "pass", "host", "42")
+ r.NoError(t, err)
+ return provider
+}
+
+func TestProviderIMAPLoadMessagesInfo(t *testing.T) {
+ m := initMocks(t)
+ defer m.ctrl.Finish()
+
+ provider := newTestIMAPProvider(t, m)
+
+ progress := newProgress(log, nil)
+ drainProgressUpdateChannel(&progress)
+
+ rule := &Rule{SourceMailbox: Mailbox{Name: "Mailbox"}}
+ uidValidity := 1
+ count := 2200
+ failingIndex := 2100
+
+ m.imapClientProvider.EXPECT().Select(rule.SourceMailbox.Name, gomock.Any()).Return(&imap.MailboxStatus{}, nil).AnyTimes()
+ m.imapClientProvider.EXPECT().
+ Fetch(gomock.Any(), gomock.Any(), gomock.Any()).
+ DoAndReturn(func(seqSet *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
+ defer close(ch)
+ for _, seq := range seqSet.Set {
+ for i := seq.Start; i <= seq.Stop; i++ {
+ if int(i) == failingIndex {
+ return errors.New("internal server error")
+ }
+ ch <- &imap.Message{
+ SeqNum: i,
+ Uid: i * 10,
+ Size: i * 100,
+ }
+ }
+ }
+ return nil
+ }).
+ // 2200 messages is split into two batches (2000 and 200),
+ // the second one fails and makes 200 calls (one-by-one).
+ // Plus two failed requests are repeated `imapRetries` times.
+ Times(2 + 200 + (2 * (imapRetries - 1)))
+
+ messageInfo := provider.loadMessagesInfo(rule, &progress, uint32(uidValidity), uint32(count))
+
+ r.Equal(t, count-1, len(messageInfo)) // One message produces internal server error.
+ for index := 1; index <= count; index++ {
+ uid := index * 10
+ key := fmt.Sprintf("%s_%d:%d", rule.SourceMailbox.Name, uidValidity, uid)
+
+ if index == failingIndex {
+ r.Empty(t, messageInfo[key])
+ continue
+ }
+
+ r.Equal(t, imapMessageInfo{
+ id: key,
+ uid: uint32(uid),
+ size: uint32(index * 100),
+ }, messageInfo[key])
+ }
+}
diff --git a/internal/transfer/provider_imap_utils.go b/internal/transfer/provider_imap_utils.go
index 16f2166e..4dbda047 100644
--- a/internal/transfer/provider_imap_utils.go
+++ b/internal/transfer/provider_imap_utils.go
@@ -24,10 +24,11 @@ import (
"time"
imapID "github.com/ProtonMail/go-imap-id"
+ "github.com/ProtonMail/proton-bridge/pkg/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/emersion/go-imap"
imapClient "github.com/emersion/go-imap/client"
- sasl "github.com/emersion/go-sasl"
+ "github.com/emersion/go-sasl"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -51,6 +52,43 @@ func (l *imapErrorLogger) Println(v ...interface{}) {
l.log.Errorln(v...)
}
+func imapClientDial(addr string) (IMAPClientProvider, error) {
+ if _, err := net.DialTimeout("tcp", addr, imapDialTimeout); err != nil {
+ return nil, errors.Wrap(err, "failed to dial server")
+ }
+
+ client, err := imapClientDialHelper(addr)
+ if err == nil {
+ client.ErrorLog = &imapErrorLogger{logrus.WithField("pkg", "imap-client")}
+ // Logrus `WriterLevel` fails for big messages because of bufio.MaxScanTokenSize limit.
+ // Also, this spams a lot, uncomment once needed during development.
+ //client.SetDebug(imap.NewDebugWriter(
+ // logrus.WithField("pkg", "imap/client").WriterLevel(logrus.TraceLevel),
+ // logrus.WithField("pkg", "imap/server").WriterLevel(logrus.TraceLevel),
+ //))
+ }
+ return client, err
+}
+
+func imapClientDialHelper(addr string) (*imapClient.Client, error) {
+ host, _, _ := net.SplitHostPort(addr)
+ if host == "127.0.0.1" {
+ return imapClient.Dial(addr)
+ }
+
+ // IMAP mail.yahoo.com has problem with golang TLS 1.3 implementation
+ // with weird behaviour, i.e., Yahoo does not return error during dial
+ // or handshake but server does logs out right after successful login
+ // leaving no time to perform any action.
+ // Limiting TLS to version 1.2 is working just fine.
+ var tlsConf *tls.Config
+ if strings.Contains(strings.ToLower(host), "yahoo") {
+ log.Warning("Yahoo server detected: limiting maximal TLS version to 1.2.")
+ tlsConf = &tls.Config{MaxVersion: tls.VersionTLS12}
+ }
+ return imapClient.DialTLS(addr, tlsConf)
+}
+
func (p *IMAPProvider) ensureConnection(callback func() error) error {
return p.ensureConnectionAndSelection(callback, "")
}
@@ -138,41 +176,10 @@ func (p *IMAPProvider) auth() error { //nolint[funlen]
log.Info("Connecting to server")
- if _, err := net.DialTimeout("tcp", p.addr, imapDialTimeout); err != nil {
- return ErrIMAPConnection{imapError{Err: err, Message: "failed to dial server"}}
- }
-
- var client *imapClient.Client
- var err error
- host, _, _ := net.SplitHostPort(p.addr)
- if host == "127.0.0.1" {
- client, err = imapClient.Dial(p.addr)
- } else {
- // IMAP.mail.yahoo.com have problem with golang TLS1.3
- // implementation with weird behaviour i.e. Yahoo
- // no error during dial or handshake but server logs out right
- // after successful login leaving no time to perform any
- // action. It was discovered that limiting to maximum TLS
- // version 1.2 for yahoo servers is working solution.
-
- var tlsConf *tls.Config
- if strings.Contains(strings.ToLower(host), "yahoo") {
- log.Warning("Yahoo server detected: limiting maximal TLS version to 1.2.")
- tlsConf = &tls.Config{MaxVersion: tls.VersionTLS12}
- }
- client, err = imapClient.DialTLS(p.addr, tlsConf)
- }
+ client, err := p.clientDialer(p.addr)
if err != nil {
return ErrIMAPConnection{imapError{Err: err, Message: "failed to connect to server"}}
}
-
- client.ErrorLog = &imapErrorLogger{logrus.WithField("pkg", "imap-client")}
- // Logrus `WriterLevel` fails for big messages because of bufio.MaxScanTokenSize limit.
- // Also, this spams a lot, uncomment once needed during development.
- //client.SetDebug(imap.NewDebugWriter(
- // logrus.WithField("pkg", "imap/client").WriterLevel(logrus.TraceLevel),
- // logrus.WithField("pkg", "imap/server").WriterLevel(logrus.TraceLevel),
- //))
p.client = client
log.Info("Connected")
@@ -210,13 +217,15 @@ func (p *IMAPProvider) auth() error { //nolint[funlen]
log.Info("Logged in")
- idClient := imapID.NewClient(p.client)
- if ok, err := idClient.SupportID(); err == nil && ok {
- serverID, err := idClient.ID(imapID.ID{
- imapID.FieldName: "ImportExport",
- imapID.FieldVersion: "beta",
- })
- log.WithField("ID", serverID).WithError(err).Debug("Server info")
+ if c, ok := p.client.(*imapClient.Client); ok {
+ idClient := imapID.NewClient(c)
+ if ok, err := idClient.SupportID(); err == nil && ok {
+ serverID, err := idClient.ID(imapID.ID{
+ imapID.FieldName: "ImportExport",
+ imapID.FieldVersion: constants.Version,
+ })
+ log.WithField("ID", serverID).WithError(err).Debug("Server info")
+ }
}
return err
diff --git a/internal/transfer/transfer_test.go b/internal/transfer/transfer_test.go
index 653407f9..f71e6d9c 100644
--- a/internal/transfer/transfer_test.go
+++ b/internal/transfer/transfer_test.go
@@ -31,11 +31,12 @@ import (
type mocks struct {
t *testing.T
- ctrl *gomock.Controller
- panicHandler *transfermocks.MockPanicHandler
- clientManager *transfermocks.MockClientManager
- pmapiClient *pmapimocks.MockClient
- pmapiConfig *pmapi.ClientConfig
+ ctrl *gomock.Controller
+ panicHandler *transfermocks.MockPanicHandler
+ clientManager *transfermocks.MockClientManager
+ imapClientProvider *transfermocks.MockIMAPClientProvider
+ pmapiClient *pmapimocks.MockClient
+ pmapiConfig *pmapi.ClientConfig
keyring *crypto.KeyRing
}
@@ -46,12 +47,13 @@ func initMocks(t *testing.T) mocks {
m := mocks{
t: t,
- ctrl: mockCtrl,
- panicHandler: transfermocks.NewMockPanicHandler(mockCtrl),
- clientManager: transfermocks.NewMockClientManager(mockCtrl),
- pmapiClient: pmapimocks.NewMockClient(mockCtrl),
- pmapiConfig: &pmapi.ClientConfig{},
- keyring: newTestKeyring(),
+ ctrl: mockCtrl,
+ panicHandler: transfermocks.NewMockPanicHandler(mockCtrl),
+ clientManager: transfermocks.NewMockClientManager(mockCtrl),
+ imapClientProvider: transfermocks.NewMockIMAPClientProvider(mockCtrl),
+ pmapiClient: pmapimocks.NewMockClient(mockCtrl),
+ pmapiConfig: &pmapi.ClientConfig{},
+ keyring: newTestKeyring(),
}
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).AnyTimes()
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 5a6a0cf9..e1544e62 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -21,6 +21,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "runtime"
"github.com/ProtonMail/go-appdir"
"github.com/hashicorp/go-multierror"
@@ -247,3 +248,17 @@ func (c *Config) GetDefaultIMAPPort() int {
func (c *Config) GetDefaultSMTPPort() int {
return 1025
}
+
+// getAPIOS returns actual operating system.
+func (c *Config) getAPIOS() string {
+ switch os := runtime.GOOS; os {
+ case "darwin": // nolint: goconst
+ return "macOS"
+ case "linux":
+ return "Linux"
+ case "windows":
+ return "Windows"
+ }
+
+ return "Linux"
+}
diff --git a/pkg/config/pmapi_noprod.go b/pkg/config/pmapi_noprod.go
index 653d1afb..ec1c093d 100644
--- a/pkg/config/pmapi_noprod.go
+++ b/pkg/config/pmapi_noprod.go
@@ -29,7 +29,7 @@ import (
func (c *Config) GetAPIConfig() *pmapi.ClientConfig {
return &pmapi.ClientConfig{
- AppVersion: strings.Title(c.appName) + "_" + c.version,
+ AppVersion: c.getAPIOS() + strings.Title(c.appName) + "_" + c.version,
ClientID: c.appName,
}
}
diff --git a/pkg/config/pmapi_prod.go b/pkg/config/pmapi_prod.go
index 38cd22a6..fde1274c 100644
--- a/pkg/config/pmapi_prod.go
+++ b/pkg/config/pmapi_prod.go
@@ -31,7 +31,7 @@ import (
func (c *Config) GetAPIConfig() *pmapi.ClientConfig {
return &pmapi.ClientConfig{
- AppVersion: strings.Title(c.appName) + "_" + c.version,
+ AppVersion: c.getAPIOS() + strings.Title(c.appName) + "_" + c.version,
ClientID: c.appName,
Timeout: 25 * time.Minute, // Overall request timeout (~25MB / 25 mins => ~16kB/s, should be reasonable).
FirstReadTimeout: 30 * time.Second, // 30s to match 30s response header timeout.
diff --git a/pkg/pmapi/client.go b/pkg/pmapi/client.go
index 3e1c9cea..3f513b08 100644
--- a/pkg/pmapi/client.go
+++ b/pkg/pmapi/client.go
@@ -254,7 +254,7 @@ func (c *client) doBuffered(req *http.Request, bodyBuffer []byte, retryUnauthori
head += "\n"
}
c.log.Tracef("REQHEAD \n%s", head)
- c.log.Tracef("REQBODY '%s'", string(bodyBuffer))
+ c.log.Tracef("REQBODY '%s'", printBytes(bodyBuffer))
}
hasBody := len(bodyBuffer) > 0
diff --git a/pkg/pmapi/debug.go b/pkg/pmapi/debug.go
new file mode 100644
index 00000000..6f74bc59
--- /dev/null
+++ b/pkg/pmapi/debug.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2020 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 .
+
+package pmapi
+
+import "unicode/utf8"
+
+func printBytes(body []byte) string {
+ if utf8.Valid(body) {
+ return string(body)
+ }
+ enc := []rune{}
+ for _, b := range body {
+ switch {
+ case b == 9:
+ enc = append(enc, rune('⟼'))
+ case b == 13:
+ enc = append(enc, rune('↵'))
+ case b < 32, b == 127:
+ enc = append(enc, '◡')
+ case b > 31 && b < 127, b == 10:
+ enc = append(enc, rune(b))
+ default:
+ enc = append(enc, 9728+rune(b))
+ }
+ }
+
+ return string(enc)
+}
diff --git a/pkg/pmapi/keyring.go b/pkg/pmapi/keyring.go
index 978ac6e7..46d9dc1c 100644
--- a/pkg/pmapi/keyring.go
+++ b/pkg/pmapi/keyring.go
@@ -19,6 +19,7 @@ package pmapi
import (
"bytes"
+ "encoding/base64"
"encoding/json"
"io"
"io/ioutil"
@@ -289,3 +290,57 @@ func signAttachment(encrypter *crypto.KeyRing, data io.Reader) (signature io.Rea
}
return bytes.NewReader(sig.GetBinary()), nil
}
+
+func encryptAndEncodeSessionKeys(
+ pubkey *crypto.KeyRing,
+ bodyKey *crypto.SessionKey,
+ attkeys map[string]*crypto.SessionKey,
+) (bodyPacket string, attachmentPackets map[string]string, err error) {
+ // Encrypt message body keys.
+ packetBytes, err := pubkey.EncryptSessionKey(bodyKey)
+ if err != nil {
+ return
+ }
+ bodyPacket = base64.StdEncoding.EncodeToString(packetBytes)
+
+ // Encrypt attachment keys.
+ attachmentPackets = make(map[string]string)
+ for id, attkey := range attkeys {
+ var packets []byte
+ if packets, err = pubkey.EncryptSessionKey(attkey); err != nil {
+ return
+ }
+ attachmentPackets[id] = base64.StdEncoding.EncodeToString(packets)
+ }
+ return
+}
+
+func encryptSymmDecryptKey(
+ kr *crypto.KeyRing,
+ textToEncrypt string,
+) (decryptedKey *crypto.SessionKey, symEncryptedData []byte, err error) {
+ // We use only primary key to encrypt the message. Our keyring contains all keys (primary, old and deacivated ones).
+ firstKey, err := kr.FirstKey()
+ if err != nil {
+ return
+ }
+
+ pgpMessage, err := firstKey.Encrypt(crypto.NewPlainMessageFromString(textToEncrypt), kr)
+ if err != nil {
+ return
+ }
+
+ pgpSplitMessage, err := pgpMessage.SeparateKeyAndData(len(textToEncrypt), 0)
+ if err != nil {
+ return
+ }
+
+ decryptedKey, err = kr.DecryptSessionKey(pgpSplitMessage.GetBinaryKeyPacket())
+ if err != nil {
+ return
+ }
+
+ symEncryptedData = pgpSplitMessage.GetBinaryDataPacket()
+
+ return
+}
diff --git a/pkg/pmapi/message_send.go b/pkg/pmapi/message_send.go
new file mode 100644
index 00000000..ec4140eb
--- /dev/null
+++ b/pkg/pmapi/message_send.go
@@ -0,0 +1,367 @@
+// Copyright (c) 2020 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 .
+
+package pmapi
+
+import (
+ "encoding/base64"
+ "errors"
+
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
+)
+
+// Draft actions
+const (
+ DraftActionReply = 0
+ DraftActionReplyAll = 1
+ DraftActionForward = 2
+)
+
+// PackageFlag for send message package types
+type PackageFlag int
+
+func (p *PackageFlag) Has(flag PackageFlag) bool { return iHasFlag(int(*p), int(flag)) }
+func (p *PackageFlag) HasAtLeastOne(flag PackageFlag) bool {
+ return iHasAtLeastOneFlag(int(*p), int(flag))
+}
+func (p *PackageFlag) Is(flag PackageFlag) bool { return iIsFlag(int(*p), int(flag)) }
+func (p *PackageFlag) HasNo(flag PackageFlag) bool { return iHasNoneOfFlag(int(*p), int(flag)) }
+
+// Send message package types.
+const (
+ InternalPackage = PackageFlag(1)
+ EncryptedOutsidePackage = PackageFlag(2)
+ ClearPackage = PackageFlag(4)
+ PGPInlinePackage = PackageFlag(8)
+ PGPMIMEPackage = PackageFlag(16)
+ ClearMIMEPackage = PackageFlag(32)
+)
+
+// SignatureFlag for send signature types.
+type SignatureFlag int
+
+func (p *SignatureFlag) Is(flag SignatureFlag) bool { return iIsFlag(int(*p), int(flag)) }
+func (p *SignatureFlag) Has(flag SignatureFlag) bool { return iHasFlag(int(*p), int(flag)) }
+func (p *SignatureFlag) HasNo(flag SignatureFlag) bool { return iHasNoneOfFlag(int(*p), int(flag)) }
+
+// Send signature types.
+const (
+ SignatureNone = SignatureFlag(0)
+ SignatureDetached = SignatureFlag(1)
+ SignatureAttachedArmored = SignatureFlag(2)
+)
+
+// DraftReq defines paylod for creating drafts
+type DraftReq struct {
+ Message *Message
+ ParentID string `json:",omitempty"`
+ Action int
+ AttachmentKeyPackets []string
+}
+
+func (c *client) CreateDraft(m *Message, parent string, action int) (created *Message, err error) {
+ createReq := &DraftReq{Message: m, ParentID: parent, Action: action, AttachmentKeyPackets: []string{}}
+
+ req, err := c.NewJSONRequest("POST", "/mail/v4/messages", createReq)
+ if err != nil {
+ return
+ }
+
+ var res MessageRes
+ if err = c.DoJSON(req, &res); err != nil {
+ return
+ }
+
+ created, err = res.Message, res.Err()
+ return
+}
+
+type AlgoKey struct {
+ Key string
+ Algorithm string
+}
+
+type MessageAddress struct {
+ Type PackageFlag
+ EncryptedBodyKeyPacket string `json:"BodyKeyPacket"` // base64-encoded key packet.
+ Signature SignatureFlag
+ EncryptedAttachmentKeyPackets map[string]string `json:"AttachmentKeyPackets"`
+}
+
+type MessagePackage struct {
+ Addresses map[string]*MessageAddress
+ Type PackageFlag
+ MIMEType string
+ EncryptedBody string `json:"Body"` // base64-encoded encrypted data packet.
+ DecryptedBodyKey AlgoKey `json:"BodyKey"` // base64-encoded session key (only if cleartext recipients).
+ DecryptedAttachmentKeys map[string]AlgoKey `json:"AttachmentKeys"` // Only include if cleartext & attachments.
+}
+
+func newMessagePackage(
+ send sendData,
+ attKeys map[string]AlgoKey,
+) (pkg *MessagePackage) {
+ pkg = &MessagePackage{
+ EncryptedBody: base64.StdEncoding.EncodeToString(send.ciphertext),
+ Addresses: send.addressMap,
+ MIMEType: send.contentType,
+ Type: send.sharedScheme,
+ }
+
+ if send.sharedScheme.HasAtLeastOne(ClearPackage | ClearMIMEPackage) {
+ pkg.DecryptedBodyKey.Key = send.decryptedBodyKey.GetBase64Key()
+ pkg.DecryptedBodyKey.Algorithm = send.decryptedBodyKey.Algo
+ }
+
+ if len(attKeys) != 0 && send.sharedScheme.Has(ClearPackage) {
+ pkg.DecryptedAttachmentKeys = attKeys
+ }
+
+ return pkg
+}
+
+type sendData struct {
+ decryptedBodyKey *crypto.SessionKey //body session key
+ addressMap map[string]*MessageAddress
+ sharedScheme PackageFlag
+ ciphertext []byte
+ cleartext string
+ contentType string
+}
+
+type SendMessageReq struct {
+ ExpirationTime int64 `json:",omitempty"`
+ // AutoSaveContacts int `json:",omitempty"`
+
+ // Data for encrypted recipients.
+ Packages []*MessagePackage
+
+ mime, plain, rich sendData
+ attKeys map[string]*crypto.SessionKey
+ kr *crypto.KeyRing
+}
+
+func NewSendMessageReq(
+ kr *crypto.KeyRing,
+ mimeBody, plainBody, richBody string,
+ attKeys map[string]*crypto.SessionKey,
+) *SendMessageReq {
+ req := &SendMessageReq{}
+
+ req.mime.addressMap = make(map[string]*MessageAddress)
+ req.plain.addressMap = make(map[string]*MessageAddress)
+ req.rich.addressMap = make(map[string]*MessageAddress)
+
+ req.mime.cleartext = mimeBody
+ req.plain.cleartext = plainBody
+ req.rich.cleartext = richBody
+
+ req.attKeys = attKeys
+ req.kr = kr
+
+ return req
+}
+
+var (
+ errUnknownContentType = errors.New("unknown content type")
+ errMultipartInNonMIME = errors.New("multipart mixed not allowed in this scheme")
+ errAttSignNotSupported = errors.New("attached signature not supported")
+ errEncryptMustSign = errors.New("encrypted package must be signed")
+ errEncryptedOutsideNotSupported = errors.New("encrypted outside is not supported")
+ errWrongSendScheme = errors.New("wrong send scheme")
+ errInternalMustEncrypt = errors.New("internal package must be encrypted")
+ errInlineMustBePlain = errors.New("PGP Inline package must be plain text")
+ errMissingPubkey = errors.New("cannot encrypt body key packet: missing pubkey")
+ errClearSignMustNotBeHTML = errors.New("clear signed packet must be multipart or plain")
+ errMIMEMustBeMultipart = errors.New("MIME packet must be multipart")
+ errClearMIMEMustSign = errors.New("clear MIME must be signed")
+ errClearSignMustNotBePGPInline = errors.New("clear sign must not be PGP inline")
+)
+
+func (req *SendMessageReq) AddRecipient(
+ email string, sendScheme PackageFlag,
+ pubkey *crypto.KeyRing, signature SignatureFlag,
+ contentType string, doEncrypt bool,
+) (err error) {
+ if signature.Has(SignatureAttachedArmored) {
+ return errAttSignNotSupported
+ }
+
+ if doEncrypt && signature.HasNo(SignatureDetached) {
+ return errEncryptMustSign
+ }
+
+ switch sendScheme {
+ case PGPMIMEPackage, ClearMIMEPackage:
+ if contentType != ContentTypeMultipartMixed {
+ return errMIMEMustBeMultipart
+ }
+ return req.addMIMERecipient(email, sendScheme, pubkey, signature)
+ case InternalPackage, ClearPackage, PGPInlinePackage:
+ if contentType == ContentTypeMultipartMixed {
+ return errMultipartInNonMIME
+ }
+ return req.addNonMIMERecipient(email, sendScheme, pubkey, signature, contentType, doEncrypt)
+ case EncryptedOutsidePackage:
+ return errEncryptedOutsideNotSupported
+ default:
+ return errWrongSendScheme
+ }
+}
+
+func (req *SendMessageReq) addNonMIMERecipient(
+ email string, sendScheme PackageFlag,
+ pubkey *crypto.KeyRing, signature SignatureFlag,
+ contentType string, doEncrypt bool,
+) (err error) {
+ if signature.Is(SignatureDetached) && !doEncrypt {
+ if sendScheme.Is(PGPInlinePackage) {
+ return errClearSignMustNotBePGPInline
+ }
+ if sendScheme.Is(ClearPackage) && contentType == ContentTypeHTML {
+ return errClearSignMustNotBeHTML
+ }
+ }
+
+ var send *sendData
+
+ switch contentType {
+ case ContentTypePlainText:
+ send = &req.plain
+ send.contentType = ContentTypePlainText
+ case ContentTypeHTML, "":
+ send = &req.rich
+ send.contentType = ContentTypeHTML
+ case ContentTypeMultipartMixed:
+ return errMultipartInNonMIME
+ default:
+ return errUnknownContentType
+ }
+
+ if send.decryptedBodyKey == nil {
+ if send.decryptedBodyKey, send.ciphertext, err = encryptSymmDecryptKey(req.kr, send.cleartext); err != nil {
+ return err
+ }
+ }
+ newAddress := &MessageAddress{Type: sendScheme, Signature: signature}
+
+ if sendScheme.Is(PGPInlinePackage) && contentType == ContentTypeHTML {
+ return errInlineMustBePlain
+ }
+ if sendScheme.Is(InternalPackage) && !doEncrypt {
+ return errInternalMustEncrypt
+ }
+ if doEncrypt && pubkey == nil {
+ return errMissingPubkey
+ }
+
+ if doEncrypt {
+ newAddress.EncryptedBodyKeyPacket, newAddress.EncryptedAttachmentKeyPackets, err = encryptAndEncodeSessionKeys(pubkey, send.decryptedBodyKey, req.attKeys)
+ if err != nil {
+ return err
+ }
+ }
+ send.addressMap[email] = newAddress
+ send.sharedScheme |= sendScheme
+
+ return nil
+}
+
+func (req *SendMessageReq) addMIMERecipient(
+ email string, sendScheme PackageFlag,
+ pubkey *crypto.KeyRing, signature SignatureFlag,
+) (err error) {
+ if sendScheme.Is(ClearMIMEPackage) && signature.HasNo(SignatureDetached) {
+ return errClearMIMEMustSign
+ }
+
+ req.mime.contentType = ContentTypeMultipartMixed
+ if req.mime.decryptedBodyKey == nil {
+ if req.mime.decryptedBodyKey, req.mime.ciphertext, err = encryptSymmDecryptKey(req.kr, req.mime.cleartext); err != nil {
+ return err
+ }
+ }
+
+ if sendScheme.Is(PGPMIMEPackage) {
+ if pubkey == nil {
+ return errMissingPubkey
+ }
+ // Attachment keys are not needed because attachments are part
+ // of MIME body and therefore attachments are encrypted with
+ // body session key.
+ mimeBodyPacket, _, err := encryptAndEncodeSessionKeys(pubkey, req.mime.decryptedBodyKey, map[string]*crypto.SessionKey{})
+ if err != nil {
+ return err
+ }
+ req.mime.addressMap[email] = &MessageAddress{Type: sendScheme, EncryptedBodyKeyPacket: mimeBodyPacket, Signature: signature}
+ } else {
+ req.mime.addressMap[email] = &MessageAddress{Type: sendScheme, Signature: signature}
+ }
+ req.mime.sharedScheme |= sendScheme
+
+ return nil
+}
+
+func (req *SendMessageReq) PreparePackages() {
+ attkeysEncoded := make(map[string]AlgoKey)
+ for attID, attkey := range req.attKeys {
+ attkeysEncoded[attID] = AlgoKey{
+ Key: attkey.GetBase64Key(),
+ Algorithm: attkey.Algo,
+ }
+ }
+
+ for _, send := range []sendData{req.mime, req.plain, req.rich} {
+ if len(send.addressMap) == 0 {
+ continue
+ }
+ req.Packages = append(req.Packages, newMessagePackage(send, attkeysEncoded))
+ }
+}
+
+type SendMessageRes struct {
+ Res
+
+ Sent *Message
+
+ // Parent is only present if the sent message has a parent (reply/reply all/forward).
+ Parent *Message
+}
+
+func (c *client) SendMessage(id string, sendReq *SendMessageReq) (sent, parent *Message, err error) {
+ if id == "" {
+ err = errors.New("pmapi: cannot send message with an empty id")
+ return
+ }
+
+ if sendReq.Packages == nil {
+ sendReq.Packages = []*MessagePackage{}
+ }
+
+ req, err := c.NewJSONRequest("POST", "/mail/v4/messages/"+id, sendReq)
+ if err != nil {
+ return
+ }
+
+ var res SendMessageRes
+ if err = c.DoJSON(req, &res); err != nil {
+ return
+ }
+
+ sent, parent, err = res.Sent, res.Parent, res.Err()
+ return
+}
diff --git a/pkg/pmapi/message_send_test.go b/pkg/pmapi/message_send_test.go
new file mode 100644
index 00000000..8739234f
--- /dev/null
+++ b/pkg/pmapi/message_send_test.go
@@ -0,0 +1,617 @@
+// Copyright (c) 2020 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 .
+
+package pmapi
+
+import (
+ "encoding/base64"
+ "testing"
+
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
+ "github.com/stretchr/testify/require"
+)
+
+type recipient struct {
+ email string
+ sendScheme PackageFlag
+ pubkey *crypto.KeyRing
+ signature SignatureFlag
+ contentType string
+ doEncrypt bool
+ wantError error
+}
+
+type testData struct {
+ emails []string
+ recipients []recipient
+ wantPackages []*MessagePackage
+
+ allRecipients map[string]recipient
+ allAddresses map[string]*MessageAddress
+
+ attKeys map[string]*crypto.SessionKey
+ mimeBody, plainBody, richBody string
+}
+
+func (td *testData) addRecipients(t testing.TB) {
+ for _, email := range td.emails {
+ rcp, ok := td.allRecipients[email]
+ require.True(t, ok, "missing recipient %s", email)
+ rcp.email = email
+ td.recipients = append(td.recipients, rcp)
+ }
+}
+
+func (td *testData) addAddresses(t testing.TB) {
+ for i, wantPackage := range td.wantPackages {
+ for email := range wantPackage.Addresses {
+ address, ok := td.allAddresses[email]
+ require.True(t, ok, "missing address %s", email)
+ td.wantPackages[i].Addresses[email] = address
+ }
+ }
+}
+
+func (td *testData) prepareAndCheck(t *testing.T) {
+ r := require.New(t)
+
+ matchPresence := func(want string) require.ValueAssertionFunc {
+ if len(want) == 0 {
+ return require.Empty
+ }
+ return require.NotEmpty
+ }
+
+ have := NewSendMessageReq(testPrivateKeyRing, td.mimeBody, td.plainBody, td.richBody, td.attKeys)
+ for _, rec := range td.recipients {
+ err := have.AddRecipient(rec.email, rec.sendScheme, rec.pubkey, rec.signature, rec.contentType, rec.doEncrypt)
+
+ if rec.wantError == nil {
+ r.NoError(err, "email %s", rec.email)
+ } else {
+ r.EqualError(err, rec.wantError.Error(), "email %s", rec.email)
+ }
+ }
+ have.PreparePackages()
+
+ r.Equal(len(td.wantPackages), len(have.Packages))
+
+ for i, wantPackage := range td.wantPackages {
+ havePackage := have.Packages[i]
+
+ r.Equal(wantPackage.MIMEType, havePackage.MIMEType, "pkg %d", i)
+ r.Equal(wantPackage.Type, havePackage.Type, "pkg %d", i)
+
+ r.Equal(len(wantPackage.Addresses), len(havePackage.Addresses), "pkg %d", i)
+ for email, wantAddress := range wantPackage.Addresses {
+ haveAddress, ok := havePackage.Addresses[email]
+ r.True(ok, "pkg %d email %s", i, email)
+
+ r.Equal(wantAddress.Type, haveAddress.Type, "pkg %d email %s", i, email)
+ matchPresence(wantAddress.EncryptedBodyKeyPacket)(t, haveAddress.EncryptedBodyKeyPacket, "pkg %d email %s", i, email)
+ r.Equal(wantAddress.Signature, haveAddress.Signature, "pkg %d email %s", i, email)
+
+ if len(td.attKeys) == 0 {
+ r.Len(haveAddress.EncryptedAttachmentKeyPackets, 0)
+ } else {
+ r.Equal(
+ len(wantAddress.EncryptedAttachmentKeyPackets),
+ len(haveAddress.EncryptedAttachmentKeyPackets),
+ "pkg %d email %s", i, email,
+ )
+ for attID, wantAttKey := range wantAddress.EncryptedAttachmentKeyPackets {
+ haveAttKey, ok := haveAddress.EncryptedAttachmentKeyPackets[attID]
+ r.True(ok, "pkg %d email %s att %s", i, email, attID)
+ matchPresence(wantAttKey)(t, haveAttKey, "pkg %d email %s att %s", i, email, attID)
+ }
+ }
+ }
+
+ matchPresence(wantPackage.EncryptedBody)(t, havePackage.EncryptedBody, "pkg %d", i)
+
+ wantBodyKey := wantPackage.DecryptedBodyKey
+ haveBodyKey := havePackage.DecryptedBodyKey
+
+ matchPresence(wantBodyKey.Algorithm)(t, haveBodyKey.Algorithm, "pkg %d", i)
+ matchPresence(wantBodyKey.Key)(t, haveBodyKey.Key, "pkg %d", i)
+
+ if len(td.attKeys) == 0 {
+ r.Len(havePackage.DecryptedAttachmentKeys, 0)
+ } else {
+ r.Equal(
+ len(wantPackage.DecryptedAttachmentKeys),
+ len(havePackage.DecryptedAttachmentKeys),
+ "pkg %d", i,
+ )
+ for attID, wantAttKey := range wantPackage.DecryptedAttachmentKeys {
+ haveAttKey, ok := havePackage.DecryptedAttachmentKeys[attID]
+ r.True(ok, "pkg %d att %s", i, attID)
+ matchPresence(wantAttKey.Key)(t, haveAttKey.Key, "pkg %d att %s", i, attID)
+ matchPresence(wantAttKey.Algorithm)(t, haveAttKey.Algorithm, "pkg %d att %s", i, attID)
+ }
+ }
+ }
+}
+
+func TestSendReq(t *testing.T) {
+ attKeyB64 := "EvjO/2RIJNn6HdoU6ACqFdZglzJhpjQ/PpjsvL3mB5Q="
+ token, err := base64.StdEncoding.DecodeString(attKeyB64)
+ require.NoError(t, err)
+
+ attKey := crypto.NewSessionKeyFromToken(token, "aes256")
+ attKeyPackets := map[string]string{"attID": "not-empty"}
+ attAlgoKeys := map[string]AlgoKey{"attID": {"not-empty", "not-empty"}}
+
+ allRecipients := map[string]recipient{
+ // Internal OK
+ "none@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, "", true, nil},
+ "html@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, nil},
+ "plain@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ // Internal bad
+ "wrongtype@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, "application/rfc822", true, errUnknownContentType},
+ "multipart@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, errMultipartInNonMIME},
+ "noencrypt@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, false, errInternalMustEncrypt},
+ "no-pubkey@pm.me": {"", InternalPackage, nil, SignatureDetached, ContentTypeHTML, true, errMissingPubkey},
+ "nosigning@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureNone, ContentTypeHTML, true, errEncryptMustSign},
+ // testing combination
+ "internal1@pm.me": {"", InternalPackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ // Clear OK
+ "html@email.com": {"", ClearPackage, nil, SignatureNone, ContentTypeHTML, false, nil},
+ "none@email.com": {"", ClearPackage, nil, SignatureNone, "", false, nil},
+ "plain@email.com": {"", ClearPackage, nil, SignatureNone, ContentTypePlainText, false, nil},
+ "plain-sign@email.com": {"", ClearPackage, nil, SignatureDetached, ContentTypePlainText, false, nil},
+ "mime-sign@email.com": {"", ClearMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, false, nil},
+ // Clear bad
+ "mime@email.com": {"", ClearMIMEPackage, nil, SignatureNone, ContentTypeMultipartMixed, false, errClearMIMEMustSign},
+ "clear-plain-sign@email.com": {"", PGPInlinePackage, nil, SignatureDetached, ContentTypePlainText, false, errClearSignMustNotBePGPInline},
+ "html-sign@email.com": {"", ClearPackage, nil, SignatureDetached, ContentTypeHTML, false, errClearSignMustNotBeHTML},
+ "mime-plain@email.com": {"", ClearMIMEPackage, nil, SignatureDetached, ContentTypePlainText, false, errMIMEMustBeMultipart},
+ "mime-html@email.com": {"", ClearMIMEPackage, nil, SignatureDetached, ContentTypeHTML, false, errMIMEMustBeMultipart},
+ // External Encryption OK
+ "mime@gpg.com": {"", PGPMIMEPackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, nil},
+ "plain@gpg.com": {"", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypePlainText, true, nil},
+ // External Encryption bad
+ "eo@gpg.com": {"", EncryptedOutsidePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, errEncryptedOutsideNotSupported},
+ "inline-html@gpg.com": {"", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeHTML, true, errInlineMustBePlain},
+ "inline-mixed@gpg.com": {"", PGPInlinePackage, testPublicKeyRing, SignatureDetached, ContentTypeMultipartMixed, true, errMultipartInNonMIME},
+ "mime-plain@gpg.com": {"", PGPMIMEPackage, nil, SignatureDetached, ContentTypePlainText, true, errMIMEMustBeMultipart},
+ "mime-html@sgpg.com": {"", PGPMIMEPackage, nil, SignatureDetached, ContentTypeHTML, true, errMIMEMustBeMultipart},
+ "no-pubkey@gpg.com": {"", PGPMIMEPackage, nil, SignatureDetached, ContentTypeMultipartMixed, true, errMissingPubkey},
+ "not-signed@gpg.com": {"", PGPMIMEPackage, testPublicKeyRing, SignatureNone, ContentTypeMultipartMixed, true, errEncryptMustSign},
+ }
+
+ allAddresses := map[string]*MessageAddress{
+ "none@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "not-empty",
+ EncryptedAttachmentKeyPackets: attKeyPackets,
+ },
+ "plain@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "not-empty",
+ EncryptedAttachmentKeyPackets: attKeyPackets,
+ },
+ "html@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "not-empty",
+ EncryptedAttachmentKeyPackets: attKeyPackets,
+ },
+ "internal1@pm.me": {
+ Type: InternalPackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "not-empty",
+ EncryptedAttachmentKeyPackets: attKeyPackets,
+ },
+
+ "html@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "none@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "plain@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureNone,
+ },
+ "plain-sign@email.com": {
+ Type: ClearPackage,
+ Signature: SignatureDetached,
+ },
+ "mime-sign@email.com": {
+ Type: ClearMIMEPackage,
+ Signature: SignatureDetached,
+ },
+
+ "mime@gpg.com": {
+ Type: PGPMIMEPackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "non-empty",
+ },
+ "plain@gpg.com": {
+ Type: PGPInlinePackage,
+ Signature: SignatureDetached,
+ EncryptedBodyKeyPacket: "non-empty",
+ EncryptedAttachmentKeyPackets: attKeyPackets,
+ },
+ }
+
+ // NOTE naming
+ // Single: there should be one package
+ // Multiple: there should be more than one package
+ // Internal: there should be internal package
+ // Clear: there should be non-encrypted package
+ // Encrypted: there should be encrypted package
+ // NotAllowed: combination of inputs which are not allowed
+ newTests := map[string]testData{
+ "Nothing": { // expect no crash
+ emails: []string{},
+ wantPackages: []*MessagePackage{},
+ },
+ "Fails": {
+ emails: []string{
+ "wrongtype@pm.me",
+ "multipart@pm.me",
+ "noencrypt@pm.me",
+ "no-pubkey@pm.me",
+ "nosigning@pm.me",
+
+ "html-sign@email.com",
+ "mime-plain@email.com",
+ "mime-html@email.com",
+ "mime@email.com",
+ "clear-plain-sign@email.com",
+
+ "eo@gpg.com",
+ "inline-html@gpg.com",
+ "inline-mixed@gpg.com",
+ "mime-plain@gpg.com",
+ "mime-html@sgpg.com",
+ "no-pubkey@gpg.com",
+ "not-signed@gpg.com",
+ },
+ },
+
+ // one scheme in one package
+ "SingleInternalHTML": {
+ emails: []string{"none@pm.me", "html@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "none@pm.me": nil,
+ "html@pm.me": nil,
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+ "SingleInternalPlain": {
+ emails: []string{"plain@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": nil,
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+
+ "SingleClearHTML": {
+ emails: []string{"none@email.com", "html@email.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@email.com": nil,
+ "none@email.com": nil,
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearPlain": {
+ emails: []string{"plain@email.com", "plain-sign@email.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@email.com": nil,
+ "plain-sign@email.com": nil,
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearMIME": {
+ emails: []string{"mime-sign@email.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime-sign@email.com": nil,
+ },
+ Type: ClearMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+
+ "SingleEncyptedPlain": {
+ emails: []string{"plain@gpg.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@gpg.com": nil,
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+ "SingleEncyptedMIME": {
+ emails: []string{"mime@gpg.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": nil,
+ },
+ Type: PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+
+ // two schemes combined to one package
+ "SingleClearInternalPlain": {
+ emails: []string{"plain@email.com", "plain-sign@email.com", "plain@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": nil,
+ "plain@email.com": nil,
+ "plain-sign@email.com": nil,
+ },
+ Type: InternalPackage | ClearPackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleClearInternalHTML": {
+ emails: []string{"none@email.com", "html@email.com", "html@pm.me", "none@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "none@pm.me": nil,
+ "html@pm.me": nil,
+ "html@email.com": nil,
+ "none@email.com": nil,
+ },
+ Type: InternalPackage | ClearPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "SingleEncryptedInternalPlain": {
+ emails: []string{"plain@gpg.com", "plain@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": nil,
+ "plain@gpg.com": nil,
+ },
+ Type: InternalPackage | PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+ "SingleEncryptedClearMIME": {
+ emails: []string{"mime@gpg.com", "mime-sign@email.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": nil,
+ "mime-sign@email.com": nil,
+ },
+ Type: ClearMIMEPackage | PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ },
+ },
+
+ // one scheme separated to multiple packages
+ "MultipleInternal": {
+ emails: []string{"none@pm.me", "html@pm.me", "plain@pm.me"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@pm.me": nil,
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "none@pm.me": nil,
+ "html@pm.me": nil,
+ },
+ Type: InternalPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+ "MultipleClear": {
+ emails: []string{
+ "none@email.com", "html@email.com",
+ "plain@email.com", "plain-sign@email.com",
+ "mime-sign@email.com",
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime-sign@email.com": nil,
+ },
+ Type: ClearMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@email.com": nil,
+ "plain-sign@email.com": nil,
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "html@email.com": nil,
+ "none@email.com": nil,
+ },
+ Type: ClearPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ "MultipleEncrypted": {
+ emails: []string{"plain@gpg.com", "mime@gpg.com"},
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": nil,
+ },
+ Type: PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@gpg.com": nil,
+ },
+ Type: PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ },
+ },
+ },
+
+ "MultipleComboAll": {
+ emails: []string{
+ "none@pm.me",
+ "plain@pm.me",
+ "html@pm.me",
+
+ "none@email.com",
+ "html@email.com",
+ "plain@email.com",
+ "plain-sign@email.com",
+ "mime-sign@email.com",
+
+ "mime@gpg.com",
+ "plain@gpg.com",
+ },
+ wantPackages: []*MessagePackage{
+ {
+ Addresses: map[string]*MessageAddress{
+ "mime@gpg.com": nil,
+ "mime-sign@email.com": nil,
+ },
+ Type: ClearMIMEPackage | PGPMIMEPackage,
+ MIMEType: ContentTypeMultipartMixed,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "plain@gpg.com": nil,
+ "plain@email.com": nil,
+ "plain-sign@email.com": nil,
+ "plain@pm.me": nil,
+ },
+ Type: InternalPackage | ClearPackage | PGPInlinePackage,
+ MIMEType: ContentTypePlainText,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ {
+ Addresses: map[string]*MessageAddress{
+ "none@pm.me": nil,
+ "html@pm.me": nil,
+ "none@email.com": nil,
+ "html@email.com": nil,
+ },
+ Type: InternalPackage | ClearPackage,
+ MIMEType: ContentTypeHTML,
+ EncryptedBody: "non-empty",
+ DecryptedBodyKey: AlgoKey{"non-empty", "non-empty"},
+ DecryptedAttachmentKeys: attAlgoKeys,
+ },
+ },
+ },
+ }
+
+ for name, test := range newTests {
+ test.mimeBody = "Mime body"
+ test.plainBody = "Plain body"
+ test.richBody = "HTML body"
+ test.allRecipients = allRecipients
+ test.allAddresses = allAddresses
+
+ test.addRecipients(t)
+ test.addAddresses(t)
+
+ t.Run("NoAtt"+name, test.prepareAndCheck)
+ test.attKeys = map[string]*crypto.SessionKey{"attID": attKey}
+ t.Run("Att"+name, test.prepareAndCheck)
+ }
+}
diff --git a/pkg/pmapi/messages.go b/pkg/pmapi/messages.go
index 623bd07b..32ee60d2 100644
--- a/pkg/pmapi/messages.go
+++ b/pkg/pmapi/messages.go
@@ -569,114 +569,6 @@ func (c *client) GetMessage(id string) (msg *Message, err error) {
return res.Message, res.Err()
}
-type SendMessageReq struct {
- ExpirationTime int64 `json:",omitempty"`
- // AutoSaveContacts int `json:",omitempty"`
-
- // Data for encrypted recipients.
- Packages []*MessagePackage
-}
-
-// Message package types.
-const (
- InternalPackage = 1
- EncryptedOutsidePackage = 2
- ClearPackage = 4
- PGPInlinePackage = 8
- PGPMIMEPackage = 16
- ClearMIMEPackage = 32
-)
-
-// Signature types.
-const (
- NoSignature = 0
- YesSignature = 1
-)
-
-type MessagePackage struct {
- Addresses map[string]*MessageAddress
- Type int
- MIMEType string
- Body string // base64-encoded encrypted data packet.
- BodyKey AlgoKey // base64-encoded session key (only if cleartext recipients).
- AttachmentKeys map[string]AlgoKey // Only include if cleartext & attachments.
-}
-
-type MessageAddress struct {
- Type int
- BodyKeyPacket string // base64-encoded key packet.
- Signature int // 0 = None, 1 = Detached, 2 = Attached/Armored
- AttachmentKeyPackets map[string]string
-}
-
-type AlgoKey struct {
- Key string
- Algorithm string
-}
-
-type SendMessageRes struct {
- Res
-
- Sent *Message
-
- // Parent is only present if the sent message has a parent (reply/reply all/forward).
- Parent *Message
-}
-
-func (c *client) SendMessage(id string, sendReq *SendMessageReq) (sent, parent *Message, err error) {
- if id == "" {
- err = errors.New("pmapi: cannot send message with an empty id")
- return
- }
-
- if sendReq.Packages == nil {
- sendReq.Packages = []*MessagePackage{}
- }
-
- req, err := c.NewJSONRequest("POST", "/mail/v4/messages/"+id, sendReq)
- if err != nil {
- return
- }
-
- var res SendMessageRes
- if err = c.DoJSON(req, &res); err != nil {
- return
- }
-
- sent, parent, err = res.Sent, res.Parent, res.Err()
- return
-}
-
-const (
- DraftActionReply = 0
- DraftActionReplyAll = 1
- DraftActionForward = 2
-)
-
-type DraftReq struct {
- Message *Message
- ParentID string `json:",omitempty"`
- Action int
- AttachmentKeyPackets []string
-}
-
-func (c *client) CreateDraft(m *Message, parent string, action int) (created *Message, err error) {
- createReq := &DraftReq{Message: m, ParentID: parent, Action: action, AttachmentKeyPackets: []string{}}
-
- req, err := c.NewJSONRequest("POST", "/mail/v4/messages", createReq)
- if err != nil {
- return
- }
-
- var res MessageRes
- if err = c.DoJSON(req, &res); err != nil {
- return
- }
-
- created, err = res.Message, res.Err()
- return
-}
-
type MessagesActionReq struct {
IDs []string
}
diff --git a/pkg/pmapi/settings.go b/pkg/pmapi/settings.go
index dca1097d..0ab9c30f 100644
--- a/pkg/pmapi/settings.go
+++ b/pkg/pmapi/settings.go
@@ -85,7 +85,7 @@ type MailSettings struct {
RightToLeft int
AttachPublicKey int
Sign int
- PGPScheme int
+ PGPScheme PackageFlag
PromptPin int
Autocrypt int
NumMessagePerPage int
diff --git a/pkg/pmapi/users.go b/pkg/pmapi/users.go
index b5c86e09..4d723ddb 100644
--- a/pkg/pmapi/users.go
+++ b/pkg/pmapi/users.go
@@ -18,7 +18,7 @@
package pmapi
import (
- "github.com/getsentry/raven-go"
+ "github.com/getsentry/sentry-go"
"github.com/pkg/errors"
)
@@ -119,7 +119,11 @@ func (c *client) UpdateUser() (user *User, err error) {
}
c.user = user
- raven.SetUserContext(&raven.User{ID: user.ID})
+ sentry.ConfigureScope(func(scope *sentry.Scope) {
+ scope.SetUser(sentry.User{
+ ID: user.ID,
+ })
+ })
var tmpList AddressList
if tmpList, err = c.GetAddresses(); err == nil {
diff --git a/pkg/pmapi/utils.go b/pkg/pmapi/utils.go
new file mode 100644
index 00000000..dfcc0e00
--- /dev/null
+++ b/pkg/pmapi/utils.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2020 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 .
+
+package pmapi
+
+func iHasFlag(i, flag int) bool { return i&flag == flag }
+func iHasAtLeastOneFlag(i, flag int) bool { return i&flag > 0 }
+func iIsFlag(i, flag int) bool { return i == flag }
+func iHasNoneOfFlag(i, flag int) bool { return !iHasAtLeastOneFlag(i, flag) }
diff --git a/pkg/sentry/report.go b/pkg/sentry/report.go
index a5bc478e..cd967584 100644
--- a/pkg/sentry/report.go
+++ b/pkg/sentry/report.go
@@ -18,135 +18,15 @@
package sentry
import (
- "fmt"
- "regexp"
+ "errors"
"runtime"
- "runtime/pprof"
- "strconv"
- "strings"
+ "time"
- "github.com/getsentry/raven-go"
+ "github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus"
)
-const fileParseError = "[file parse error]"
-
-var isGoroutine = regexp.MustCompile("^goroutine [[:digit:]]+.*") //nolint[gochecknoglobals]
-
-// Threads implements standard sentry thread report.
-type Threads struct {
- Values []Thread `json:"values"`
-}
-
-// Class specifier.
-func (s *Threads) Class() string { return "threads" }
-
-// Thread wraps a single stacktrace.
-type Thread struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Crashed bool `json:"crashed"`
- Stacktrace *raven.Stacktrace `json:"stacktrace"`
-}
-
-// TraceAllRoutines traces all goroutines and saves them to the current object.
-func (s *Threads) TraceAllRoutines() {
- s.Values = []Thread{}
- goroutines := &strings.Builder{}
- _ = pprof.Lookup("goroutine").WriteTo(goroutines, 2)
-
- thread := Thread{ID: -1}
- var frame *raven.StacktraceFrame
- for _, v := range strings.Split(goroutines.String(), "\n") {
- // Ignore empty lines.
- if v == "" {
- continue
- }
-
- // New routine.
- if isGoroutine.MatchString(v) {
- if thread.ID >= 0 {
- s.Values = append(s.Values, thread)
- }
- thread = Thread{ID: thread.ID + 1, Name: v, Crashed: thread.ID == -1, Stacktrace: &raven.Stacktrace{Frames: []*raven.StacktraceFrame{}}}
- continue
- }
-
- // New function.
- if frame == nil {
- frame = &raven.StacktraceFrame{Function: v}
- continue
- }
-
- // Set filename and add frame.
- if frame.Filename == "" {
- fld := strings.Fields(v)
- if len(fld) != 2 {
- frame.Filename = fileParseError
- frame.AbsolutePath = v
- } else {
- frame.Filename = fld[0]
- sp := strings.Split(fld[0], ":")
- if len(sp) > 1 {
- i, err := strconv.Atoi(sp[len(sp)-1])
- if err == nil {
- frame.Filename = strings.Join(sp[:len(sp)-1], ":")
- frame.Lineno = i
- }
- }
- }
- if frame.AbsolutePath == "" && frame.Filename != fileParseError {
- frame.AbsolutePath = frame.Filename
- if sp := strings.Split(frame.Filename, "/"); len(sp) > 1 {
- frame.Filename = sp[len(sp)-1]
- }
- }
- thread.Stacktrace.Frames = append([]*raven.StacktraceFrame{frame}, thread.Stacktrace.Frames...)
- frame = nil
- continue
- }
- }
- // Add last thread.
- s.Values = append(s.Values, thread)
-}
-
-func findPanicSender(s *Threads, err error) string {
- out := "error nil"
- if err != nil {
- out = err.Error()
- }
- for _, thread := range s.Values {
- if !thread.Crashed {
- continue
- }
- for i, fr := range thread.Stacktrace.Frames {
- if strings.HasSuffix(fr.Filename, "panic.go") && strings.HasPrefix(fr.Function, "panic") {
- // Next frame if any.
- j := 0
- if i > j {
- j = i - 1
- }
-
- // Directory and filename.
- fname := thread.Stacktrace.Frames[j].AbsolutePath
- if sp := strings.Split(fname, "/"); len(sp) > 2 {
- fname = strings.Join(sp[len(sp)-2:], "/")
- }
-
- // Line number.
- if ln := thread.Stacktrace.Frames[j].Lineno; ln > 0 {
- fname = fmt.Sprintf("%s:%d", fname, ln)
- }
-
- out = fmt.Sprintf("%s: %s", fname, out)
- break // Just first panic.
- }
- }
- }
- return out
-}
-
-// ReportSentryCrash reports a sentry crash with stacktrace from all goroutines.
+// ReportSentryCrash reports a sentry crash.
func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error) (err error) {
if reportErr == nil {
return
@@ -160,18 +40,16 @@ func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error)
"UserID": "",
}
- threads := &Threads{}
- threads.TraceAllRoutines()
- errorWithFile := findPanicSender(threads, reportErr)
- packet := raven.NewPacket(errorWithFile, threads)
+ sentry.WithScope(func(scope *sentry.Scope) {
+ scope.SetTags(tags)
+ sentry.CaptureException(reportErr)
+ })
- eventID, ch := raven.Capture(packet, tags)
-
- if err = <-ch; err == nil {
- log.WithField("errorID", eventID).Warn("Reported sentry error")
- } else {
- log.WithField("error", reportErr).WithError(err).Error("Failed to report sentry error")
+ if !sentry.Flush(time.Second * 10) {
+ log.WithField("error", reportErr).Error("failed to report sentry error")
+ return errors.New("failed to report sentry error")
}
- return err
+ log.WithField("error", reportErr).Warn("reported sentry error")
+ return
}
diff --git a/pkg/sentry/report_test.go b/pkg/sentry/report_test.go
deleted file mode 100644
index 4c902ee4..00000000
--- a/pkg/sentry/report_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2020 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 .
-
-package sentry
-
-import (
- "errors"
- "testing"
-
- "github.com/getsentry/raven-go"
-)
-
-func TestSentryCrashReport(t *testing.T) {
- if err := ReportSentryCrash(
- "clientID",
- "appVersion",
- "useragent",
- errors.New("Testing crash report - api proxy; goroutines with threads, find origin"),
- ); err != nil {
- t.Fatal("Expected no error while report, but have", err)
- }
-}
-
-func (s *Threads) TraceAllRoutinesTest() {
- s.Values = []Thread{
- {
- ID: 0,
- Name: "goroutine 20 [running]",
- Crashed: true,
- Stacktrace: &raven.Stacktrace{
- Frames: []*raven.StacktraceFrame{
- {
- Filename: "/home/dev/build/go-1.10.2/go/src/runtime/pprof/pprof.go",
- Function: "runtime/pprof.writeGoroutineStacks(0x9b7de0, 0xc4203e2900, 0xd0, 0xd0)",
- Lineno: 650,
- },
- },
- },
- },
- {
- ID: 1,
- Name: "goroutine 20 [chan receive]",
- Crashed: false,
- Stacktrace: &raven.Stacktrace{
- Frames: []*raven.StacktraceFrame{
- {
- Filename: "/home/dev/build/go-1.10.2/go/src/testing/testing.go",
- Function: "testing.(*T).Run(0xc4203e42d0, 0x90f445, 0x15, 0x97d358, 0x47a501)",
- Lineno: 825,
- },
- },
- },
- },
- }
-}
diff --git a/release-notes/bugs-bridge.txt b/release-notes/bugs-bridge.txt
index 35c99caa..53ccd62c 100644
--- a/release-notes/bugs-bridge.txt
+++ b/release-notes/bugs-bridge.txt
@@ -1,3 +1,4 @@
-• Ensured that conversations are properly threaded
-• Fixed Linux font issues (Fedora)
-• Better handling of Mime encrypted messages
+• Bridge crashes related to labels handling
+• GUI popup related to TLS connection error
+• An issue where a random session key is included in the data payload
+• Error handling (including improved detection)
diff --git a/release-notes/notes-bridge.txt b/release-notes/notes-bridge.txt
index 1491c60c..9d5c3f98 100644
--- a/release-notes/notes-bridge.txt
+++ b/release-notes/notes-bridge.txt
@@ -1,4 +1,4 @@
-• Ensured better message flow by refactoring both address and date parsing
-• Improved secure connectivity checks
-• Better deb packaging
-• More robust error handling
+• Improved package creation logic
+• Refactor of sending functions to simplify code maintenance
+• Added tests for package creation
+• For more detailed summary of the changes see https://github.com/ProtonMail/proton-bridge/blob/master/Changelog.md
diff --git a/test/api_checks_test.go b/test/api_checks_test.go
index d052932f..231da38c 100644
--- a/test/api_checks_test.go
+++ b/test/api_checks_test.go
@@ -28,7 +28,7 @@ import (
func APIChecksFeatureContext(s *godog.Suite) {
s.Step(`^API endpoint "([^"]*)" is called with:$`, apiIsCalledWith)
- s.Step(`^message is sent with API call:$`, messageIsSentWithAPICall)
+ s.Step(`^message is sent with API call$`, messageIsSentWithAPICall)
s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages)
s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has messages$`, apiMailboxForAddressOfUserHasMessages)
}
diff --git a/test/features/bridge/smtp/send/bcc.feature b/test/features/bridge/smtp/send/bcc.feature
index ce7348ec..683804f8 100644
--- a/test/features/bridge/smtp/send/bcc.feature
+++ b/test/features/bridge/smtp/send/bcc.feature
@@ -17,7 +17,7 @@ Feature: SMTP with bcc
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | hello |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -52,7 +52,7 @@ Feature: SMTP with bcc
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | | hello |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
diff --git a/test/features/bridge/smtp/send/html.feature b/test/features/bridge/smtp/send/html.feature
index a14a341f..4524a742 100644
--- a/test/features/bridge/smtp/send/html.feature
+++ b/test/features/bridge/smtp/send/html.feature
@@ -21,7 +21,7 @@ Feature: SMTP sending of HTML messages
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:
+ And message is sent with API call
"""
{
"Message": {
@@ -96,7 +96,7 @@ Feature: SMTP sending of HTML messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Html Inline External |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -185,7 +185,7 @@ Feature: SMTP sending of HTML messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Html Inline Alternative Internal |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -274,7 +274,7 @@ Feature: SMTP sending of HTML messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Html Inline Alternative External |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
diff --git a/test/features/bridge/smtp/send/html_att.feature b/test/features/bridge/smtp/send/html_att.feature
index 7111154b..005d0302 100644
--- a/test/features/bridge/smtp/send/html_att.feature
+++ b/test/features/bridge/smtp/send/html_att.feature
@@ -41,7 +41,7 @@ Feature: SMTP sending of HTML messages with attachments
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | HTML with attachment internal |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -100,7 +100,7 @@ Feature: SMTP sending of HTML messages with attachments
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | HTML with attachment external PGP |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
diff --git a/test/features/bridge/smtp/send/plain.feature b/test/features/bridge/smtp/send/plain.feature
index 5f8bfcf6..f34e4d8f 100644
--- a/test/features/bridge/smtp/send/plain.feature
+++ b/test/features/bridge/smtp/send/plain.feature
@@ -16,7 +16,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -50,7 +50,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -87,7 +87,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Plain text internal |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -124,7 +124,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text external |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -161,7 +161,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text no charset external |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -201,7 +201,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain text no charset external |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -236,7 +236,7 @@ Feature: SMTP sending of plain messages
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain, no charset, no content, external |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
diff --git a/test/features/bridge/smtp/send/plain_att.feature b/test/features/bridge/smtp/send/plain_att.feature
index e0c21fda..56462a2d 100644
--- a/test/features/bridge/smtp/send/plain_att.feature
+++ b/test/features/bridge/smtp/send/plain_att.feature
@@ -41,7 +41,7 @@ Feature: SMTP sending of plain messages with attachments
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | bridgetest@protonmail.com | Plain with attachment |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -100,7 +100,7 @@ Feature: SMTP sending of plain messages with attachments
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Plain with attachment external |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
@@ -160,7 +160,7 @@ Feature: SMTP sending of plain messages with attachments
And mailbox "Sent" for "user" has messages
| from | to | cc | subject |
| [userAddress] | pm.bridge.qa@gmail.com | bridgeqa@seznam.cz | Plain with attachment external PGP and external CC |
- And message is sent with API call:
+ And message is sent with API call
"""
{
"Message": {
diff --git a/test/features/bridge/smtp/send/send_append.feature b/test/features/bridge/smtp/send/send_append.feature
new file mode 100644
index 00000000..ebf79b7f
--- /dev/null
+++ b/test/features/bridge/smtp/send/send_append.feature
@@ -0,0 +1,50 @@
+Feature: SMTP sending with APPENDing to Sent
+ Background:
+ Given there is connected user "user"
+ And there is IMAP client logged in as "user"
+ And there is IMAP client selected in "Sent"
+ And there is SMTP client logged in as "user"
+
+ Scenario: Send message and append to Sent
+ # First do sending.
+ When SMTP client sends message
+ """
+ To: Internal Bridge
+ Subject: Manual send and append
+ Message-ID: bridgemessage42
+
+ hello
+
+ """
+ Then SMTP response is "OK"
+ And mailbox "Sent" for "user" has 1 messages
+ And mailbox "Sent" for "user" has messages
+ | externalid | from | to | subject |
+ | bridgemessage42 | [userAddress] | bridgetest@protonmail.com | Manual send and append |
+ And message is sent with API call
+ """
+ {
+ "Message": {
+ "Subject": "Manual send and append",
+ "ExternalID": "bridgemessage42"
+ }
+ }
+ """
+
+ # Then simulate manual append to Sent mailbox - message should be detected as a duplicate.
+ When IMAP client imports message to "Sent"
+ """
+ To: Internal Bridge
+ Subject: Manual send and append
+ Message-ID: bridgemessage42
+
+ hello
+
+ """
+ Then IMAP response is "OK"
+ And mailbox "Sent" for "user" has 1 messages
+
+ # Check that the external ID was not lost in the process.
+ When IMAP client sends command "FETCH 1 body.peek[header]"
+ Then IMAP response is "OK"
+ And IMAP response contains "bridgemessage42"
diff --git a/test/store_checks_test.go b/test/store_checks_test.go
index 435d6f34..3a5dd9b4 100644
--- a/test/store_checks_test.go
+++ b/test/store_checks_test.go
@@ -211,6 +211,10 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []int
if message.ID != id {
matches = false
}
+ case "externalid":
+ if message.ExternalID != cell.Value {
+ matches = false
+ }
case "from": //nolint[goconst]
address := ctx.EnsureAddress(account.Username(), cell.Value)
if !areAddressesSame(message.Sender.Address, address) {
diff --git a/unreleased.md b/unreleased.md
index f0d45e4d..0fb9ce82 100644
--- a/unreleased.md
+++ b/unreleased.md
@@ -3,3 +3,9 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Unreleased
+
+### Added
+
+### Changed
+
+### Removed