forked from Silverfish/proton-bridge
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d576beeb8 | |||
| e3332d1cb6 | |||
| f59f68f894 | |||
| 9c881a02d6 | |||
| 7b21c2d734 | |||
| 9fdc5960bf | |||
| fe853efe32 | |||
| 9b82c03959 | |||
| 914d1b27b5 | |||
| f295d03641 | |||
| 8515f6e6ac | |||
| 4d330e24c1 | |||
| a7a52bc57e | |||
| 3cef7985d3 | |||
| 40db822450 | |||
| 2de202ca02 | |||
| 38eb9fdac7 | |||
| f469d34781 | |||
| 33dfc5ce09 | |||
| 2100e2ff7c | |||
| e9b7cce138 | |||
| 6877a5a15d | |||
| 64206e69bd | |||
| 7643c76cb1 | |||
| b0f59273d3 | |||
| af8eb9d37d | |||
| 635e51f32f | |||
| ca962ce5ad | |||
| a50266cdc0 | |||
| 6230200218 | |||
| f96cd167ef | |||
| d043cb9086 |
@ -1,4 +1,4 @@
|
|||||||
image: gitlab.protontech.ch:4567/go/bridge-internal
|
image: gitlab.protontech.ch:4567/go/bridge-internal:latest
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- eval $(ssh-agent -s)
|
- eval $(ssh-agent -s)
|
||||||
@ -45,6 +45,8 @@ lint:
|
|||||||
- branches
|
- branches
|
||||||
script:
|
script:
|
||||||
- make lint
|
- make lint
|
||||||
|
tags:
|
||||||
|
- medium
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
@ -60,6 +62,8 @@ test:
|
|||||||
- pass init `gpg --list-keys | grep "^ " | tail -1 | tr -d '[:space:]'`
|
- pass init `gpg --list-keys | grep "^ " | tail -1 | tr -d '[:space:]'`
|
||||||
# Then finally run the tests
|
# Then finally run the tests
|
||||||
- make test
|
- make test
|
||||||
|
tags:
|
||||||
|
- medium
|
||||||
|
|
||||||
test-integration:
|
test-integration:
|
||||||
stage: test
|
stage: test
|
||||||
@ -67,6 +71,8 @@ test-integration:
|
|||||||
- branches
|
- branches
|
||||||
script:
|
script:
|
||||||
- VERBOSITY=debug make -C test test
|
- VERBOSITY=debug make -C test test
|
||||||
|
tags:
|
||||||
|
- large
|
||||||
|
|
||||||
dependency-updates:
|
dependency-updates:
|
||||||
stage: test
|
stage: test
|
||||||
@ -85,6 +91,8 @@ dependency-updates:
|
|||||||
# Note: The latest artifacts for refs are locked against deletion, and kept regardless of the expiry time.
|
# Note: The latest artifacts for refs are locked against deletion, and kept regardless of the expiry time.
|
||||||
# Introduced in GitLab 13.0 behind a disabled feature flag, and made the default behavior in GitLab 13.4.
|
# Introduced in GitLab 13.0 behind a disabled feature flag, and made the default behavior in GitLab 13.4.
|
||||||
expire_in: 1 day
|
expire_in: 1 day
|
||||||
|
tags:
|
||||||
|
- large
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
extends: .build-base
|
extends: .build-base
|
||||||
|
|||||||
11
BUILDS.md
11
BUILDS.md
@ -13,7 +13,16 @@ To enable the sending of crash reports using Sentry please set the
|
|||||||
Otherwise, the sending of crash reports will be disabled.
|
Otherwise, the sending of crash reports will be disabled.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
* for Windows please unset the `MSYSTEM` variable
|
In order to build Bridge or Import-Export app with Qt interface we are using
|
||||||
|
[Qt Go Binding](https://github.com/therecipe/qt). The dependencies and
|
||||||
|
installation of this tool is part of `make build` target. If you have issues
|
||||||
|
with installation of therecipe/qt we recommend to follow [this
|
||||||
|
wiki](https://github.com/therecipe/qt/wiki/Installation-on-Linux)
|
||||||
|
|
||||||
|
Please note that `$(go env GOPATH)/bin` must be in your `PATH` to ensure
|
||||||
|
binaries installed by `therecipe/qt` (such as `qtdeploy`) are found. Also,
|
||||||
|
before you start build **on Windows**, please unset the `MSYSTEM` variable
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export MSYSTEM=
|
export MSYSTEM=
|
||||||
|
|||||||
200
Changelog.md
200
Changelog.md
@ -2,10 +2,40 @@
|
|||||||
|
|
||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Bridge 1.5.4] Golden Gate
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Log warning about permanently deleting messages.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* License path on Arch and Windows.
|
||||||
|
|
||||||
|
## [Bridge 1.5.3] Golden Gate [Import-Export 1.2.3] Elbe
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-906 Handle RFC2047-encoded content transfer encoding values.
|
||||||
|
* GODT-887 Make supports build with native Qt.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-893 Bump go-rfc5322 dependency to v0.2.1 to properly detect syntax errors during parsing.
|
||||||
|
* GODT-892 Swap type and value from sentry exception and cut panic handlers from the traceback.
|
||||||
|
* GODT-854 EXPUNGE and FETCH unilateral responses are returned before OK EXPUNGE or OK STORE, respectively.
|
||||||
|
* #109 Renamed COPYING.md to not be read by [pkg-go-dev](https://pkg.go.dev/license-policy).
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* GODT-651 Build creates proper binary names.
|
||||||
|
* GODT-148 Allow import (using the Import-Export app) of already encrypted messages as is.
|
||||||
|
* GODT-202 Update to latest go-smtp.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-135 Support parameters in SMTP `FROM MAIL` command, such as `BODY=7BIT`, or empty value `FROM MAIL:<>` used by some clients.
|
||||||
|
* GODT-338 GODT-781 GODT-857 GODT-866 Flaky tests.
|
||||||
|
* GODT-773 Replace old dates with birthday of RFC822 to not crash Apple Mail. Original is available under `X-Original-Date` header.
|
||||||
|
|
||||||
## [Bridge 1.5.2] Golden Gate
|
## [Bridge 1.5.2] Golden Gate
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-883 Use `ClearPacket` for `text/plain` with signature
|
* GODT-883 Use `ClearPacket` for `text/plain` with signature.
|
||||||
|
|
||||||
|
|
||||||
## [Bridge 1.5.1] Golden Gate
|
## [Bridge 1.5.1] Golden Gate
|
||||||
@ -26,12 +56,12 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-878 Encryption of session keys moved to pmapi.
|
* GODT-878 Encryption of session keys moved to pmapi.
|
||||||
|
|
||||||
|
|
||||||
## [IE 1.2.1] Elbe
|
## [IE 1.2.1, 1.2.2] Elbe
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* GODT-799 Skipped messages do not change total counts but shows as separate number.
|
* GODT-799 Skipped messages do not change total counts but shows as separate number.
|
||||||
|
|
||||||
## Fixed
|
### Fixed
|
||||||
* GODT-799 Fix skipping unwanted folders importing from mbox files.
|
* GODT-799 Fix skipping unwanted folders importing from mbox files.
|
||||||
* GODT-769 Close connection before deleting labels to prevent panics accessing deleted bucket.
|
* GODT-769 Close connection before deleting labels to prevent panics accessing deleted bucket.
|
||||||
|
|
||||||
@ -48,7 +78,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-847 Waiting for unilateral update during deleting the message.
|
* GODT-847 Waiting for unilateral update during deleting the message.
|
||||||
* GODT-849 Show in error counts in the end also lost messages.
|
* GODT-849 Show in error counts in the end also lost messages.
|
||||||
* GODT-835 Do not include conversation ID in references to show properly conversation threads in clients.
|
* GODT-835 Do not include conversation ID in references to show properly conversation threads in clients.
|
||||||
* GODT-685 Improve deb packaging regarding dejavu font
|
* GODT-685 Improve deb packaging regarding dejavu font.
|
||||||
|
|
||||||
|
|
||||||
## [IE 1.2.0] Elbe
|
## [IE 1.2.0] Elbe
|
||||||
@ -60,7 +90,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* GODT-677 Windows IE: global import settings not fit in window.
|
* GODT-677 Windows IE: global import settings not fit in window.
|
||||||
* GODT-794 Congo fails to update to Danube
|
* GODT-794 Congo fails to update to Danube.
|
||||||
* GODT-749 Don't force PGP/Inline when sending plaintext messages.
|
* GODT-749 Don't force PGP/Inline when sending plaintext messages.
|
||||||
* GODT-764 Fix deadlock in integration tests for Import-Export.
|
* GODT-764 Fix deadlock in integration tests for Import-Export.
|
||||||
* GODT-662 Do not resume paused transfer progress after dismissing cancel popup.
|
* GODT-662 Do not resume paused transfer progress after dismissing cancel popup.
|
||||||
@ -88,8 +118,8 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* GODT-798 Replace, don't add, transfer encoding when making body 7-bit clean.
|
* GODT-798 Replace, don't add, transfer encoding when making body 7-bit clean.
|
||||||
* Move/Copy duplicate for emails with References in Outlook
|
* Move/Copy duplicate for emails with References in Outlook.
|
||||||
* CSB-247 Cannot update from 1.4.0
|
* CSB-247 Cannot update from 1.4.0.
|
||||||
|
|
||||||
|
|
||||||
## [Bridge 1.4.3] Forth
|
## [Bridge 1.4.3] Forth
|
||||||
@ -115,7 +145,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-776 Fix crash when IMAP client connects while account is logging in.
|
* GODT-776 Fix crash when IMAP client connects while account is logging in.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8
|
* Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8.
|
||||||
* GODT-785 Clear separation of different message IDs in integration tests.
|
* GODT-785 Clear separation of different message IDs in integration tests.
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-741 Import-Export shows "Unable to parse time" notice instead of zero time in error report window.
|
* GODT-741 Import-Export shows "Unable to parse time" notice instead of zero time in error report window.
|
||||||
@ -172,60 +202,60 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-461 Add support for `\Deleted` flag.
|
* GODT-461 Add support for `\Deleted` flag.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-462 Pausing event loop while FETCHing to prevent EXPUNGE
|
* GODT-462 Pausing event loop while FETCHing to prevent EXPUNGE.
|
||||||
* Wait for unilateral response to be delivered
|
* Wait for unilateral response to be delivered.
|
||||||
* GODT-409 Set flags have to replace all flags.
|
* GODT-409 Set flags have to replace all flags.
|
||||||
* GODT-531 Better way to add trusted certificate in macOS.
|
* GODT-531 Better way to add trusted certificate in macOS.
|
||||||
* Bumped golangci-lint to v1.29.0
|
* Bumped golangci-lint to v1.29.0.
|
||||||
* GODT-549 Check log file size more often to prevent huge log files.
|
* GODT-549 Check log file size more often to prevent huge log files.
|
||||||
* Bumped various dependencies:
|
* Bumped various dependencies:
|
||||||
* andybalholm/cascadia v1.1.0 -> v1.2.0
|
* Updated andybalholm/cascadia v1.1.0 -> v1.2.0.
|
||||||
* emersion/go-imap-specialuse 20161227184202-ba031ced6a62 -> 20200722111535-598ff00e4075
|
* Updated emersion/go-imap-specialuse 20161227184202-ba031ced6a62 -> 20200722111535-598ff00e4075.
|
||||||
* emersion/go-sasl 20191210011802-430746ea8b9b -> 20200509203442-7bfe0ed36a21
|
* Updated emersion/go-sasl 20191210011802-430746ea8b9b -> 20200509203442-7bfe0ed36a21.
|
||||||
* github.com/go-resty/resty/v2 v2.2.0 -> v2.3.0
|
* Updated github.com/go-resty/resty/v2 v2.2.0 -> v2.3.0.
|
||||||
* github.com/golang/mock v1.4.3 -> v1.4.4
|
* Updated github.com/golang/mock v1.4.3 -> v1.4.4.
|
||||||
* github.com/google/go-cmp v0.4.0 -> v0.5.1
|
* Updated github.com/google/go-cmp v0.4.0 -> v0.5.1.
|
||||||
* github.com/hashicorp/go-multierror v1.0.0 -> v1.1.0
|
* Updated github.com/hashicorp/go-multierror v1.0.0 -> v1.1.0.
|
||||||
* github.com/jaytaylor/html2text 20200220170450-61d9dc4d7195 -> 20200412013138-3577fbdbcff7
|
* Updated github.com/jaytaylor/html2text 20200220170450-61d9dc4d7195 -> 20200412013138-3577fbdbcff7.
|
||||||
* github.com/jhillyerd/enmime v0.8.0 -> v0.8.1
|
* Updated github.com/jhillyerd/enmime v0.8.0 -> v0.8.1.
|
||||||
* github.com/keybase/go-keychain 20200218013740-86d4642e4ce2 -> 20200502122510-cda31fe0c86d
|
* Updated github.com/keybase/go-keychain 20200218013740-86d4642e4ce2 -> 20200502122510-cda31fe0c86d.
|
||||||
* github.com/logrusorgru/aurora 20200102142835-e9ef32dff381 -> v2.0.3+incompatible
|
* Updated github.com/logrusorgru/aurora 20200102142835-e9ef32dff381 -> v2.0.3+incompatible.
|
||||||
* github.com/miekg/dns v1.1.29 -> v1.1.30
|
* Updated github.com/miekg/dns v1.1.29 -> v1.1.30.
|
||||||
* github.com/nsf/jsondiff 20190712045011-8443391ee9b6 -> 20200515183724-f29ed568f4ce
|
* Updated github.com/nsf/jsondiff 20190712045011-8443391ee9b6 -> 20200515183724-f29ed568f4ce.
|
||||||
* github.com/sirupsen/logrus v1.4.2 -> v1.6.0
|
* Updated github.com/sirupsen/logrus v1.4.2 -> v1.6.0.
|
||||||
* github.com/stretchr/testify v1.5.1 -> v1.6.1
|
* Updated github.com/stretchr/testify v1.5.1 -> v1.6.1.
|
||||||
* github.com/therecipe/qt 20200126204426-5074eb6d8c41 -> 20200701200531-7f61353ee73e
|
* Updated github.com/therecipe/qt 20200126204426-5074eb6d8c41 -> 20200701200531-7f61353ee73e.
|
||||||
* github.com/urfave/cli v1.22.3 -> v1.22.4
|
* Updated github.com/urfave/cli v1.22.3 -> v1.22.4.
|
||||||
* golang.org/x/net 20200301022130-244492dfa37a -> 20200707034311-ab3426394381
|
* Updated golang.org/x/net 20200301022130-244492dfa37a -> 20200707034311-ab3426394381.
|
||||||
* golang.org/x/text v0.3.2 -> v0.3.3
|
* Updated golang.org/x/text v0.3.2 -> v0.3.3.
|
||||||
* Set first-start to false in bridge, not in frontend.
|
* Set first-start to false in bridge, not in frontend.
|
||||||
* GODT-400 Refactor sendingInfo.
|
* GODT-400 Refactor sendingInfo.
|
||||||
* GODT-513 Update routes to API v4.
|
* GODT-513 Update routes to API v4.
|
||||||
* GODT-551 Do not ignore errors during message flagging.
|
* GODT-551 Do not ignore errors during message flagging.
|
||||||
* GODT-380 Adding IE GUI to Bridge repo and building
|
* GODT-380 Adding IE GUI to Bridge repo and building.
|
||||||
* BR: extend functionality of PopupDialog
|
* BR: extend functionality of PopupDialog.
|
||||||
* BR: makefile APP_VERSION instead of BRIDGE_VERSION
|
* BR: makefile APP_VERSION instead of BRIDGE_VERSION.
|
||||||
* BR: use common logs function for Qt
|
* BR: use common logs function for Qt.
|
||||||
* BR: change `go.progressDescription` to `string`
|
* BR: change `go.progressDescription` to `string`.
|
||||||
* IE: Rounded button has fa-icon
|
* IE: Rounded button has fa-icon.
|
||||||
* IE: `Upgrade` → `Update`
|
* IE: `Upgrade` → `Update`.
|
||||||
* IE: Moving `AccountModel` to `qt-common`
|
* IE: Moving `AccountModel` to `qt-common`.
|
||||||
* IE: Added `ReportBug` to `internal/importexport`
|
* IE: Added `ReportBug` to `internal/importexport`.
|
||||||
* IE: Added event watch in GUI
|
* IE: Added event watch in GUI.
|
||||||
* IE: Removed `onLoginFinished`
|
* IE: Removed `onLoginFinished`.
|
||||||
* Structure for transfer rules in QML
|
* Structure for transfer rules in QML.
|
||||||
* GODT-213 Convert panics from message parser to error.
|
* GODT-213 Convert panics from message parser to error.
|
||||||
* GODT-585 Do not allow deleting messages from All Mail.
|
* GODT-585 Do not allow deleting messages from All Mail.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* GODT-655 Fix date picker with automatic Windows DST
|
* GODT-655 Fix date picker with automatic Windows DST.
|
||||||
* GODT-454 Fix send on closed channel when receiving unencrypted send confirmation from GUI.
|
* GODT-454 Fix send on closed channel when receiving unencrypted send confirmation from GUI.
|
||||||
* GODT-597 Duplicate sending when draft creation takes too long
|
* GODT-597 Duplicate sending when draft creation takes too long.
|
||||||
* GODT-634 Hover on links in popups.
|
* GODT-634 Hover on links in popups.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v1.3.x] Emma (v1.3.2 beta 2020-08-04, v1.3.3 beta 2020-08-06, v1.3.3 live 2020-08-12)
|
## [Bridge 1.3.x] Emma (v1.3.2 beta 2020-08-04, v1.3.3 beta 2020-08-06, v1.3.3 live 2020-08-12)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* GODT-554 Detect and notify about "bad certificate" IMAP TLS error.
|
* GODT-554 Detect and notify about "bad certificate" IMAP TLS error.
|
||||||
@ -285,7 +315,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Issue causing deadlock when reloading users keys due to double-locking of a mutex.
|
* Issue causing deadlock when reloading users keys due to double-locking of a mutex.
|
||||||
* Correctly handle failure to unlock single key.
|
* Correctly handle failure to unlock single key.
|
||||||
* GODT-479 Fix flaky integration tests.
|
* GODT-479 Fix flaky integration tests.
|
||||||
* GODT-484 Fix infinite loop when decoding invalid 2231 charset
|
* GODT-484 Fix infinite loop when decoding invalid 2231 charset.
|
||||||
* GODT-267 Correctly detect if a message is a draft even if does not have DraftLabel.
|
* GODT-267 Correctly detect if a message is a draft even if does not have DraftLabel.
|
||||||
* GODT-308 Reduce minimum read speed threshold to avoid issues with flaky internet.
|
* GODT-308 Reduce minimum read speed threshold to avoid issues with flaky internet.
|
||||||
* GODT-321 Changing address ordering would cause all messages to disappear in combined mode.
|
* GODT-321 Changing address ordering would cause all messages to disappear in combined mode.
|
||||||
@ -294,7 +324,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-427 Fix race condition in auth refresh that could cause user to be logged out.
|
* GODT-427 Fix race condition in auth refresh that could cause user to be logged out.
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.8] Donghai-fix-append (beta 2020-06-XXX)
|
## [Bridge 1.2.8] Donghai-fix-append (beta 2020-06-XXX)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-396 reduce number of EXISTS calls.
|
* GODT-396 reduce number of EXISTS calls.
|
||||||
@ -303,7 +333,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
### Fixed
|
### Fixed
|
||||||
* GODT-502 Fixed crash when unable to parse a message header.
|
* GODT-502 Fixed crash when unable to parse a message header.
|
||||||
|
|
||||||
## [v1.2.7] Donghai-fix-sync - (beta 2020-05-07 live 2020-04-20)
|
## [Bridge 1.2.7] Donghai-fix-sync - (beta 2020-05-07 live 2020-04-20)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* IMAP extension MOVE with UIDPLUS support.
|
* IMAP extension MOVE with UIDPLUS support.
|
||||||
@ -326,7 +356,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Use correct binary name when finding location of addcert.scpt.
|
* Use correct binary name when finding location of addcert.scpt.
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.6] Donghai - beta (2020-03-31)
|
## [Bridge 1.2.6] Donghai - beta (2020-03-31)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* GODT-145 Support drafts.
|
* GODT-145 Support drafts.
|
||||||
@ -381,13 +411,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* UserIDs were not checked when importing to Sent folder (affects copying from account1/sent to account2/sent).
|
* UserIDs were not checked when importing to Sent folder (affects copying from account1/sent to account2/sent).
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.5] Charles - live (2020-03-11) beta (from 2020-02-10)
|
## [Bridge 1.2.5] Charles - live (2020-03-11) beta (from 2020-02-10)
|
||||||
|
|
||||||
### Hotfix
|
|
||||||
* CSB-40 panic in credential store.
|
|
||||||
* Keyring unlocking locker.
|
|
||||||
* No panic on failed html parse.
|
|
||||||
* Too many open files.
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* GODT-112 Migration of preferences from c10 to c11.
|
* GODT-112 Migration of preferences from c10 to c11.
|
||||||
@ -432,13 +456,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Separated IMAP to store and IMAP.
|
* Separated IMAP to store and IMAP.
|
||||||
* Store is responsible for everything about db and calls to pmapi, including event loop, sync, address mode.
|
* Store is responsible for everything about db and calls to pmapi, including event loop, sync, address mode.
|
||||||
* IMAP is responsible only for IMAP interfaces.
|
* IMAP is responsible only for IMAP interfaces.
|
||||||
* Event loop is only one per ProtonMail account (instead of one per alias)
|
* Event loop is only one per ProtonMail account (instead of one per alias).
|
||||||
* It also means only one database per account (instead of one per address)
|
* It also means only one database per account (instead of one per address).
|
||||||
* Changing address mode is not destroying database, only buckets with IDs mapping (keeping metadata for account)
|
* Changing address mode is not destroying database, only buckets with IDs mapping (keeping metadata for account).
|
||||||
* Before first sync we set event ID so we will not miss changes happening during sync.
|
* Before first sync we set event ID so we will not miss changes happening during sync.
|
||||||
* Thanks to previous point we are not starting new sync when we finish first one because of unprocessed events.
|
* Thanks to previous point we are not starting new sync when we finish first one because of unprocessed events.
|
||||||
* Sync is not blocking event loop (user can get new messages even during sync)
|
* Sync is not blocking event loop (user can get new messages even during sync).
|
||||||
* Sync is not blocking reading operations (user can list mailboxes even before first sync is done)
|
* Sync is not blocking reading operations (user can list mailboxes even before first sync is done).
|
||||||
* Sync is not blocking writing operations such as mark messages read/unread and so on.
|
* Sync is not blocking writing operations such as mark messages read/unread and so on.
|
||||||
* Most operations have to be passed to API and only event loop is writing them to the database.
|
* Most operations have to be passed to API and only event loop is writing them to the database.
|
||||||
* Avoid relying on counts API endpoint; use event counts as much as possible.
|
* Avoid relying on counts API endpoint; use event counts as much as possible.
|
||||||
@ -448,8 +472,8 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Synchronisation will create a label if not yet present.
|
* Synchronisation will create a label if not yet present.
|
||||||
* Labels and Folders (including system folders) are stored in DB together with their counts for offline read-out.
|
* Labels and Folders (including system folders) are stored in DB together with their counts for offline read-out.
|
||||||
* AddressIDs for all user addresses are stored in DB.
|
* AddressIDs for all user addresses are stored in DB.
|
||||||
* IMAP updates channel is set when an IMAP client connects (and IMAP updates are dropped until then)
|
* IMAP updates channel is set when an IMAP client connects (and IMAP updates are dropped until then).
|
||||||
* DB keeps track of address mode (split/combined)
|
* DB keeps track of address mode (split/combined).
|
||||||
* Event loop starts as soon as user is initialised (i.e. logged in), not just when imap is connected.
|
* Event loop starts as soon as user is initialised (i.e. logged in), not just when imap is connected.
|
||||||
* Use pmapi v1.0.13.
|
* Use pmapi v1.0.13.
|
||||||
* Logout user if initialisation fails.
|
* Logout user if initialisation fails.
|
||||||
@ -457,6 +481,10 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Use godog v0.8.0 under new name 'cucumber' (instead of DATA-DOG).
|
* Use godog v0.8.0 under new name 'cucumber' (instead of DATA-DOG).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
* CSB-40 panic in credential store.
|
||||||
|
* Keyring unlocking locker.
|
||||||
|
* No panic on failed html parse.
|
||||||
|
* Too many open files.
|
||||||
* #1057 Logging in to an already logged in user would display unrelated error "invalid mailbox password".
|
* #1057 Logging in to an already logged in user would display unrelated error "invalid mailbox password".
|
||||||
* #1056 Changing mailbox password sometimes didn't log out user.
|
* #1056 Changing mailbox password sometimes didn't log out user.
|
||||||
* #1066 Split address mode can not work when credentials store is cleared.
|
* #1066 Split address mode can not work when credentials store is cleared.
|
||||||
@ -472,7 +500,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-103 User keys were not unlocked later if they were not unlocked during startup.
|
* GODT-103 User keys were not unlocked later if they were not unlocked during startup.
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.4] Brooklyn beta (2019-12-16)
|
## [Bridge 1.2.4] Brooklyn beta (2019-12-16)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* #976: fix slow authentication.
|
* #976: fix slow authentication.
|
||||||
@ -487,7 +515,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Fixed an issue where entering an in-use port multiple times via the CLI would make bridge use it.
|
* Fixed an issue where entering an in-use port multiple times via the CLI would make bridge use it.
|
||||||
* Update therecipe/qt and Qt to 5.13.
|
* Update therecipe/qt and Qt to 5.13.
|
||||||
|
|
||||||
## [v1.2.3] Akashi - live (2019-11-05) beta (2019-10-22)
|
## [Bridge 1.2.3] Akashi - live (2019-11-05) beta (2019-10-22)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* #963 report first-start metric with bridge version.
|
* #963 report first-start metric with bridge version.
|
||||||
@ -517,17 +545,17 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Code made compatible with name changes in go-pmapi.
|
* Code made compatible with name changes in go-pmapi.
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.2] - beta and live 2019-09-06
|
## [Bridge 1.2.2] - beta and live 2019-09-06
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* User compare case insensitive.
|
* User compare case insensitive.
|
||||||
|
|
||||||
## [v1.2.1] - beta and live 2019-09-05
|
## [Bridge 1.2.1] - beta and live 2019-09-05
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* #924 fix start of bridge without internet connection.
|
* #924 fix start of bridge without internet connection.
|
||||||
|
|
||||||
## [v1.2.0] - beta 2019-08-22
|
## [Bridge 1.2.0] - beta 2019-08-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* #903 added http.Client timeout to not hang out forever.
|
* #903 added http.Client timeout to not hang out forever.
|
||||||
@ -627,7 +655,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Handle logout in event loop.
|
* Handle logout in event loop.
|
||||||
|
|
||||||
|
|
||||||
## [v1.1.6] - 2019-07-09 (beta 2019-07-01)
|
## [Bridge 1.1.6] - 2019-07-09 (beta 2019-07-01)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* #841 assume text/plain during sending e-mails when missing content type.
|
* #841 assume text/plain during sending e-mails when missing content type.
|
||||||
@ -659,7 +687,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Lint corrections.
|
* Lint corrections.
|
||||||
|
|
||||||
|
|
||||||
## [v1.1.5] - 2019-05-23 (beta 2019-05-23, 2019-05-16)
|
## [Bridge 1.1.5] - 2019-05-23 (beta 2019-05-23, 2019-05-16)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Fix custom message format.
|
* Fix custom message format.
|
||||||
@ -673,7 +701,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Only one crash from second instance.
|
* Only one crash from second instance.
|
||||||
* During event `MessageID` in log as field.
|
* During event `MessageID` in log as field.
|
||||||
|
|
||||||
## [v1.1.4 live] - 2019-04-10 (beta 2019-04-05, 2019-03-27)
|
## [Bridge 1.1.4 live] - 2019-04-10 (beta 2019-04-05, 2019-03-27)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Address with port to IMAP debug.
|
* Address with port to IMAP debug.
|
||||||
@ -694,7 +722,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
### Removed
|
### Removed
|
||||||
* #750 Synchronization after 450 messages.
|
* #750 Synchronization after 450 messages.
|
||||||
|
|
||||||
## [v1.1.3] - 2019-03-04
|
## [Bridge 1.1.3] - 2019-03-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Sentry crash reporting in main.
|
* Sentry crash reporting in main.
|
||||||
@ -706,13 +734,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* #720 sync every 3 pages.
|
* #720 sync every 3 pages.
|
||||||
* #512 extending list of charsets go-pm-mime!4.
|
* #512 extending list of charsets go-pm-mime!4.
|
||||||
|
|
||||||
## [v1.1.2] - beta only 2019-02-21
|
## [Bridge 1.1.2] - beta only 2019-02-21
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* #512 fail on unknown charset.
|
* #512 fail on unknown charset.
|
||||||
* #729 #733 visitor for MIME parsing.
|
* #729 #733 visitor for MIME parsing.
|
||||||
|
|
||||||
## [v1.1.1] - 2019-02-11
|
## [Bridge 1.1.1] - 2019-02-11
|
||||||
### Added
|
### Added
|
||||||
* #671 include `name` param in attachment `Content-Type` (in addition to `Content-Disposition` param `filename`).
|
* #671 include `name` param in attachment `Content-Type` (in addition to `Content-Disposition` param `filename`).
|
||||||
* #671 do not include content headers for section requests e.g. `BODY.PEEK[2]`.
|
* #671 do not include content headers for section requests e.g. `BODY.PEEK[2]`.
|
||||||
@ -765,7 +793,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* SMTP stays authenticated after sent message.
|
* SMTP stays authenticated after sent message.
|
||||||
* Reduce memory, processor and number of API calls.
|
* Reduce memory, processor and number of API calls.
|
||||||
|
|
||||||
## [v1.1.0] - 2018-10-22
|
## [Bridge 1.1.0] - 2018-10-22
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
* `go-pmapi.Config.ClientSecret`.
|
* `go-pmapi.Config.ClientSecret`.
|
||||||
@ -841,11 +869,11 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Additional synchronization of mail database.
|
* Additional synchronization of mail database.
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.6 silent] - 2018-08-23
|
## [Bridge 1.0.6 silent] - 2018-08-23
|
||||||
### Added
|
### Added
|
||||||
* New svg icon in linux package.
|
* New svg icon in linux package.
|
||||||
|
|
||||||
## [v1.0.6] - 2018-08-09
|
## [Bridge 1.0.6] - 2018-08-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* `backend.GetUserSettings()`.
|
* `backend.GetUserSettings()`.
|
||||||
@ -874,7 +902,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Frequent Thunderbird timeout.
|
* Frequent Thunderbird timeout.
|
||||||
* SMTP requests not case-sensitive.
|
* SMTP requests not case-sensitive.
|
||||||
|
|
||||||
## [v1.0.5] - 2018-07-12
|
## [Bridge 1.0.5] - 2018-07-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* UpdateCurrentAgent from lastMailClient.
|
* UpdateCurrentAgent from lastMailClient.
|
||||||
@ -906,7 +934,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Fixed 7bit MIME issue while sending.
|
* Fixed 7bit MIME issue while sending.
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.4] - 2018-05-15
|
## [Bridge 1.0.4] - 2018-05-15
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Version files available at both download and static.
|
* Version files available at both download and static.
|
||||||
@ -927,11 +955,11 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Notification that outgoing email will be delivered as non-encrypted.
|
* Notification that outgoing email will be delivered as non-encrypted.
|
||||||
* NOTE: Due to a change of the keychain format, you will need to add your account(s) to the Bridge after installing this version.
|
* NOTE: Due to a change of the keychain format, you will need to add your account(s) to the Bridge after installing this version.
|
||||||
|
|
||||||
### Bugs fixed
|
### Fixed bugs
|
||||||
* Support accounts with same user names.
|
* Support accounts with same user names.
|
||||||
* Support sending vCalendar event.
|
* Support sending vCalendar event.
|
||||||
|
|
||||||
## [v1.0.3] - 2018-03-26
|
## [Bridge 1.0.3] - 2018-03-26
|
||||||
* All from silent updates plus following.
|
* All from silent updates plus following.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@ -966,7 +994,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* Remove firewall error message.
|
* Remove firewall error message.
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.2] - 2018-03-12
|
## [Bridge 1.0.2] - 2018-03-12
|
||||||
* All from silent updates plus following.
|
* All from silent updates plus following.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -988,7 +1016,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.1-4 (linux only)] Silent deploy - 2018-02-28
|
## [Bridge 1.0.1-4 (linux only)] Silent deploy - 2018-02-28
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* More similar look of window title bar to Windows 10 style.
|
* More similar look of window title bar to Windows 10 style.
|
||||||
@ -1012,14 +1040,14 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.1] Silent deploy - 2017-12-30
|
## [Bridge 1.0.1] Silent deploy - 2017-12-30
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Fixed bug with parsing address list (CC became BCC).
|
* Fixed bug with parsing address list (CC became BCC).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.1] - 2017-12-20
|
## [Bridge 1.0.1] - 2017-12-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* When current log file is more than 10MB open new one, checked every 15min.
|
* When current log file is more than 10MB open new one, checked every 15min.
|
||||||
@ -1051,7 +1079,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v1.0.0] - 2017-12-06
|
## [Bridge 1.0.0] - 2017-12-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Encoding support of message body, title items, attachment name, for all standard charsets.
|
* Encoding support of message body, title items, attachment name, for all standard charsets.
|
||||||
|
|||||||
23
Makefile
23
Makefile
@ -10,8 +10,8 @@ TARGET_OS?=${GOOS}
|
|||||||
.PHONY: build build-ie build-nogui build-ie-nogui check-has-go
|
.PHONY: build build-ie build-nogui build-ie-nogui check-has-go
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=1.5.2-git
|
BRIDGE_APP_VERSION?=1.5.4-git
|
||||||
IE_APP_VERSION?=1.2.1-git
|
IE_APP_VERSION?=1.2.3-git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
SRC_ICO:=logo.ico
|
SRC_ICO:=logo.ico
|
||||||
SRC_ICNS:=Bridge.icns
|
SRC_ICNS:=Bridge.icns
|
||||||
@ -64,6 +64,12 @@ ifeq "${TARGET_CMD}" "Import-Export"
|
|||||||
TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz
|
TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifdef QT_API
|
||||||
|
VENDOR_TARGET:=prepare-vendor update-qt-docs
|
||||||
|
else
|
||||||
|
VENDOR_TARGET=update-vendor
|
||||||
|
endif
|
||||||
|
|
||||||
build: ${TGZ_TARGET}
|
build: ${TGZ_TARGET}
|
||||||
build-ie:
|
build-ie:
|
||||||
TARGET_CMD=Import-Export $(MAKE) build
|
TARGET_CMD=Import-Export $(MAKE) build
|
||||||
@ -106,7 +112,7 @@ ifneq "${GOOS}" "${TARGET_OS}"
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} update-vendor
|
${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} ${VENDOR_TARGET}
|
||||||
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
|
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
|
||||||
cp cmd/${TARGET_CMD}/main.go .
|
cp cmd/${TARGET_CMD}/main.go .
|
||||||
qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET}
|
qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET}
|
||||||
@ -123,7 +129,7 @@ icon_windows.syso: icon.rc logo.ico
|
|||||||
|
|
||||||
|
|
||||||
## Rules for therecipe/qt
|
## Rules for therecipe/qt
|
||||||
.PHONY: prepare-vendor update-vendor
|
.PHONY: prepare-vendor update-vendor update-qt-docs
|
||||||
THERECIPE_ENV:=github.com/therecipe/env_${TARGET_OS}_amd64_513
|
THERECIPE_ENV:=github.com/therecipe/env_${TARGET_OS}_amd64_513
|
||||||
|
|
||||||
# vendor folder will be deleted by gomod hence we cache the big repo
|
# vendor folder will be deleted by gomod hence we cache the big repo
|
||||||
@ -148,6 +154,8 @@ prepare-vendor:
|
|||||||
update-vendor: vendor-cache/${THERECIPE_ENV} prepare-vendor
|
update-vendor: vendor-cache/${THERECIPE_ENV} prepare-vendor
|
||||||
${LINKCMD}
|
${LINKCMD}
|
||||||
|
|
||||||
|
update-qt-docs:
|
||||||
|
go get github.com/therecipe/qt/internal/binding/files/docs/$(QT_API)
|
||||||
|
|
||||||
## Dev dependencies
|
## Dev dependencies
|
||||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated
|
.PHONY: install-devel-tools install-linter install-go-mod-outdated
|
||||||
@ -209,15 +217,18 @@ coverage: test
|
|||||||
mocks:
|
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/users Configer,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/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/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/internal/store PanicHandler,ClientManager,BridgeUser,ChangeNotifier > 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/listener Listener > internal/store/mocks/utils_mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
|
||||||
|
|
||||||
lint: lint-golang lint-license
|
lint: lint-golang lint-license lint-changelog
|
||||||
|
|
||||||
lint-license:
|
lint-license:
|
||||||
./utils/missing_license.sh check
|
./utils/missing_license.sh check
|
||||||
|
|
||||||
|
lint-changelog:
|
||||||
|
./utils/changelog_linter.sh
|
||||||
|
|
||||||
lint-golang:
|
lint-golang:
|
||||||
which golangci-lint || $(MAKE) install-linter
|
which golangci-lint || $(MAKE) install-linter
|
||||||
golangci-lint run ./...
|
golangci-lint run ./...
|
||||||
|
|||||||
@ -3,7 +3,7 @@ Copyright (c) 2020 Proton Technologies AG
|
|||||||
|
|
||||||
This repository holds the ProtonMail Bridge and the ProtonMail Import-Export applications.
|
This repository holds the ProtonMail Bridge and the ProtonMail Import-Export applications.
|
||||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
For licensing information see [COPYING](./COPYING.md).
|
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
go.mod
5
go.mod
@ -6,7 +6,6 @@ go 1.13
|
|||||||
// They are in a separate require block to highlight this.
|
// They are in a separate require block to highlight this.
|
||||||
require (
|
require (
|
||||||
github.com/docker/docker-credential-helpers v0.6.3
|
github.com/docker/docker-credential-helpers v0.6.3
|
||||||
github.com/emersion/go-smtp v0.0.0-20180712174835-db5eec195e67
|
|
||||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
|
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
)
|
)
|
||||||
@ -18,7 +17,7 @@ require (
|
|||||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6
|
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||||
github.com/ProtonMail/go-rfc5322 v0.2.0
|
github.com/ProtonMail/go-rfc5322 v0.2.1
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1
|
github.com/ProtonMail/gopenpgp/v2 v2.0.1
|
||||||
github.com/PuerkitoBio/goquery v1.5.1
|
github.com/PuerkitoBio/goquery v1.5.1
|
||||||
@ -38,6 +37,7 @@ require (
|
|||||||
github.com/emersion/go-mbox v1.0.2
|
github.com/emersion/go-mbox v1.0.2
|
||||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a
|
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
|
github.com/emersion/go-smtp v0.14.0
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe
|
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
|
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
|
||||||
github.com/fatih/color v1.9.0
|
github.com/fatih/color v1.9.0
|
||||||
@ -75,7 +75,6 @@ require (
|
|||||||
replace (
|
replace (
|
||||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
||||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201102134601-418cd74e9474
|
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201102134601-418cd74e9474
|
||||||
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309
|
|
||||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||||
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8
|
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8
|
||||||
)
|
)
|
||||||
|
|||||||
9
go.sum
9
go.sum
@ -25,10 +25,8 @@ github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDE
|
|||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.2.0 h1:tndoDGFtiCvESta9KLUeMksojz8qf76PefnkoQ+fqeg=
|
github.com/ProtonMail/go-rfc5322 v0.2.1 h1:J2PHusboDAYUfE+uBfoJnKZPbnVmzK1zXw6dQrgV8yE=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.2.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
github.com/ProtonMail/go-rfc5322 v0.2.1/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309 h1:2pzfKjhBjSnw3BgmfTYRFQr1rFGxhfhUY0KKkg+RYxE=
|
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309/go.mod h1:6UoBvDAMA/cTBwS3Y7tGpKnY5RH1F1uYHschT6eqAkI=
|
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1 h1:x0uvDhry5WzoHeJO4J3dgMLhG4Z9PeBJ2O+sDOY0LcU=
|
github.com/ProtonMail/gopenpgp/v2 v2.0.1 h1:x0uvDhry5WzoHeJO4J3dgMLhG4Z9PeBJ2O+sDOY0LcU=
|
||||||
@ -94,6 +92,8 @@ github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a/go.mod h1:k
|
|||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
|
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
||||||
|
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
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 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
||||||
@ -310,6 +310,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./credits.sh at Tue Nov 24 08:56:01 AM CET 2020. DO NOT EDIT.
|
// Code generated by ./credits.sh at Fri Nov 27 09:23:06 CET 2020. DO NOT EDIT.
|
||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/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;"
|
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-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/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;"
|
||||||
|
|||||||
@ -15,18 +15,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./release-notes.sh at 'Mon Nov 23 07:38:53 AM CET 2020'. DO NOT EDIT.
|
// Code generated by ./release-notes.sh at 'Wed Dec 9 05:55:04 PM CET 2020'. DO NOT EDIT.
|
||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
const ReleaseNotes = `• Improved package creation logic
|
const ReleaseNotes = `• Support read confirmations
|
||||||
• Refactor of sending functions to simplify code maintenance
|
• Adding GPLv3 licence button to the GUI
|
||||||
• Added tests for package creation
|
• Improved testing
|
||||||
• For more detailed summary of the changes see https://github.com/ProtonMail/proton-bridge/blob/master/Changelog.md
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const ReleaseFixedBugs = `• Bridge crashes related to labels handling
|
const ReleaseFixedBugs = `• AppleMail crashes (timestamp related)
|
||||||
• GUI popup related to TLS connection error
|
• Encoding errors
|
||||||
• An issue where a random session key is included in the data payload
|
• Installation issues on linux
|
||||||
• Error handling (including improved detection)
|
|
||||||
`
|
`
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||||
|
pkgSentry "github.com/ProtonMail/proton-bridge/pkg/sentry"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@ -52,8 +53,9 @@ var (
|
|||||||
// Main sets up Sentry, filters out unwanted args, creates app and runs it.
|
// 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) {
|
func Main(appName, usage string, extraFlags []cli.Flag, run func(*cli.Context) error) {
|
||||||
err := sentry.Init(sentry.ClientOptions{
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: constants.DSNSentry,
|
Dsn: constants.DSNSentry,
|
||||||
Release: constants.Revision,
|
Release: constants.Revision,
|
||||||
|
BeforeSend: pkgSentry.EnhanceSentryEvent,
|
||||||
})
|
})
|
||||||
|
|
||||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,6 +93,8 @@ type PanicHandler struct {
|
|||||||
|
|
||||||
// HandlePanic should be called in defer to ensure restart of app after error.
|
// HandlePanic should be called in defer to ensure restart of app after error.
|
||||||
func (ph *PanicHandler) HandlePanic() {
|
func (ph *PanicHandler) HandlePanic() {
|
||||||
|
sentry.SkipDuringUnwind()
|
||||||
|
|
||||||
r := recover()
|
r := recover()
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -102,7 +102,7 @@ Item {
|
|||||||
Row {
|
Row {
|
||||||
anchors.left : parent.left
|
anchors.left : parent.left
|
||||||
|
|
||||||
Rectangle { height: Style.dialog.spacing; width: (wrapper.width- credits.width - release.width - sepaCreditsRelease.width)/2; color: "transparent"}
|
Rectangle { height: Style.dialog.spacing; width: (wrapper.width - credits.width - licenseFile.width - release.width - sepaCreditsRelease.width)/2; color: "transparent"}
|
||||||
|
|
||||||
ClickIconText {
|
ClickIconText {
|
||||||
id:credits
|
id:credits
|
||||||
@ -114,6 +114,20 @@ Item {
|
|||||||
onClicked : winMain.dialogCredits.show()
|
onClicked : winMain.dialogCredits.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {id: sepaLicenseFile ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
|
||||||
|
|
||||||
|
ClickIconText {
|
||||||
|
id:licenseFile
|
||||||
|
iconText : ""
|
||||||
|
text : qsTr("License", "link to click on to view license file")
|
||||||
|
textColor : Style.main.textDisabled
|
||||||
|
fontSize : Style.main.fontSize
|
||||||
|
textUnderline : true
|
||||||
|
onClicked : {
|
||||||
|
go.openLicenseFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {id: sepaCreditsRelease ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
|
Rectangle {id: sepaCreditsRelease ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
|
||||||
|
|
||||||
ClickIconText {
|
ClickIconText {
|
||||||
|
|||||||
@ -355,6 +355,25 @@ Dialog {
|
|||||||
InlineLabelSelect {
|
InlineLabelSelect {
|
||||||
id: globalLabels
|
id: globalLabels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Style.dialog.spacing
|
||||||
|
CheckBoxLabel {
|
||||||
|
id: importEncrypted
|
||||||
|
text: qsTr("Import encrypted emails as they are")
|
||||||
|
anchors {
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: Style.dialog.fontSize/1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoToolTip {
|
||||||
|
anchors {
|
||||||
|
verticalCenter: importEncrypted.verticalCenter
|
||||||
|
}
|
||||||
|
info: qsTr("When this option is enabled, encrypted emails will be imported as ciphertext. Otherwise, such messages will be skipped.", "todo")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
@ -1018,7 +1037,7 @@ Dialog {
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
case DialogImport.Page.Progress:
|
case DialogImport.Page.Progress:
|
||||||
go.startImport(root.address)
|
go.startImport(root.address, importEncrypted.checked)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,6 +106,20 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: licenseFile
|
||||||
|
text : qsTr("License", "link to click on to open license file")
|
||||||
|
color : Style.main.textDisabled
|
||||||
|
font.pointSize: Style.main.fontSize * Style.pt
|
||||||
|
font.underline: true
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked : {
|
||||||
|
go.openLicenseFile()
|
||||||
|
}
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: releaseNotes
|
id: releaseNotes
|
||||||
|
|||||||
@ -433,6 +433,10 @@ func (f *FrontendQt) resetSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) openLicenseFile() {
|
||||||
|
go open.Run(f.config.GetLicenseFilePath())
|
||||||
|
}
|
||||||
|
|
||||||
// getLocalVersionInfo is identical to bridge.
|
// getLocalVersionInfo is identical to bridge.
|
||||||
func (f *FrontendQt) getLocalVersionInfo() {
|
func (f *FrontendQt) getLocalVersionInfo() {
|
||||||
defer f.Qml.ProcessFinished()
|
defer f.Qml.ProcessFinished()
|
||||||
|
|||||||
@ -73,7 +73,7 @@ func (f *FrontendQt) loadStructuresForImport() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) StartImport(email string) { // TODO email not needed
|
func (f *FrontendQt) StartImport(email string, importEncrypted bool) { // TODO email not needed
|
||||||
log.Trace("Starting import")
|
log.Trace("Starting import")
|
||||||
|
|
||||||
f.Qml.SetProgressDescription("init") // TODO use const
|
f.Qml.SetProgressDescription("init") // TODO use const
|
||||||
@ -84,6 +84,7 @@ func (f *FrontendQt) StartImport(email string) { // TODO email not needed
|
|||||||
f.Qml.SetTotal(1)
|
f.Qml.SetTotal(1)
|
||||||
f.Qml.SetImportLogFileName("")
|
f.Qml.SetImportLogFileName("")
|
||||||
|
|
||||||
|
f.transfer.SetSkipEncryptedMessages(!importEncrypted)
|
||||||
progress := f.transfer.Start()
|
progress := f.transfer.Start()
|
||||||
|
|
||||||
f.Qml.SetImportLogFileName(progress.FileReport())
|
f.Qml.SetImportLogFileName(progress.FileReport())
|
||||||
|
|||||||
@ -73,6 +73,7 @@ type GoQMLInterface struct {
|
|||||||
_ func(okay bool) `signal:"importStructuresLoadFinished"`
|
_ func(okay bool) `signal:"importStructuresLoadFinished"`
|
||||||
_ func() `signal:"openManual"`
|
_ func() `signal:"openManual"`
|
||||||
_ func(showMessage bool) `signal:"runCheckVersion"`
|
_ func(showMessage bool) `signal:"runCheckVersion"`
|
||||||
|
_ func() `slot:"openLicenseFile"`
|
||||||
_ func() `slot:"getLocalVersionInfo"`
|
_ func() `slot:"getLocalVersionInfo"`
|
||||||
_ func() `slot:"loadImportReports"`
|
_ func() `slot:"loadImportReports"`
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ type GoQMLInterface struct {
|
|||||||
_ func() string `slot:"leastUsedColor"`
|
_ func() string `slot:"leastUsedColor"`
|
||||||
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
||||||
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
|
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
|
||||||
_ func(email string) `slot:"startImport"`
|
_ func(email string, importEncrypted bool) `slot:"startImport"`
|
||||||
_ func() `slot:"resetSource"`
|
_ func() `slot:"resetSource"`
|
||||||
|
|
||||||
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetAddress string) `slot:"setupAndLoadForImport"`
|
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetAddress string) `slot:"setupAndLoadForImport"`
|
||||||
@ -167,6 +168,7 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
s.SetIsRestarting(false)
|
s.SetIsRestarting(false)
|
||||||
s.SetProgramTitle(f.programName)
|
s.SetProgramTitle(f.programName)
|
||||||
|
|
||||||
|
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
||||||
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
||||||
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
||||||
s.ConnectGetBackendVersion(func() string {
|
s.ConnectGetBackendVersion(func() string {
|
||||||
|
|||||||
@ -415,6 +415,10 @@ func (s *FrontendQt) isNewVersionAvailable(showMessage bool) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) openLicenseFile() {
|
||||||
|
go open.Run(s.config.GetLicenseFilePath())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) getLocalVersionInfo() {
|
func (s *FrontendQt) getLocalVersionInfo() {
|
||||||
defer s.Qml.ProcessFinished()
|
defer s.Qml.ProcessFinished()
|
||||||
localVersion := s.updates.GetLocalVersion()
|
localVersion := s.updates.GetLocalVersion()
|
||||||
|
|||||||
@ -91,6 +91,7 @@ type GoQMLInterface struct {
|
|||||||
_ func() `slot:"errorSystray"`
|
_ func() `slot:"errorSystray"`
|
||||||
_ func() `slot:"normalSystray"`
|
_ func() `slot:"normalSystray"`
|
||||||
|
|
||||||
|
_ func() `slot:"openLicenseFile"`
|
||||||
_ func() `slot:"getLocalVersionInfo"`
|
_ func() `slot:"getLocalVersionInfo"`
|
||||||
_ func(showMessage bool) `slot:"isNewVersionAvailable"`
|
_ func(showMessage bool) `slot:"isNewVersionAvailable"`
|
||||||
_ func() string `slot:"getBackendVersion"`
|
_ func() string `slot:"getBackendVersion"`
|
||||||
@ -152,6 +153,7 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
s.ConnectClearCache(f.clearCache)
|
s.ConnectClearCache(f.clearCache)
|
||||||
s.ConnectClearKeychain(f.clearKeychain)
|
s.ConnectClearKeychain(f.clearKeychain)
|
||||||
|
|
||||||
|
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
||||||
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
||||||
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
||||||
s.ConnectGetIMAPPort(f.getIMAPPort)
|
s.ConnectGetIMAPPort(f.getIMAPPort)
|
||||||
|
|||||||
@ -46,6 +46,9 @@ type imapBackend struct {
|
|||||||
imapCache map[string]map[string]string
|
imapCache map[string]map[string]string
|
||||||
imapCachePath string
|
imapCachePath string
|
||||||
imapCacheLock *sync.RWMutex
|
imapCacheLock *sync.RWMutex
|
||||||
|
|
||||||
|
updatesBlocking map[string]bool
|
||||||
|
updatesBlockingLocker sync.Locker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIMAPBackend returns struct implementing go-imap/backend interface.
|
// NewIMAPBackend returns struct implementing go-imap/backend interface.
|
||||||
@ -58,10 +61,6 @@ func NewIMAPBackend(
|
|||||||
bridgeWrap := newBridgeWrap(bridge)
|
bridgeWrap := newBridgeWrap(bridge)
|
||||||
backend := newIMAPBackend(panicHandler, cfg, bridgeWrap, eventListener)
|
backend := newIMAPBackend(panicHandler, cfg, bridgeWrap, eventListener)
|
||||||
|
|
||||||
// We want idle updates coming from bridge's updates channel (which in turn come
|
|
||||||
// from the bridge users' stores) to be sent to the imap backend's update channel.
|
|
||||||
backend.updates = bridge.GetIMAPUpdatesChannel()
|
|
||||||
|
|
||||||
go backend.monitorDisconnectedUsers()
|
go backend.monitorDisconnectedUsers()
|
||||||
|
|
||||||
return backend
|
return backend
|
||||||
@ -84,6 +83,9 @@ func newIMAPBackend(
|
|||||||
|
|
||||||
imapCachePath: cfg.GetIMAPCachePath(),
|
imapCachePath: cfg.GetIMAPCachePath(),
|
||||||
imapCacheLock: &sync.RWMutex{},
|
imapCacheLock: &sync.RWMutex{},
|
||||||
|
|
||||||
|
updatesBlocking: map[string]bool{},
|
||||||
|
updatesBlockingLocker: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +171,9 @@ func (ib *imapBackend) Login(_ *imap.ConnInfo, username, password string) (goIMA
|
|||||||
// The update channel should be nil until we try to login to IMAP for the first time
|
// The update channel should be nil until we try to login to IMAP for the first time
|
||||||
// so that it doesn't make bridge slow for users who are only using bridge for SMTP
|
// so that it doesn't make bridge slow for users who are only using bridge for SMTP
|
||||||
// (otherwise the store will be locked for 1 sec per email during synchronization).
|
// (otherwise the store will be locked for 1 sec per email during synchronization).
|
||||||
imapUser.user.SetIMAPIdleUpdateChannel()
|
if store := imapUser.user.GetStore(); store != nil {
|
||||||
|
store.SetChangeNotifier(ib)
|
||||||
|
}
|
||||||
|
|
||||||
return imapUser, nil
|
return imapUser, nil
|
||||||
}
|
}
|
||||||
|
|||||||
169
internal/imap/backend_updates.go
Normal file
169
internal/imap/backend_updates.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package imap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
|
imap "github.com/emersion/go-imap"
|
||||||
|
goIMAPBackend "github.com/emersion/go-imap/backend"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type operation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
operationUpdateMessage operation = "store"
|
||||||
|
operationDeleteMessage operation = "expunge"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ib *imapBackend) setUpdatesBeBlocking(address, mailboxName string, op operation) {
|
||||||
|
ib.changeUpdatesBlocking(address, mailboxName, op, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) unsetUpdatesBeBlocking(address, mailboxName string, op operation) {
|
||||||
|
ib.changeUpdatesBlocking(address, mailboxName, op, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) changeUpdatesBlocking(address, mailboxName string, op operation, block bool) {
|
||||||
|
ib.updatesBlockingLocker.Lock()
|
||||||
|
defer ib.updatesBlockingLocker.Unlock()
|
||||||
|
|
||||||
|
key := strings.ToLower(address + "_" + mailboxName + "_" + string(op))
|
||||||
|
if block {
|
||||||
|
ib.updatesBlocking[key] = true
|
||||||
|
} else {
|
||||||
|
delete(ib.updatesBlocking, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) isBlocking(address, mailboxName string, op operation) bool {
|
||||||
|
key := strings.ToLower(address + "_" + mailboxName + "_" + string(op))
|
||||||
|
return ib.updatesBlocking[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) Notice(address, notice string) {
|
||||||
|
update := new(goIMAPBackend.StatusUpdate)
|
||||||
|
update.Update = goIMAPBackend.NewUpdate(address, "")
|
||||||
|
update.StatusResp = &imap.StatusResp{
|
||||||
|
Type: imap.StatusRespOk,
|
||||||
|
Code: imap.CodeAlert,
|
||||||
|
Info: notice,
|
||||||
|
}
|
||||||
|
ib.sendIMAPUpdate(update, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) UpdateMessage(
|
||||||
|
address, mailboxName string,
|
||||||
|
uid, sequenceNumber uint32,
|
||||||
|
msg *pmapi.Message, hasDeletedFlag bool,
|
||||||
|
) {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"address": address,
|
||||||
|
"mailbox": mailboxName,
|
||||||
|
"seqNum": sequenceNumber,
|
||||||
|
"uid": uid,
|
||||||
|
"flags": message.GetFlags(msg),
|
||||||
|
"deleted": hasDeletedFlag,
|
||||||
|
}).Trace("IDLE update")
|
||||||
|
update := new(goIMAPBackend.MessageUpdate)
|
||||||
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
|
update.Message = imap.NewMessage(sequenceNumber, []imap.FetchItem{imap.FetchFlags, imap.FetchUid})
|
||||||
|
update.Message.Flags = message.GetFlags(msg)
|
||||||
|
if hasDeletedFlag {
|
||||||
|
update.Message.Flags = append(update.Message.Flags, imap.DeletedFlag)
|
||||||
|
}
|
||||||
|
update.Message.Uid = uid
|
||||||
|
ib.sendIMAPUpdate(update, ib.isBlocking(address, mailboxName, operationUpdateMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) DeleteMessage(address, mailboxName string, sequenceNumber uint32) {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"address": address,
|
||||||
|
"mailbox": mailboxName,
|
||||||
|
"seqNum": sequenceNumber,
|
||||||
|
}).Trace("IDLE delete")
|
||||||
|
update := new(goIMAPBackend.ExpungeUpdate)
|
||||||
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
|
update.SeqNum = sequenceNumber
|
||||||
|
ib.sendIMAPUpdate(update, ib.isBlocking(address, mailboxName, operationDeleteMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) MailboxCreated(address, mailboxName string) {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"address": address,
|
||||||
|
"mailbox": mailboxName,
|
||||||
|
}).Trace("IDLE mailbox info")
|
||||||
|
update := new(goIMAPBackend.MailboxInfoUpdate)
|
||||||
|
update.Update = goIMAPBackend.NewUpdate(address, "")
|
||||||
|
update.MailboxInfo = &imap.MailboxInfo{
|
||||||
|
Attributes: []string{imap.NoInferiorsAttr},
|
||||||
|
Delimiter: store.PathDelimiter,
|
||||||
|
Name: mailboxName,
|
||||||
|
}
|
||||||
|
ib.sendIMAPUpdate(update, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) MailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint32) {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"address": address,
|
||||||
|
"mailbox": mailboxName,
|
||||||
|
"total": total,
|
||||||
|
"unread": unread,
|
||||||
|
"unreadSeqNum": unreadSeqNum,
|
||||||
|
}).Trace("IDLE status")
|
||||||
|
update := new(goIMAPBackend.MailboxUpdate)
|
||||||
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
|
update.MailboxStatus = imap.NewMailboxStatus(mailboxName, []imap.StatusItem{imap.StatusMessages, imap.StatusUnseen})
|
||||||
|
update.MailboxStatus.Messages = total
|
||||||
|
update.MailboxStatus.Unseen = unread
|
||||||
|
update.MailboxStatus.UnseenSeqNum = unreadSeqNum
|
||||||
|
ib.sendIMAPUpdate(update, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *imapBackend) sendIMAPUpdate(update goIMAPBackend.Update, block bool) {
|
||||||
|
if ib.updates == nil {
|
||||||
|
log.Trace("IMAP IDLE unavailable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
done := update.Done()
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
log.Warn("IMAP update could not be sent (timeout)")
|
||||||
|
return
|
||||||
|
case ib.updates <- update:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !block {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
log.Warn("IMAP update could not be delivered (timeout).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,7 +40,6 @@ type bridgeUser interface {
|
|||||||
IsCombinedAddressMode() bool
|
IsCombinedAddressMode() bool
|
||||||
GetAddressID(address string) (string, error)
|
GetAddressID(address string) (string, error)
|
||||||
GetPrimaryAddress() string
|
GetPrimaryAddress() string
|
||||||
SetIMAPIdleUpdateChannel()
|
|
||||||
UpdateUser() error
|
UpdateUser() error
|
||||||
Logout() error
|
Logout() error
|
||||||
CloseConnection(address string)
|
CloseConnection(address string)
|
||||||
|
|||||||
39
internal/imap/cache/cache_test.go
vendored
39
internal/imap/cache/cache_test.go
vendored
@ -18,7 +18,6 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -59,34 +58,34 @@ func TestClearOld(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClearBig(t *testing.T) {
|
func TestClearBig(t *testing.T) {
|
||||||
msg := []byte("Test message")
|
r := require.New(t)
|
||||||
|
wantMessage := []byte("Test message")
|
||||||
|
|
||||||
nSize := 3
|
wantCacheSize := 3
|
||||||
cacheSizeLimit = nSize*len(msg) + 1
|
nTestMessages := wantCacheSize * wantCacheSize
|
||||||
cacheTimeLimit = int64(nSize * nSize * 2) // be sure the message will survive
|
cacheSizeLimit = wantCacheSize*len(wantMessage) + 1
|
||||||
|
cacheTimeLimit = int64(1 << 20) // be sure the message will survive
|
||||||
|
|
||||||
// It should have more than nSize items.
|
// It should never have more than nSize items.
|
||||||
for i := 0; i < nSize*nSize; i++ {
|
for i := 0; i < nTestMessages; i++ {
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
SaveMail(fmt.Sprintf("%s%d", testUID, i), msg, bs)
|
SaveMail(fmt.Sprintf("%s%d", testUID, i), wantMessage, bs)
|
||||||
if len(mailCache) > nSize {
|
r.LessOrEqual(len(mailCache), wantCacheSize, "cache too big when %d", i)
|
||||||
t.Error("Number of items in cache should not be more than", nSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the oldest are deleted first.
|
// Check that the oldest are deleted first.
|
||||||
for i := 0; i < nSize*nSize; i++ {
|
for i := 0; i < nTestMessages; i++ {
|
||||||
iUID := fmt.Sprintf("%s%d", testUID, i)
|
iUID := fmt.Sprintf("%s%d", testUID, i)
|
||||||
reader, _ := LoadMail(iUID)
|
reader, _ := LoadMail(iUID)
|
||||||
if i < nSize*(nSize-1) && reader.Len() != 0 {
|
mail := mailCache[iUID]
|
||||||
mail := mailCache[iUID]
|
|
||||||
t.Error("LoadMail should return empty but have:", mail.data, iUID, mail.key.Timestamp)
|
|
||||||
}
|
|
||||||
stored := make([]byte, len(msg))
|
|
||||||
_, _ = reader.Read(stored)
|
|
||||||
|
|
||||||
if i >= nSize*(nSize-1) && !bytes.Equal(stored, msg) {
|
if i < (nTestMessages - wantCacheSize) {
|
||||||
t.Error("LoadMail returned wrong message:", stored, iUID)
|
r.Zero(reader.Len(), "LoadMail should return empty, but have %s for %s time %d ", string(mail.data), iUID, mail.key.Timestamp)
|
||||||
|
} else {
|
||||||
|
stored := make([]byte, len(wantMessage))
|
||||||
|
_, err := reader.Read(stored)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(wantMessage, stored, "LoadMail returned wrong message: %s for %s time %d", stored, iUID, mail.key.Timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,7 +118,7 @@ func (im *imapMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, err
|
|||||||
l.Data["address"] = im.storeAddress.AddressID()
|
l.Data["address"] = im.storeAddress.AddressID()
|
||||||
status := imap.NewMailboxStatus(im.name, items)
|
status := imap.NewMailboxStatus(im.name, items)
|
||||||
status.UidValidity = im.storeMailbox.UIDValidity()
|
status.UidValidity = im.storeMailbox.UIDValidity()
|
||||||
status.PermanentFlags = []string{
|
status.Flags = []string{
|
||||||
imap.SeenFlag, strings.ToUpper(imap.SeenFlag),
|
imap.SeenFlag, strings.ToUpper(imap.SeenFlag),
|
||||||
imap.FlaggedFlag, strings.ToUpper(imap.FlaggedFlag),
|
imap.FlaggedFlag, strings.ToUpper(imap.FlaggedFlag),
|
||||||
imap.DeletedFlag, strings.ToUpper(imap.DeletedFlag),
|
imap.DeletedFlag, strings.ToUpper(imap.DeletedFlag),
|
||||||
@ -127,6 +127,7 @@ func (im *imapMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, err
|
|||||||
message.ThunderbirdJunkFlag,
|
message.ThunderbirdJunkFlag,
|
||||||
message.ThunderbirdNonJunkFlag,
|
message.ThunderbirdNonJunkFlag,
|
||||||
}
|
}
|
||||||
|
status.PermanentFlags = append([]string{}, status.Flags...)
|
||||||
|
|
||||||
dbTotal, dbUnread, dbUnreadSeqNum, err := im.storeMailbox.GetCounts()
|
dbTotal, dbUnread, dbUnreadSeqNum, err := im.storeMailbox.GetCounts()
|
||||||
l.WithFields(logrus.Fields{
|
l.WithFields(logrus.Fields{
|
||||||
@ -177,6 +178,9 @@ func (im *imapMailbox) Check() error {
|
|||||||
// Expunge permanently removes all messages that have the \Deleted flag set
|
// Expunge permanently removes all messages that have the \Deleted flag set
|
||||||
// from the currently selected mailbox.
|
// from the currently selected mailbox.
|
||||||
func (im *imapMailbox) Expunge() error {
|
func (im *imapMailbox) Expunge() error {
|
||||||
|
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
|
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
|
|
||||||
return im.storeMailbox.RemoveDeleted()
|
return im.storeMailbox.RemoveDeleted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,10 @@ import (
|
|||||||
openpgperrors "golang.org/x/crypto/openpgp/errors"
|
openpgperrors "golang.org/x/crypto/openpgp/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rfc822Birthday = time.Date(1982, 8, 13, 0, 0, 0, 0, time.UTC) //nolint[gochecknoglobals]
|
||||||
|
)
|
||||||
|
|
||||||
type doNotCacheError struct{ e error }
|
type doNotCacheError struct{ e error }
|
||||||
|
|
||||||
func (dnc *doNotCacheError) Error() string { return dnc.e.Error() }
|
func (dnc *doNotCacheError) Error() string { return dnc.e.Error() }
|
||||||
@ -605,7 +609,7 @@ func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
tmpBuf := &bytes.Buffer{}
|
tmpBuf := &bytes.Buffer{}
|
||||||
mainHeader := message.GetHeader(m)
|
mainHeader := buildHeader(m)
|
||||||
if err = writeHeader(tmpBuf, mainHeader); err != nil {
|
if err = writeHeader(tmpBuf, mainHeader); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -703,3 +707,23 @@ func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (
|
|||||||
}
|
}
|
||||||
return structure, msgBody, err
|
return structure, msgBody, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildHeader(msg *pmapi.Message) textproto.MIMEHeader {
|
||||||
|
header := message.GetHeader(msg)
|
||||||
|
|
||||||
|
msgTime := time.Unix(msg.Time, 0)
|
||||||
|
|
||||||
|
// Apple Mail crashes fetching messages with date older than 1970.
|
||||||
|
// There is no point having message older than RFC itself, it's not possible.
|
||||||
|
d, err := msg.Header.Date()
|
||||||
|
if err != nil || d.Before(rfc822Birthday) || msgTime.Before(rfc822Birthday) {
|
||||||
|
if err != nil || d.IsZero() {
|
||||||
|
header.Set("X-Original-Date", msgTime.Format(time.RFC1123Z))
|
||||||
|
} else {
|
||||||
|
header.Set("X-Original-Date", d.Format(time.RFC1123Z))
|
||||||
|
}
|
||||||
|
header.Set("Date", rfc822Birthday.Format(time.RFC1123Z))
|
||||||
|
}
|
||||||
|
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|||||||
@ -46,6 +46,9 @@ func (im *imapMailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, operat
|
|||||||
// Called from go-imap in goroutines - we need to handle panics for each function.
|
// Called from go-imap in goroutines - we need to handle panics for each function.
|
||||||
defer im.panicHandler.HandlePanic()
|
defer im.panicHandler.HandlePanic()
|
||||||
|
|
||||||
|
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
||||||
|
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
||||||
|
|
||||||
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
||||||
if err != nil || len(messageIDs) == 0 {
|
if err != nil || len(messageIDs) == 0 {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -126,7 +126,9 @@ func (s *imapServer) ListenAndServe() {
|
|||||||
|
|
||||||
// Stops the server.
|
// Stops the server.
|
||||||
func (s *imapServer) Close() {
|
func (s *imapServer) Close() {
|
||||||
_ = s.server.Close()
|
if err := s.server.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close the connection")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *imapServer) monitorDisconnectedUsers() {
|
func (s *imapServer) monitorDisconnectedUsers() {
|
||||||
@ -139,7 +141,9 @@ func (s *imapServer) monitorDisconnectedUsers() {
|
|||||||
disconnectUser := func(conn imapserver.Conn) {
|
disconnectUser := func(conn imapserver.Conn) {
|
||||||
connUser := conn.Context().User
|
connUser := conn.Context().User
|
||||||
if connUser != nil && strings.EqualFold(connUser.Username(), address) {
|
if connUser != nil && strings.EqualFold(connUser.Username(), address) {
|
||||||
_ = conn.Close()
|
if err := conn.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close the connection")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.server.ForEachConn(disconnectUser)
|
s.server.ForEachConn(disconnectUser)
|
||||||
|
|||||||
@ -43,6 +43,8 @@ type storeUserProvider interface {
|
|||||||
parentID string) (*pmapi.Message, []*pmapi.Attachment, error)
|
parentID string) (*pmapi.Message, []*pmapi.Attachment, error)
|
||||||
|
|
||||||
PauseEventLoop(bool)
|
PauseEventLoop(bool)
|
||||||
|
|
||||||
|
SetChangeNotifier(store.ChangeNotifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeAddressProvider interface {
|
type storeAddressProvider interface {
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./credits.sh at Tue Nov 24 08:56:01 AM CET 2020. DO NOT EDIT.
|
// Code generated by ./credits.sh at Fri Nov 27 09:23:06 CET 2020. DO NOT EDIT.
|
||||||
|
|
||||||
package importexport
|
package importexport
|
||||||
|
|
||||||
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/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;"
|
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-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/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;"
|
||||||
|
|||||||
@ -15,14 +15,14 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./release-notes.sh at 'Wed Nov 11 01:57:14 PM CET 2020'. DO NOT EDIT.
|
// Code generated by ./release-notes.sh at 'Wed Dec 9 05:57:01 PM CET 2020'. DO NOT EDIT.
|
||||||
|
|
||||||
package importexport
|
package importexport
|
||||||
|
|
||||||
const ReleaseNotes = `• Further improvements to address and date parsing
|
const ReleaseNotes = `• Allow an import of already encrypted messages (as cypher text)
|
||||||
• Better handling and displaying of skipped messages
|
• Cosmetic GUI changes
|
||||||
• Improved error reporting
|
• Better error handling
|
||||||
`
|
`
|
||||||
|
|
||||||
const ReleaseFixedBugs = `
|
const ReleaseFixedBugs = `• Installation issues on linux
|
||||||
`
|
`
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/pkg/confirmer"
|
"github.com/ProtonMail/proton-bridge/pkg/confirmer"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
goSMTPBackend "github.com/emersion/go-smtp"
|
goSMTPBackend "github.com/emersion/go-smtp"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ func newSMTPBackend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates a user.
|
// Login authenticates a user.
|
||||||
func (sb *smtpBackend) Login(username, password string) (goSMTPBackend.User, error) {
|
func (sb *smtpBackend) Login(_ *goSMTPBackend.ConnectionState, username, password string) (goSMTPBackend.Session, error) {
|
||||||
// Called from go-smtp in goroutines - we need to handle panics for each function.
|
// Called from go-smtp in goroutines - we need to handle panics for each function.
|
||||||
defer sb.panicHandler.HandlePanic()
|
defer sb.panicHandler.HandlePanic()
|
||||||
username = strings.ToLower(username)
|
username = strings.ToLower(username)
|
||||||
@ -97,7 +98,14 @@ func (sb *smtpBackend) Login(username, password string) (goSMTPBackend.User, err
|
|||||||
if user.IsCombinedAddressMode() {
|
if user.IsCombinedAddressMode() {
|
||||||
addressID = ""
|
addressID = ""
|
||||||
}
|
}
|
||||||
return newSMTPUser(sb.panicHandler, sb.eventListener, sb, user, addressID)
|
return newSMTPUser(sb.panicHandler, sb.eventListener, sb, user, username, addressID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *smtpBackend) AnonymousLogin(_ *goSMTPBackend.ConnectionState) (goSMTPBackend.Session, error) {
|
||||||
|
// Called from go-smtp in goroutines - we need to handle panics for each function.
|
||||||
|
defer sb.panicHandler.HandlePanic()
|
||||||
|
|
||||||
|
return nil, errors.New("anonymous login not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *smtpBackend) shouldReportOutgoingNoEnc() bool {
|
func (sb *smtpBackend) shouldReportOutgoingNoEnc() bool {
|
||||||
|
|||||||
@ -51,12 +51,12 @@ func NewSMTPServer(debug bool, port int, useSSL bool, tls *tls.Config, smtpBacke
|
|||||||
|
|
||||||
s.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server {
|
s.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server {
|
||||||
return sasl.NewLoginServer(func(address, password string) error {
|
return sasl.NewLoginServer(func(address, password string) error {
|
||||||
user, err := conn.Server().Backend.Login(address, password)
|
user, err := conn.Server().Backend.Login(nil, address, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetUser(user)
|
conn.SetSession(user)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -85,14 +85,16 @@ func (s *smtpServer) ListenAndServe() {
|
|||||||
l.Error("SMTP failed: ", err)
|
l.Error("SMTP failed: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer s.server.Close()
|
defer s.server.Close() //nolint[errcheck]
|
||||||
|
|
||||||
l.Info("SMTP server stopped")
|
l.Info("SMTP server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops the server.
|
// Stops the server.
|
||||||
func (s *smtpServer) Close() {
|
func (s *smtpServer) Close() {
|
||||||
s.server.Close()
|
if err := s.server.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close the connection")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *smtpServer) monitorDisconnectedUsers() {
|
func (s *smtpServer) monitorDisconnectedUsers() {
|
||||||
@ -102,9 +104,11 @@ func (s *smtpServer) monitorDisconnectedUsers() {
|
|||||||
for address := range ch {
|
for address := range ch {
|
||||||
log.Info("Disconnecting all open SMTP connections for ", address)
|
log.Info("Disconnecting all open SMTP connections for ", address)
|
||||||
disconnectUser := func(conn *goSMTP.Conn) {
|
disconnectUser := func(conn *goSMTP.Conn) {
|
||||||
connUser := conn.User()
|
connUser := conn.Session()
|
||||||
if connUser != nil {
|
if connUser != nil {
|
||||||
_ = conn.Close()
|
if err := conn.Close(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close the connection")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.server.ForEachConn(disconnectUser)
|
s.server.ForEachConn(disconnectUser)
|
||||||
|
|||||||
@ -44,7 +44,11 @@ type smtpUser struct {
|
|||||||
backend *smtpBackend
|
backend *smtpBackend
|
||||||
user bridgeUser
|
user bridgeUser
|
||||||
storeUser storeUserProvider
|
storeUser storeUserProvider
|
||||||
|
username string
|
||||||
addressID string
|
addressID string
|
||||||
|
|
||||||
|
from string
|
||||||
|
to []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSMTPUser returns struct implementing go-smtp/session interface.
|
// newSMTPUser returns struct implementing go-smtp/session interface.
|
||||||
@ -53,8 +57,9 @@ func newSMTPUser(
|
|||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
smtpBackend *smtpBackend,
|
smtpBackend *smtpBackend,
|
||||||
user bridgeUser,
|
user bridgeUser,
|
||||||
|
username string,
|
||||||
addressID string,
|
addressID string,
|
||||||
) (goSMTPBackend.User, error) {
|
) (goSMTPBackend.Session, error) {
|
||||||
storeUser := user.GetStore()
|
storeUser := user.GetStore()
|
||||||
if storeUser == nil {
|
if storeUser == nil {
|
||||||
return nil, errors.New("user database is not initialized")
|
return nil, errors.New("user database is not initialized")
|
||||||
@ -66,6 +71,7 @@ func newSMTPUser(
|
|||||||
backend: smtpBackend,
|
backend: smtpBackend,
|
||||||
user: user,
|
user: user,
|
||||||
storeUser: storeUser,
|
storeUser: storeUser,
|
||||||
|
username: username,
|
||||||
addressID: addressID,
|
addressID: addressID,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -145,6 +151,55 @@ func (su *smtpUser) getAPIKeyData(recipient string) (apiKeys []pmapi.PublicKey,
|
|||||||
return su.client().GetPublicKeysForEmail(recipient)
|
return su.client().GetPublicKeysForEmail(recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discard currently processed message.
|
||||||
|
func (su *smtpUser) Reset() {
|
||||||
|
log.Trace("Resetting the session")
|
||||||
|
su.from = ""
|
||||||
|
su.to = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set return path for currently processed message.
|
||||||
|
func (su *smtpUser) Mail(from string, opts goSMTPBackend.MailOptions) error {
|
||||||
|
log.WithField("from", from).WithField("opts", opts).Trace("Setting mail from")
|
||||||
|
|
||||||
|
// REQUIRETLS and SMTPUTF8 have to be announced to be used by client.
|
||||||
|
// Bridge does not use those extensions so this should not happen.
|
||||||
|
if opts.RequireTLS {
|
||||||
|
return errors.New("REQUIRETLS extension is not supported")
|
||||||
|
}
|
||||||
|
if opts.UTF8 {
|
||||||
|
return errors.New("SMTPUTF8 extension is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Auth != nil && *opts.Auth != "" && *opts.Auth != su.username {
|
||||||
|
return errors.New("changing identity is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
su.from = from
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add recipient for currently processed message.
|
||||||
|
func (su *smtpUser) Rcpt(to string) error {
|
||||||
|
log.WithField("to", to).Trace("Adding recipient")
|
||||||
|
if to != "" {
|
||||||
|
su.to = append(su.to, to)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set currently processed message contents and send it.
|
||||||
|
func (su *smtpUser) Data(r io.Reader) error {
|
||||||
|
log.Trace("Sending the message")
|
||||||
|
if su.from == "" {
|
||||||
|
return errors.New("missing sender")
|
||||||
|
}
|
||||||
|
if len(su.to) == 0 {
|
||||||
|
return errors.New("missing recipient")
|
||||||
|
}
|
||||||
|
return su.Send(su.from, su.to, r)
|
||||||
|
}
|
||||||
|
|
||||||
// Send sends an email from the given address to the given addresses with the given body.
|
// Send sends an email from the given address to the given addresses with the given body.
|
||||||
func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err error) { //nolint[funlen]
|
func (su *smtpUser) Send(from string, to []string, messageReader io.Reader) (err error) { //nolint[funlen]
|
||||||
// Called from go-smtp in goroutines - we need to handle panics for each function.
|
// Called from go-smtp in goroutines - we need to handle panics for each function.
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func (storeAddress *Address) createOrUpdateMailboxEvent(label *pmapi.Label) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
storeAddress.mailboxes[label.ID] = mailbox
|
storeAddress.mailboxes[label.ID] = mailbox
|
||||||
mailbox.store.imapMailboxCreated(storeAddress.address, mailbox.labelName)
|
mailbox.store.notifyMailboxCreated(storeAddress.address, mailbox.labelName)
|
||||||
} else {
|
} else {
|
||||||
mailbox.labelName = prefix + label.Path
|
mailbox.labelName = prefix + label.Path
|
||||||
mailbox.color = label.Color
|
mailbox.color = label.Color
|
||||||
|
|||||||
@ -18,119 +18,56 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imap "github.com/emersion/go-imap"
|
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetIMAPUpdateChannel sets the channel on which imap update messages will be sent. This should be the channel
|
type ChangeNotifier interface {
|
||||||
// on which the imap backend listens for imap updates.
|
Notice(address, notice string)
|
||||||
func (store *Store) SetIMAPUpdateChannel(updates chan imapBackend.Update) {
|
UpdateMessage(
|
||||||
store.log.Debug("Listening for IMAP updates")
|
address, mailboxName string,
|
||||||
|
uid, sequenceNumber uint32,
|
||||||
if store.imapUpdates = updates; store.imapUpdates == nil {
|
msg *pmapi.Message, hasDeletedFlag bool)
|
||||||
store.log.Error("The IMAP Updates channel is nil")
|
DeleteMessage(address, mailboxName string, sequenceNumber uint32)
|
||||||
}
|
MailboxCreated(address, mailboxName string)
|
||||||
|
MailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint32)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) imapNotice(address, notice string) *imapBackend.StatusUpdate {
|
// SetChangeNotifier sets notifier to be called once mailbox or message changes.
|
||||||
update := new(imapBackend.StatusUpdate)
|
func (store *Store) SetChangeNotifier(notifier ChangeNotifier) {
|
||||||
update.Update = imapBackend.NewUpdate(address, "")
|
store.notifier = notifier
|
||||||
update.StatusResp = &imap.StatusResp{
|
|
||||||
Type: imap.StatusRespOk,
|
|
||||||
Code: imap.CodeAlert,
|
|
||||||
Info: notice,
|
|
||||||
}
|
|
||||||
store.imapSendUpdate(update)
|
|
||||||
return update
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) imapUpdateMessage(
|
func (store *Store) notifyNotice(address, notice string) {
|
||||||
address, mailboxName string,
|
if store.notifier == nil {
|
||||||
uid, sequenceNumber uint32,
|
|
||||||
msg *pmapi.Message, hasDeletedFlag bool,
|
|
||||||
) *imapBackend.MessageUpdate {
|
|
||||||
store.log.WithFields(logrus.Fields{
|
|
||||||
"address": address,
|
|
||||||
"mailbox": mailboxName,
|
|
||||||
"seqNum": sequenceNumber,
|
|
||||||
"uid": uid,
|
|
||||||
"flags": message.GetFlags(msg),
|
|
||||||
"deleted": hasDeletedFlag,
|
|
||||||
}).Trace("IDLE update")
|
|
||||||
update := new(imapBackend.MessageUpdate)
|
|
||||||
update.Update = imapBackend.NewUpdate(address, mailboxName)
|
|
||||||
update.Message = imap.NewMessage(sequenceNumber, []imap.FetchItem{imap.FetchFlags, imap.FetchUid})
|
|
||||||
update.Message.Flags = message.GetFlags(msg)
|
|
||||||
if hasDeletedFlag {
|
|
||||||
update.Message.Flags = append(update.Message.Flags, imap.DeletedFlag)
|
|
||||||
}
|
|
||||||
update.Message.Uid = uid
|
|
||||||
store.imapSendUpdate(update)
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) imapDeleteMessage(address, mailboxName string, sequenceNumber uint32) *imapBackend.ExpungeUpdate {
|
|
||||||
store.log.WithFields(logrus.Fields{
|
|
||||||
"address": address,
|
|
||||||
"mailbox": mailboxName,
|
|
||||||
"seqNum": sequenceNumber,
|
|
||||||
}).Trace("IDLE delete")
|
|
||||||
update := new(imapBackend.ExpungeUpdate)
|
|
||||||
update.Update = imapBackend.NewUpdate(address, mailboxName)
|
|
||||||
update.SeqNum = sequenceNumber
|
|
||||||
store.imapSendUpdate(update)
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) imapMailboxCreated(address, mailboxName string) *imapBackend.MailboxInfoUpdate {
|
|
||||||
store.log.WithFields(logrus.Fields{
|
|
||||||
"address": address,
|
|
||||||
"mailbox": mailboxName,
|
|
||||||
}).Trace("IDLE mailbox info")
|
|
||||||
update := new(imapBackend.MailboxInfoUpdate)
|
|
||||||
update.Update = imapBackend.NewUpdate(address, "")
|
|
||||||
update.MailboxInfo = &imap.MailboxInfo{
|
|
||||||
Attributes: []string{imap.NoInferiorsAttr},
|
|
||||||
Delimiter: PathDelimiter,
|
|
||||||
Name: mailboxName,
|
|
||||||
}
|
|
||||||
store.imapSendUpdate(update)
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) imapMailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint) *imapBackend.MailboxUpdate {
|
|
||||||
store.log.WithFields(logrus.Fields{
|
|
||||||
"address": address,
|
|
||||||
"mailbox": mailboxName,
|
|
||||||
"total": total,
|
|
||||||
"unread": unread,
|
|
||||||
"unreadSeqNum": unreadSeqNum,
|
|
||||||
}).Trace("IDLE status")
|
|
||||||
update := new(imapBackend.MailboxUpdate)
|
|
||||||
update.Update = imapBackend.NewUpdate(address, mailboxName)
|
|
||||||
update.MailboxStatus = imap.NewMailboxStatus(mailboxName, []imap.StatusItem{imap.StatusMessages, imap.StatusUnseen})
|
|
||||||
update.MailboxStatus.Messages = uint32(total)
|
|
||||||
update.MailboxStatus.Unseen = uint32(unread)
|
|
||||||
update.MailboxStatus.UnseenSeqNum = uint32(unreadSeqNum)
|
|
||||||
store.imapSendUpdate(update)
|
|
||||||
return update
|
|
||||||
}
|
|
||||||
|
|
||||||
func (store *Store) imapSendUpdate(update imapBackend.Update) {
|
|
||||||
if store.imapUpdates == nil {
|
|
||||||
store.log.Trace("IMAP IDLE unavailable")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
store.notifier.Notice(address, notice)
|
||||||
select {
|
}
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
store.log.Warn("IMAP update could not be sent (timeout)")
|
func (store *Store) notifyUpdateMessage(address, mailboxName string, uid, sequenceNumber uint32, msg *pmapi.Message, hasDeletedFlag bool) {
|
||||||
return
|
if store.notifier == nil {
|
||||||
case store.imapUpdates <- update:
|
return
|
||||||
}
|
}
|
||||||
|
store.notifier.UpdateMessage(address, mailboxName, uid, sequenceNumber, msg, hasDeletedFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *Store) notifyDeleteMessage(address, mailboxName string, sequenceNumber uint32) {
|
||||||
|
if store.notifier == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.notifier.DeleteMessage(address, mailboxName, sequenceNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *Store) notifyMailboxCreated(address, mailboxName string) {
|
||||||
|
if store.notifier == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.notifier.MailboxCreated(address, mailboxName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *Store) notifyMailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint) {
|
||||||
|
if store.notifier == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.notifier.MailboxStatus(address, mailboxName, uint32(total), uint32(unread), uint32(unreadSeqNum))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,52 +21,43 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateOrUpdateMessageIMAPUpdates(t *testing.T) {
|
func TestNotifyChangeCreateOrUpdateMessage(t *testing.T) {
|
||||||
m, clear := initMocks(t)
|
m, clear := initMocks(t)
|
||||||
defer clear()
|
defer clear()
|
||||||
|
|
||||||
updates := make(chan imapBackend.Update)
|
m.changeNotifier.EXPECT().MailboxStatus(addr1, "All Mail", uint32(1), uint32(0), uint32(0))
|
||||||
|
m.changeNotifier.EXPECT().MailboxStatus(addr1, "All Mail", uint32(2), uint32(0), uint32(0))
|
||||||
|
m.changeNotifier.EXPECT().UpdateMessage(addr1, "All Mail", uint32(1), uint32(1), gomock.Any(), false)
|
||||||
|
m.changeNotifier.EXPECT().UpdateMessage(addr1, "All Mail", uint32(2), uint32(2), gomock.Any(), false)
|
||||||
|
|
||||||
m.newStoreNoEvents(true)
|
m.newStoreNoEvents(true)
|
||||||
m.store.SetIMAPUpdateChannel(updates)
|
m.store.SetChangeNotifier(m.changeNotifier)
|
||||||
|
|
||||||
go checkIMAPUpdates(t, updates, []func(interface{}) bool{
|
|
||||||
checkMessageUpdate(addr1, "All Mail", 1, 1),
|
|
||||||
checkMessageUpdate(addr1, "All Mail", 2, 2),
|
|
||||||
})
|
|
||||||
|
|
||||||
insertMessage(t, m, "msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
insertMessage(t, m, "msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
insertMessage(t, m, "msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
insertMessage(t, m, "msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
|
|
||||||
close(updates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateOrUpdateMessageIMAPUpdatesBulkUpdate(t *testing.T) {
|
func TestNotifyChangeCreateOrUpdateMessages(t *testing.T) {
|
||||||
m, clear := initMocks(t)
|
m, clear := initMocks(t)
|
||||||
defer clear()
|
defer clear()
|
||||||
|
|
||||||
updates := make(chan imapBackend.Update)
|
m.changeNotifier.EXPECT().MailboxStatus(addr1, "All Mail", uint32(2), uint32(0), uint32(0))
|
||||||
|
m.changeNotifier.EXPECT().UpdateMessage(addr1, "All Mail", uint32(1), uint32(1), gomock.Any(), false)
|
||||||
|
m.changeNotifier.EXPECT().UpdateMessage(addr1, "All Mail", uint32(2), uint32(2), gomock.Any(), false)
|
||||||
|
|
||||||
m.newStoreNoEvents(true)
|
m.newStoreNoEvents(true)
|
||||||
m.store.SetIMAPUpdateChannel(updates)
|
m.store.SetChangeNotifier(m.changeNotifier)
|
||||||
|
|
||||||
go checkIMAPUpdates(t, updates, []func(interface{}) bool{
|
|
||||||
checkMessageUpdate(addr1, "All Mail", 1, 1),
|
|
||||||
checkMessageUpdate(addr1, "All Mail", 2, 2),
|
|
||||||
})
|
|
||||||
|
|
||||||
msg1 := getTestMessage("msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
msg1 := getTestMessage("msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
msg2 := getTestMessage("msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
msg2 := getTestMessage("msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
require.Nil(t, m.store.createOrUpdateMessagesEvent([]*pmapi.Message{msg1, msg2}))
|
require.Nil(t, m.store.createOrUpdateMessagesEvent([]*pmapi.Message{msg1, msg2}))
|
||||||
|
|
||||||
close(updates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteMessageIMAPUpdate(t *testing.T) {
|
func TestNotifyChangeDeleteMessage(t *testing.T) {
|
||||||
m, clear := initMocks(t)
|
m, clear := initMocks(t)
|
||||||
defer clear()
|
defer clear()
|
||||||
|
|
||||||
@ -75,55 +66,10 @@ func TestDeleteMessageIMAPUpdate(t *testing.T) {
|
|||||||
insertMessage(t, m, "msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
insertMessage(t, m, "msg1", "Test message 1", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
insertMessage(t, m, "msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
insertMessage(t, m, "msg2", "Test message 2", addrID1, 0, []string{pmapi.AllMailLabel})
|
||||||
|
|
||||||
updates := make(chan imapBackend.Update)
|
m.changeNotifier.EXPECT().DeleteMessage(addr1, "All Mail", uint32(2))
|
||||||
m.store.SetIMAPUpdateChannel(updates)
|
m.changeNotifier.EXPECT().DeleteMessage(addr1, "All Mail", uint32(1))
|
||||||
go checkIMAPUpdates(t, updates, []func(interface{}) bool{
|
|
||||||
checkMessageDelete(addr1, "All Mail", 2),
|
|
||||||
checkMessageDelete(addr1, "All Mail", 1),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
m.store.SetChangeNotifier(m.changeNotifier)
|
||||||
require.Nil(t, m.store.deleteMessageEvent("msg2"))
|
require.Nil(t, m.store.deleteMessageEvent("msg2"))
|
||||||
require.Nil(t, m.store.deleteMessageEvent("msg1"))
|
require.Nil(t, m.store.deleteMessageEvent("msg1"))
|
||||||
close(updates)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIMAPUpdates(t *testing.T, updates chan imapBackend.Update, checkFunctions []func(interface{}) bool) {
|
|
||||||
idx := 0
|
|
||||||
for update := range updates {
|
|
||||||
if idx >= len(checkFunctions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !checkFunctions[idx](update) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
require.True(t, idx == len(checkFunctions), "Less updates than expected: %+v of %+v", idx, len(checkFunctions))
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMessageUpdate(username, mailbox string, seqNum, uid int) func(interface{}) bool { //nolint[unparam]
|
|
||||||
return func(update interface{}) bool {
|
|
||||||
switch u := update.(type) {
|
|
||||||
case *imapBackend.MessageUpdate:
|
|
||||||
return (u.Update.Username() == username &&
|
|
||||||
u.Update.Mailbox() == mailbox &&
|
|
||||||
u.Message.SeqNum == uint32(seqNum) &&
|
|
||||||
u.Message.Uid == uint32(uid))
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMessageDelete(username, mailbox string, seqNum int) func(interface{}) bool { //nolint[unparam]
|
|
||||||
return func(update interface{}) bool {
|
|
||||||
switch u := update.(type) {
|
|
||||||
case *imapBackend.ExpungeUpdate:
|
|
||||||
return (u.Update.Username() == username &&
|
|
||||||
u.Update.Mailbox() == mailbox &&
|
|
||||||
u.SeqNum == uint32(seqNum))
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -571,7 +571,7 @@ func (loop *eventLoop) processNotices(l *logrus.Entry, notices []string) {
|
|||||||
for _, notice := range notices {
|
for _, notice := range notices {
|
||||||
l.Infof("Notice: %q", notice)
|
l.Infof("Notice: %q", notice)
|
||||||
for _, address := range loop.user.GetStoreAddresses() {
|
for _, address := range loop.user.GetStoreAddresses() {
|
||||||
loop.store.imapNotice(address, notice)
|
loop.store.notifyNotice(address, notice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,10 +53,10 @@ func TestEventLoopProcessMoreEvents(t *testing.T) {
|
|||||||
}, nil),
|
}, nil),
|
||||||
)
|
)
|
||||||
m.newStoreNoEvents(true)
|
m.newStoreNoEvents(true)
|
||||||
m.client.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
|
|
||||||
|
|
||||||
// Event loop runs in goroutine and will be stopped by deferred mock clearing.
|
// Event loop runs in goroutine started during store creation (newStoreNoEvents).
|
||||||
go m.store.eventLoop.start()
|
// Force to run the next event.
|
||||||
|
m.store.eventLoop.pollNow()
|
||||||
|
|
||||||
// More events are processed right away.
|
// More events are processed right away.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
@ -78,38 +77,42 @@ func TestEventLoopUpdateMessageFromLoop(t *testing.T) {
|
|||||||
subject := "old subject"
|
subject := "old subject"
|
||||||
newSubject := "new subject"
|
newSubject := "new subject"
|
||||||
|
|
||||||
// First sync will add message with old subject to database.
|
m.newStoreNoEvents(true, &pmapi.Message{
|
||||||
m.client.EXPECT().GetMessage("msg1").Return(&pmapi.Message{
|
|
||||||
ID: "msg1",
|
ID: "msg1",
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
}, nil)
|
})
|
||||||
// Event will update the subject.
|
|
||||||
m.client.EXPECT().GetEvent("latestEventID").Return(&pmapi.Event{
|
|
||||||
EventID: "event1",
|
|
||||||
Messages: []*pmapi.EventMessage{{
|
|
||||||
EventItem: pmapi.EventItem{
|
|
||||||
ID: "msg1",
|
|
||||||
Action: pmapi.EventUpdate,
|
|
||||||
},
|
|
||||||
Updated: &pmapi.EventMessageUpdated{
|
|
||||||
ID: "msg1",
|
|
||||||
Subject: &newSubject,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
m.newStoreNoEvents(true)
|
eventReceived := make(chan struct{})
|
||||||
|
m.client.EXPECT().GetEvent("latestEventID").DoAndReturn(func(eventID string) (*pmapi.Event, error) {
|
||||||
|
defer close(eventReceived)
|
||||||
|
return &pmapi.Event{
|
||||||
|
EventID: "event1",
|
||||||
|
Messages: []*pmapi.EventMessage{{
|
||||||
|
EventItem: pmapi.EventItem{
|
||||||
|
ID: "msg1",
|
||||||
|
Action: pmapi.EventUpdate,
|
||||||
|
},
|
||||||
|
Updated: &pmapi.EventMessageUpdated{
|
||||||
|
ID: "msg1",
|
||||||
|
Subject: &newSubject,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
// Event loop runs in goroutine and will be stopped by deferred mock clearing.
|
// Event loop runs in goroutine started during store creation (newStoreNoEvents).
|
||||||
go m.store.eventLoop.start()
|
// Force to run the next event.
|
||||||
|
m.store.eventLoop.pollNow()
|
||||||
|
|
||||||
var err error
|
select {
|
||||||
assert.Eventually(t, func() bool {
|
case <-eventReceived:
|
||||||
var msg *pmapi.Message
|
case <-time.After(5 * time.Second):
|
||||||
msg, err = m.store.getMessageFromDB("msg1")
|
require.Fail(t, "latestEventID was not processed")
|
||||||
return err == nil && msg.Subject == newSubject
|
}
|
||||||
}, time.Second, 10*time.Millisecond)
|
|
||||||
|
msg, err := m.store.getMessageFromDB("msg1")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newSubject, msg.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEventLoopUpdateMessage(t *testing.T) {
|
func TestEventLoopUpdateMessage(t *testing.T) {
|
||||||
|
|||||||
@ -18,8 +18,6 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -233,6 +231,7 @@ func (storeMailbox *Mailbox) RemoveDeleted() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case pmapi.DraftLabel:
|
case pmapi.DraftLabel:
|
||||||
|
storeMailbox.log.WithField("ids", apiIDs).Warn("Deleting drafts")
|
||||||
if err := storeMailbox.client().DeleteMessages(apiIDs); err != nil {
|
if err := storeMailbox.client().DeleteMessages(apiIDs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -280,6 +279,7 @@ func (storeMailbox *Mailbox) deleteFromTrashOrSpam(apiIDs []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(messageIDsToDelete) > 0 {
|
if len(messageIDsToDelete) > 0 {
|
||||||
|
storeMailbox.log.WithField("ids", messageIDsToDelete).Warn("Deleting messages")
|
||||||
if err := storeMailbox.client().DeleteMessages(messageIDsToDelete); err != nil {
|
if err := storeMailbox.client().DeleteMessages(messageIDsToDelete); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -356,7 +356,7 @@ func (storeMailbox *Mailbox) txCreateOrUpdateMessages(tx *bolt.Tx, msgs []*pmapi
|
|||||||
}
|
}
|
||||||
isMarkedAsDeleted := deletedBucket.Get([]byte(msg.ID)) != nil
|
isMarkedAsDeleted := deletedBucket.Get([]byte(msg.ID)) != nil
|
||||||
if seqErr == nil {
|
if seqErr == nil {
|
||||||
storeMailbox.store.imapUpdateMessage(
|
storeMailbox.store.notifyUpdateMessage(
|
||||||
storeMailbox.storeAddress.address,
|
storeMailbox.storeAddress.address,
|
||||||
storeMailbox.labelName,
|
storeMailbox.labelName,
|
||||||
btoi(uidb),
|
btoi(uidb),
|
||||||
@ -390,7 +390,7 @@ func (storeMailbox *Mailbox) txCreateOrUpdateMessages(tx *bolt.Tx, msgs []*pmapi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot get sequence number from UID")
|
return errors.Wrap(err, "cannot get sequence number from UID")
|
||||||
}
|
}
|
||||||
storeMailbox.store.imapUpdateMessage(
|
storeMailbox.store.notifyUpdateMessage(
|
||||||
storeMailbox.storeAddress.address,
|
storeMailbox.storeAddress.address,
|
||||||
storeMailbox.labelName,
|
storeMailbox.labelName,
|
||||||
uid,
|
uid,
|
||||||
@ -441,7 +441,7 @@ func (storeMailbox *Mailbox) txDeleteMessage(tx *bolt.Tx, apiID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if seqNumErr == nil {
|
if seqNumErr == nil {
|
||||||
storeMailbox.store.imapDeleteMessage(
|
storeMailbox.store.notifyDeleteMessage(
|
||||||
storeMailbox.storeAddress.address,
|
storeMailbox.storeAddress.address,
|
||||||
storeMailbox.labelName,
|
storeMailbox.labelName,
|
||||||
seqNum,
|
seqNum,
|
||||||
@ -459,7 +459,7 @@ func (storeMailbox *Mailbox) txMailboxStatusUpdate(tx *bolt.Tx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot get counts for mailbox status update")
|
return errors.Wrap(err, "cannot get counts for mailbox status update")
|
||||||
}
|
}
|
||||||
storeMailbox.store.imapMailboxStatus(
|
storeMailbox.store.notifyMailboxStatus(
|
||||||
storeMailbox.storeAddress.address,
|
storeMailbox.storeAddress.address,
|
||||||
storeMailbox.labelName,
|
storeMailbox.labelName,
|
||||||
total,
|
total,
|
||||||
@ -503,7 +503,7 @@ func (storeMailbox *Mailbox) txMarkMessagesAsDeleted(tx *bolt.Tx, apiIDs []strin
|
|||||||
|
|
||||||
// In order to send flags in format
|
// In order to send flags in format
|
||||||
// S: * 2 FETCH (FLAGS (\Deleted \Seen))
|
// S: * 2 FETCH (FLAGS (\Deleted \Seen))
|
||||||
update := storeMailbox.store.imapUpdateMessage(
|
storeMailbox.store.notifyUpdateMessage(
|
||||||
storeMailbox.storeAddress.address,
|
storeMailbox.storeAddress.address,
|
||||||
storeMailbox.labelName,
|
storeMailbox.labelName,
|
||||||
uid,
|
uid,
|
||||||
@ -511,14 +511,6 @@ func (storeMailbox *Mailbox) txMarkMessagesAsDeleted(tx *bolt.Tx, apiIDs []strin
|
|||||||
msg,
|
msg,
|
||||||
markAsDeleted,
|
markAsDeleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
// txMarkMessagesAsDeleted is called only during processing request
|
|
||||||
// from IMAP call (i.e., not from event loop) and in such cases we
|
|
||||||
// have to wait to propagate update back before closing the response.
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
case <-update.Done():
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/ProtonMail/proton-bridge/internal/store (interfaces: PanicHandler,ClientManager,BridgeUser)
|
// Source: github.com/ProtonMail/proton-bridge/internal/store (interfaces: PanicHandler,ClientManager,BridgeUser,ChangeNotifier)
|
||||||
|
|
||||||
// Package mocks is a generated GoMock package.
|
// Package mocks is a generated GoMock package.
|
||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockPanicHandler is a mock of PanicHandler interface
|
// MockPanicHandler is a mock of PanicHandler interface
|
||||||
@ -242,3 +241,86 @@ func (mr *MockBridgeUserMockRecorder) UpdateUser() *gomock.Call {
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockChangeNotifier is a mock of ChangeNotifier interface
|
||||||
|
type MockChangeNotifier struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockChangeNotifierMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier
|
||||||
|
type MockChangeNotifierMockRecorder struct {
|
||||||
|
mock *MockChangeNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockChangeNotifier creates a new mock instance
|
||||||
|
func NewMockChangeNotifier(ctrl *gomock.Controller) *MockChangeNotifier {
|
||||||
|
mock := &MockChangeNotifier{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockChangeNotifierMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMessage mocks base method
|
||||||
|
func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "DeleteMessage", arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMessage indicates an expected call of DeleteMessage
|
||||||
|
func (mr *MockChangeNotifierMockRecorder) DeleteMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChangeNotifier)(nil).DeleteMessage), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MailboxCreated mocks base method
|
||||||
|
func (m *MockChangeNotifier) MailboxCreated(arg0, arg1 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "MailboxCreated", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MailboxCreated indicates an expected call of MailboxCreated
|
||||||
|
func (mr *MockChangeNotifierMockRecorder) MailboxCreated(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxCreated", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxCreated), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MailboxStatus mocks base method
|
||||||
|
func (m *MockChangeNotifier) MailboxStatus(arg0, arg1 string, arg2, arg3, arg4 uint32) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "MailboxStatus", arg0, arg1, arg2, arg3, arg4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MailboxStatus indicates an expected call of MailboxStatus
|
||||||
|
func (mr *MockChangeNotifierMockRecorder) MailboxStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxStatus", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxStatus), arg0, arg1, arg2, arg3, arg4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice mocks base method
|
||||||
|
func (m *MockChangeNotifier) Notice(arg0, arg1 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Notice", arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice indicates an expected call of Notice
|
||||||
|
func (mr *MockChangeNotifierMockRecorder) Notice(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notice", reflect.TypeOf((*MockChangeNotifier)(nil).Notice), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMessage mocks base method
|
||||||
|
func (m *MockChangeNotifier) UpdateMessage(arg0, arg1 string, arg2, arg3 uint32, arg4 *pmapi.Message, arg5 bool) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "UpdateMessage", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMessage indicates an expected call of UpdateMessage
|
||||||
|
func (mr *MockChangeNotifierMockRecorder) UpdateMessage(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessage", reflect.TypeOf((*MockChangeNotifier)(nil).UpdateMessage), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
|
}
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -100,12 +99,12 @@ type Store struct {
|
|||||||
|
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
|
|
||||||
cache *Cache
|
cache *Cache
|
||||||
filePath string
|
filePath string
|
||||||
db *bolt.DB
|
db *bolt.DB
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
addresses map[string]*Address
|
addresses map[string]*Address
|
||||||
imapUpdates chan imapBackend.Update
|
notifier ChangeNotifier
|
||||||
|
|
||||||
isSyncRunning bool
|
isSyncRunning bool
|
||||||
syncCooldown cooldown
|
syncCooldown cooldown
|
||||||
|
|||||||
@ -18,11 +18,12 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
storemocks "github.com/ProtonMail/proton-bridge/internal/store/mocks"
|
storemocks "github.com/ProtonMail/proton-bridge/internal/store/mocks"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
@ -43,13 +44,14 @@ const (
|
|||||||
type mocksForStore struct {
|
type mocksForStore struct {
|
||||||
tb testing.TB
|
tb testing.TB
|
||||||
|
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
events *storemocks.MockListener
|
events *storemocks.MockListener
|
||||||
user *storemocks.MockBridgeUser
|
user *storemocks.MockBridgeUser
|
||||||
client *pmapimocks.MockClient
|
client *pmapimocks.MockClient
|
||||||
clientManager *storemocks.MockClientManager
|
clientManager *storemocks.MockClientManager
|
||||||
panicHandler *storemocks.MockPanicHandler
|
panicHandler *storemocks.MockPanicHandler
|
||||||
store *Store
|
changeNotifier *storemocks.MockChangeNotifier
|
||||||
|
store *Store
|
||||||
|
|
||||||
tmpDir string
|
tmpDir string
|
||||||
cache *Cache
|
cache *Cache
|
||||||
@ -58,13 +60,14 @@ type mocksForStore struct {
|
|||||||
func initMocks(tb testing.TB) (*mocksForStore, func()) {
|
func initMocks(tb testing.TB) (*mocksForStore, func()) {
|
||||||
ctrl := gomock.NewController(tb)
|
ctrl := gomock.NewController(tb)
|
||||||
mocks := &mocksForStore{
|
mocks := &mocksForStore{
|
||||||
tb: tb,
|
tb: tb,
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
events: storemocks.NewMockListener(ctrl),
|
events: storemocks.NewMockListener(ctrl),
|
||||||
user: storemocks.NewMockBridgeUser(ctrl),
|
user: storemocks.NewMockBridgeUser(ctrl),
|
||||||
client: pmapimocks.NewMockClient(ctrl),
|
client: pmapimocks.NewMockClient(ctrl),
|
||||||
clientManager: storemocks.NewMockClientManager(ctrl),
|
clientManager: storemocks.NewMockClientManager(ctrl),
|
||||||
panicHandler: storemocks.NewMockPanicHandler(ctrl),
|
panicHandler: storemocks.NewMockPanicHandler(ctrl),
|
||||||
|
changeNotifier: storemocks.NewMockChangeNotifier(ctrl),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called during clean-up.
|
// Called during clean-up.
|
||||||
@ -89,7 +92,7 @@ func initMocks(tb testing.TB) (*mocksForStore, func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool) { //nolint[unparam]
|
func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool, msgs ...*pmapi.Message) { //nolint[unparam]
|
||||||
mocks.user.EXPECT().ID().Return("userID").AnyTimes()
|
mocks.user.EXPECT().ID().Return("userID").AnyTimes()
|
||||||
mocks.user.EXPECT().IsConnected().Return(true)
|
mocks.user.EXPECT().IsConnected().Return(true)
|
||||||
mocks.user.EXPECT().IsCombinedAddressMode().Return(combinedMode)
|
mocks.user.EXPECT().IsCombinedAddressMode().Return(combinedMode)
|
||||||
@ -102,20 +105,19 @@ func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool) { //nolint[unpar
|
|||||||
})
|
})
|
||||||
mocks.client.EXPECT().ListLabels()
|
mocks.client.EXPECT().ListLabels()
|
||||||
mocks.client.EXPECT().CountMessages("")
|
mocks.client.EXPECT().CountMessages("")
|
||||||
mocks.client.EXPECT().GetEvent(gomock.Any()).
|
|
||||||
Return(&pmapi.Event{
|
|
||||||
EventID: "latestEventID",
|
|
||||||
}, nil).AnyTimes()
|
|
||||||
|
|
||||||
// We want to wait until first sync has finished.
|
// Call to get latest event ID and then to process first event.
|
||||||
firstSyncWaiter := sync.WaitGroup{}
|
mocks.client.EXPECT().GetEvent("").Return(&pmapi.Event{
|
||||||
firstSyncWaiter.Add(1)
|
EventID: "firstEventID",
|
||||||
mocks.client.EXPECT().
|
}, nil)
|
||||||
ListMessages(gomock.Any()).
|
mocks.client.EXPECT().GetEvent("firstEventID").Return(&pmapi.Event{
|
||||||
DoAndReturn(func(*pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
EventID: "latestEventID",
|
||||||
firstSyncWaiter.Done()
|
}, nil)
|
||||||
return []*pmapi.Message{}, 0, nil
|
|
||||||
})
|
mocks.client.EXPECT().ListMessages(gomock.Any()).Return(msgs, len(msgs), nil).AnyTimes()
|
||||||
|
for _, msg := range msgs {
|
||||||
|
mocks.client.EXPECT().GetMessage(msg.ID).Return(msg, nil).AnyTimes()
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
mocks.store, err = New(
|
mocks.store, err = New(
|
||||||
@ -128,6 +130,16 @@ func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool) { //nolint[unpar
|
|||||||
)
|
)
|
||||||
require.NoError(mocks.tb, err)
|
require.NoError(mocks.tb, err)
|
||||||
|
|
||||||
// Wait for sync to finish.
|
// We want to wait until first sync has finished.
|
||||||
firstSyncWaiter.Wait()
|
require.Eventually(mocks.tb, func() bool {
|
||||||
|
for _, msg := range msgs {
|
||||||
|
_, err := mocks.store.getMessageFromDB(msg.ID)
|
||||||
|
if err != nil {
|
||||||
|
// To see in test result the latest error for debugging.
|
||||||
|
fmt.Println("Sync wait error:", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, 5*time.Second, 10*time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,6 +124,12 @@ func (p *PMAPIProvider) importDraft(msg Message, globalMailbox *Mailbox) (string
|
|||||||
return "", errors.Wrap(err, "failed to parse message")
|
return "", errors.Wrap(err, "failed to parse message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trying to encrypt an encrypted draft will return an error;
|
||||||
|
// users are forbidden to import messages encrypted with foreign keys to drafts.
|
||||||
|
if message.IsEncrypted() {
|
||||||
|
return "", errors.New("refusing to import draft encrypted by foreign key")
|
||||||
|
}
|
||||||
|
|
||||||
p.timeIt.start("encrypt", msg.ID)
|
p.timeIt.start("encrypt", msg.ID)
|
||||||
err = message.Encrypt(p.keyRing, nil)
|
err = message.Encrypt(p.keyRing, nil)
|
||||||
p.timeIt.stop("encrypt", msg.ID)
|
p.timeIt.stop("encrypt", msg.ID)
|
||||||
@ -171,13 +177,12 @@ func (p *PMAPIProvider) importDraft(msg Message, globalMailbox *Mailbox) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PMAPIProvider) transferMessage(rules transferRules, progress *Progress, msg Message, preparedImportRequestsCh chan map[string]*pmapi.ImportMsgReq) {
|
func (p *PMAPIProvider) transferMessage(rules transferRules, progress *Progress, msg Message, preparedImportRequestsCh chan map[string]*pmapi.ImportMsgReq) {
|
||||||
importMsgReq, err := p.generateImportMsgReq(msg, rules.globalMailbox)
|
importMsgReq, err := p.generateImportMsgReq(rules, progress, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress.messageImported(msg.ID, "", err)
|
progress.messageImported(msg.ID, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if importMsgReq == nil || progress.shouldStop() {
|
||||||
if progress.shouldStop() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,17 +196,26 @@ func (p *PMAPIProvider) transferMessage(rules transferRules, progress *Progress,
|
|||||||
p.nextImportRequestsSize += importMsgReqSize
|
p.nextImportRequestsSize += importMsgReqSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PMAPIProvider) generateImportMsgReq(msg Message, globalMailbox *Mailbox) (*pmapi.ImportMsgReq, error) {
|
func (p *PMAPIProvider) generateImportMsgReq(rules transferRules, progress *Progress, msg Message) (*pmapi.ImportMsgReq, error) {
|
||||||
message, attachmentReaders, err := p.parseMessage(msg)
|
message, attachmentReaders, err := p.parseMessage(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse message")
|
return nil, errors.Wrap(err, "failed to parse message")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.timeIt.start("encrypt", msg.ID)
|
var body []byte
|
||||||
body, err := p.encryptMessage(message, attachmentReaders)
|
if message.IsEncrypted() {
|
||||||
p.timeIt.stop("encrypt", msg.ID)
|
if rules.skipEncryptedMessages {
|
||||||
if err != nil {
|
progress.messageSkipped(msg.ID)
|
||||||
return nil, errors.Wrap(err, "failed to encrypt message")
|
return nil, nil
|
||||||
|
}
|
||||||
|
body = msg.Body
|
||||||
|
} else {
|
||||||
|
p.timeIt.start("encrypt", msg.ID)
|
||||||
|
body, err = p.encryptMessage(message, attachmentReaders)
|
||||||
|
p.timeIt.stop("encrypt", msg.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to encrypt message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unread := 0
|
unread := 0
|
||||||
@ -216,8 +230,8 @@ func (p *PMAPIProvider) generateImportMsgReq(msg Message, globalMailbox *Mailbox
|
|||||||
labelIDs = append(labelIDs, target.ID)
|
labelIDs = append(labelIDs, target.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if globalMailbox != nil {
|
if rules.globalMailbox != nil {
|
||||||
labelIDs = append(labelIDs, globalMailbox.ID)
|
labelIDs = append(labelIDs, rules.globalMailbox.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pmapi.ImportMsgReq{
|
return &pmapi.ImportMsgReq{
|
||||||
|
|||||||
@ -85,7 +85,7 @@ func testTransferFrom(t *testing.T, rules transferRules, provider TargetProvider
|
|||||||
progress.finish()
|
progress.finish()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
maxWait := time.Duration(len(messages)) * time.Second
|
maxWait := time.Duration(len(messages)) * 2 * time.Second
|
||||||
a.Eventually(t, func() bool {
|
a.Eventually(t, func() bool {
|
||||||
return progress.updateCh == nil
|
return progress.updateCh == nil
|
||||||
}, maxWait, 10*time.Millisecond, "Waiting for imported messages timed out")
|
}, maxWait, 10*time.Millisecond, "Waiting for imported messages timed out")
|
||||||
|
|||||||
@ -49,7 +49,7 @@ type transferRules struct {
|
|||||||
globalToTime int64
|
globalToTime int64
|
||||||
|
|
||||||
// skipEncryptedMessages determines whether message which cannot
|
// skipEncryptedMessages determines whether message which cannot
|
||||||
// be decrypted should be exported or skipped.
|
// be decrypted should be imported/exported or skipped.
|
||||||
skipEncryptedMessages bool
|
skipEncryptedMessages bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,7 @@ func (t *Transfer) setDefaultRules() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetSkipEncryptedMessages sets whether message which cannot be decrypted
|
// SetSkipEncryptedMessages sets whether message which cannot be decrypted
|
||||||
// should be exported or skipped.
|
// should be imported/exported or skipped.
|
||||||
func (t *Transfer) SetSkipEncryptedMessages(skip bool) {
|
func (t *Transfer) SetSkipEncryptedMessages(skip bool) {
|
||||||
t.rules.setSkipEncryptedMessages(skip)
|
t.rules.setSkipEncryptedMessages(skip)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -43,8 +42,6 @@ type User struct {
|
|||||||
clientManager ClientManager
|
clientManager ClientManager
|
||||||
credStorer CredentialsStorer
|
credStorer CredentialsStorer
|
||||||
|
|
||||||
imapUpdatesChannel chan imapBackend.Update
|
|
||||||
|
|
||||||
storeFactory StoreMaker
|
storeFactory StoreMaker
|
||||||
store *store.Store
|
store *store.Store
|
||||||
|
|
||||||
@ -95,7 +92,7 @@ func (u *User) client() pmapi.Client {
|
|||||||
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
||||||
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
||||||
// something in the store changed).
|
// something in the store changed).
|
||||||
func (u *User) init(idleUpdates chan imapBackend.Update) (err error) {
|
func (u *User) init() (err error) {
|
||||||
u.log.Info("Initialising user")
|
u.log.Info("Initialising user")
|
||||||
|
|
||||||
// Reload the user's credentials (if they log out and back in we need the new
|
// Reload the user's credentials (if they log out and back in we need the new
|
||||||
@ -134,20 +131,9 @@ func (u *User) init(idleUpdates chan imapBackend.Update) (err error) {
|
|||||||
}
|
}
|
||||||
u.store = store
|
u.store = store
|
||||||
|
|
||||||
// Save the imap updates channel here so it can be set later when imap connects.
|
|
||||||
u.imapUpdatesChannel = idleUpdates
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetIMAPIdleUpdateChannel() {
|
|
||||||
if u.store == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u.store.SetIMAPUpdateChannel(u.imapUpdatesChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorizeIfNecessary checks whether user is logged in and is connected to api auth channel.
|
// authorizeIfNecessary checks whether user is logged in and is connected to api auth channel.
|
||||||
// If user is not already connected to the api auth channel (for example there was no internet during start),
|
// If user is not already connected to the api auth channel (for example there was no internet during start),
|
||||||
// it tries to connect it.
|
// it tries to connect it.
|
||||||
@ -539,7 +525,7 @@ func (u *User) CloseAllConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u.store != nil {
|
if u.store != nil {
|
||||||
u.store.SetIMAPUpdateChannel(nil)
|
u.store.SetChangeNotifier(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -221,7 +221,7 @@ func TestCheckBridgeLoginLoggedOut(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||||
)
|
)
|
||||||
|
|
||||||
err = user.init(nil)
|
err = user.init()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
defer cleanUpUserData(user)
|
defer cleanUpUserData(user)
|
||||||
|
|||||||
@ -139,7 +139,7 @@ func checkNewUserHasCredentials(creds *credentials.Credentials, m mocks) {
|
|||||||
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||||
defer cleanUpUserData(user)
|
defer cleanUpUserData(user)
|
||||||
|
|
||||||
_ = user.init(nil)
|
_ = user.init()
|
||||||
|
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
|
|
||||||
|
|||||||
@ -20,8 +20,6 @@ package users
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -31,17 +29,12 @@ func testNewUser(m mocks) *User {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
mockConnectedUser(m)
|
mockConnectedUser(m)
|
||||||
|
mockEventLoopNoAction(m)
|
||||||
gomock.InOrder(
|
|
||||||
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).MaxTimes(1),
|
|
||||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).MaxTimes(1),
|
|
||||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).MaxTimes(1),
|
|
||||||
)
|
|
||||||
|
|
||||||
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||||
assert.NoError(m.t, err)
|
assert.NoError(m.t, err)
|
||||||
|
|
||||||
err = user.init(nil)
|
err = user.init()
|
||||||
assert.NoError(m.t, err)
|
assert.NoError(m.t, err)
|
||||||
|
|
||||||
mockAuthUpdate(user, "reftok", m)
|
mockAuthUpdate(user, "reftok", m)
|
||||||
@ -53,17 +46,12 @@ func testNewUserForLogout(m mocks) *User {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
mockConnectedUser(m)
|
mockConnectedUser(m)
|
||||||
|
mockEventLoopNoAction(m)
|
||||||
gomock.InOrder(
|
|
||||||
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).MaxTimes(1),
|
|
||||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).MaxTimes(1),
|
|
||||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).MaxTimes(1),
|
|
||||||
)
|
|
||||||
|
|
||||||
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||||
assert.NoError(m.t, err)
|
assert.NoError(m.t, err)
|
||||||
|
|
||||||
err = user.init(nil)
|
err = user.init()
|
||||||
assert.NoError(m.t, err)
|
assert.NoError(m.t, err)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
logrus "github.com/sirupsen/logrus"
|
logrus "github.com/sirupsen/logrus"
|
||||||
@ -58,11 +57,6 @@ type Users struct {
|
|||||||
// as is, without requesting server again.
|
// as is, without requesting server again.
|
||||||
useOnlyActiveAddresses bool
|
useOnlyActiveAddresses bool
|
||||||
|
|
||||||
// idleUpdates is a channel which the imap backend listens to and which it
|
|
||||||
// uses to send idle updates to the mail client (eg thunderbird).
|
|
||||||
// The user stores should send idle updates on this channel.
|
|
||||||
idleUpdates chan imapBackend.Update
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
// stopAll can be closed to stop all goroutines from looping (watchAppOutdated, watchAPIAuths, heartbeat etc).
|
// stopAll can be closed to stop all goroutines from looping (watchAppOutdated, watchAPIAuths, heartbeat etc).
|
||||||
@ -88,7 +82,6 @@ func New(
|
|||||||
credStorer: credStorer,
|
credStorer: credStorer,
|
||||||
storeFactory: storeFactory,
|
storeFactory: storeFactory,
|
||||||
useOnlyActiveAddresses: useOnlyActiveAddresses,
|
useOnlyActiveAddresses: useOnlyActiveAddresses,
|
||||||
idleUpdates: make(chan imapBackend.Update),
|
|
||||||
lock: sync.RWMutex{},
|
lock: sync.RWMutex{},
|
||||||
stopAll: make(chan struct{}),
|
stopAll: make(chan struct{}),
|
||||||
}
|
}
|
||||||
@ -132,7 +125,7 @@ func (u *Users) loadUsersFromCredentialsStore() (err error) {
|
|||||||
|
|
||||||
u.users = append(u.users, user)
|
u.users = append(u.users, user)
|
||||||
|
|
||||||
if initUserErr := user.init(u.idleUpdates); initUserErr != nil {
|
if initUserErr := user.init(); initUserErr != nil {
|
||||||
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,7 +278,7 @@ func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassphra
|
|||||||
return errors.Wrap(err, "failed to update token of user in credentials store")
|
return errors.Wrap(err, "failed to update token of user in credentials store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = user.init(u.idleUpdates); err != nil {
|
if err = user.init(); err != nil {
|
||||||
return errors.Wrap(err, "failed to initialise user")
|
return errors.Wrap(err, "failed to initialise user")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,7 +319,7 @@ func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassphra
|
|||||||
// The user needs to be part of the users list in order for it to receive an auth during initialisation.
|
// The user needs to be part of the users list in order for it to receive an auth during initialisation.
|
||||||
u.users = append(u.users, user)
|
u.users = append(u.users, user)
|
||||||
|
|
||||||
if err = user.init(u.idleUpdates); err != nil {
|
if err = user.init(); err != nil {
|
||||||
u.users = u.users[:len(u.users)-1]
|
u.users = u.users[:len(u.users)-1]
|
||||||
return errors.Wrap(err, "failed to initialise user")
|
return errors.Wrap(err, "failed to initialise user")
|
||||||
}
|
}
|
||||||
@ -464,15 +457,6 @@ func (u *Users) SendMetric(m metrics.Metric) {
|
|||||||
}).Debug("Metric successfully sent")
|
}).Debug("Metric successfully sent")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIMAPUpdatesChannel sets the channel on which idle events should be sent.
|
|
||||||
func (u *Users) GetIMAPUpdatesChannel() chan imapBackend.Update {
|
|
||||||
if u.idleUpdates == nil {
|
|
||||||
log.Warn("IMAP updates channel is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.idleUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowProxy instructs the app to use DoH to access an API proxy if necessary.
|
// AllowProxy instructs the app to use DoH to access an API proxy if necessary.
|
||||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||||
func (u *Users) AllowProxy() {
|
func (u *Users) AllowProxy() {
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -86,36 +85,6 @@ func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
|||||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockConnectedUser(m mocks) {
|
|
||||||
gomock.InOrder(
|
|
||||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
|
||||||
|
|
||||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
|
||||||
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
|
|
||||||
|
|
||||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
|
||||||
|
|
||||||
// Set up mocks for store initialisation for the authorized user.
|
|
||||||
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
|
||||||
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
|
||||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mockAuthUpdate simulates users calling UpdateAuthToken on the given user.
|
|
||||||
// This would normally be done by users when it receives an auth from the ClientManager,
|
|
||||||
// but as we don't have a full users instance here, we do this manually.
|
|
||||||
func mockAuthUpdate(user *User, token string, m mocks) {
|
|
||||||
gomock.InOrder(
|
|
||||||
m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil),
|
|
||||||
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(token), nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
user.updateAuthToken(refreshWithToken(token))
|
|
||||||
|
|
||||||
waitForEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewUsersWithConnectedUser(t *testing.T) {
|
func TestNewUsersWithConnectedUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|||||||
@ -284,10 +284,37 @@ func TestClearData(t *testing.T) {
|
|||||||
func mockEventLoopNoAction(m mocks) {
|
func mockEventLoopNoAction(m mocks) {
|
||||||
// Set up mocks for starting the store's event loop (in store.New).
|
// Set up mocks for starting the store's event loop (in store.New).
|
||||||
// The event loop runs in another goroutine so this might happen at any time.
|
// The event loop runs in another goroutine so this might happen at any time.
|
||||||
|
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).AnyTimes()
|
||||||
|
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
|
||||||
|
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockConnectedUser(m mocks) {
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil),
|
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil),
|
|
||||||
// Set up mocks for performing the initial store sync.
|
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil),
|
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
|
||||||
|
|
||||||
|
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||||
|
|
||||||
|
// Set up mocks for store initialisation for the authorized user.
|
||||||
|
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
||||||
|
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||||
|
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockAuthUpdate simulates users calling UpdateAuthToken on the given user.
|
||||||
|
// This would normally be done by users when it receives an auth from the ClientManager,
|
||||||
|
// but as we don't have a full users instance here, we do this manually.
|
||||||
|
func mockAuthUpdate(user *User, token string, m mocks) {
|
||||||
|
gomock.InOrder(
|
||||||
|
m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil),
|
||||||
|
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(token), nil),
|
||||||
|
)
|
||||||
|
|
||||||
|
user.updateAuthToken(refreshWithToken(token))
|
||||||
|
|
||||||
|
waitForEvents()
|
||||||
|
}
|
||||||
|
|||||||
@ -189,6 +189,61 @@ func (c *Config) GetLogPrefix() string {
|
|||||||
return "v" + c.version + "_" + c.revision
|
return "v" + c.version + "_" + c.revision
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLicenseFilePath returns path to liense file.
|
||||||
|
func (c *Config) GetLicenseFilePath() string {
|
||||||
|
path := c.getLicenseFilePath()
|
||||||
|
log.WithField("path", path).Info("License file path")
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) getLicenseFilePath() string {
|
||||||
|
// User can install app to different location, or user can run it
|
||||||
|
// directly from the package without installation, or it could be
|
||||||
|
// automatically updated (app started from differenet location).
|
||||||
|
// For all those cases, first let's check LICENSE next to the binary.
|
||||||
|
path := filepath.Join(filepath.Dir(os.Args[0]), "LICENSE")
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
appName := c.appName
|
||||||
|
if c.appName == "importExport" {
|
||||||
|
appName = "import-export"
|
||||||
|
}
|
||||||
|
// Most Linux distributions.
|
||||||
|
path := "/usr/share/doc/protonmail/" + appName + "/LICENSE"
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// Arch distributions.
|
||||||
|
return "/usr/share/licenses/protonmail-" + appName + "/LICENSE"
|
||||||
|
case "darwin": //nolint[goconst]
|
||||||
|
path := filepath.Join(filepath.Dir(os.Args[0]), "..", "Resources", "LICENSE")
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
appName := "ProtonMail Bridge.app"
|
||||||
|
if c.appName == "importExport" {
|
||||||
|
appName = "ProtonMail Import-Export.app"
|
||||||
|
}
|
||||||
|
return "/Applications/" + appName + "/Contents/Resources/LICENSE"
|
||||||
|
case "windows":
|
||||||
|
path := filepath.Join(filepath.Dir(os.Args[0]), "LICENSE.txt")
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// This should not happen, Windows should be handled by relative
|
||||||
|
// location to the binary above. This is just fallback which may
|
||||||
|
// or may not work, depends where user installed the app and how
|
||||||
|
// user started the app.
|
||||||
|
return filepath.FromSlash("C:/Program Files/Proton Technologies AG/ProtonMail Bridge/LICENSE.txt")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// GetTLSCertPath returns path to certificate; used for TLS servers (IMAP, SMTP and API).
|
// GetTLSCertPath returns path to certificate; used for TLS servers (IMAP, SMTP and API).
|
||||||
func (c *Config) GetTLSCertPath() string {
|
func (c *Config) GetTLSCertPath() string {
|
||||||
return filepath.Join(c.appDirs.UserConfig(), "cert.pem")
|
return filepath.Join(c.appDirs.UserConfig(), "cert.pem")
|
||||||
|
|||||||
@ -57,6 +57,8 @@ var logCrashRgx = regexp.MustCompile("^v.*_crash_.*\\.log$") //nolint[gochecknog
|
|||||||
|
|
||||||
// HandlePanic reports the crash to sentry or local file when sentry fails.
|
// HandlePanic reports the crash to sentry or local file when sentry fails.
|
||||||
func HandlePanic(cfg *Config, output string) {
|
func HandlePanic(cfg *Config, output string) {
|
||||||
|
sentry.SkipDuringUnwind()
|
||||||
|
|
||||||
if !cfg.IsDevMode() {
|
if !cfg.IsDevMode() {
|
||||||
apiCfg := cfg.GetAPIConfig()
|
apiCfg := cfg.GetAPIConfig()
|
||||||
if err := sentry.ReportSentryCrash(apiCfg.ClientID, apiCfg.AppVersion, apiCfg.UserAgent, errors.New(output)); err != nil {
|
if err := sentry.ReportSentryCrash(apiCfg.ClientID, apiCfg.AppVersion, apiCfg.UserAgent, errors.New(output)); err != nil {
|
||||||
|
|||||||
@ -45,6 +45,11 @@ func Parse(r io.Reader, key, keyName string) (m *pmapi.Message, mimeBody, plainB
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = convertEncodedTransferEncoding(p); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to convert encoded transfer encodings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = convertForeignEncodings(p); err != nil {
|
if err = convertForeignEncodings(p); err != nil {
|
||||||
err = errors.Wrap(err, "failed to convert foreign encodings")
|
err = errors.Wrap(err, "failed to convert foreign encodings")
|
||||||
return
|
return
|
||||||
@ -89,6 +94,30 @@ func Parse(r io.Reader, key, keyName string) (m *pmapi.Message, mimeBody, plainB
|
|||||||
return m, mimeBodyBuffer.String(), plainBody, attReaders, nil
|
return m, mimeBodyBuffer.String(), plainBody, attReaders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertEncodedTransferEncoding decodes any RFC2047-encoded content transfer encodings.
|
||||||
|
// Such content transfer encodings go against RFC but still exist in the wild anyway.
|
||||||
|
func convertEncodedTransferEncoding(p *parser.Parser) error {
|
||||||
|
logrus.Trace("Converting encoded transfer encoding")
|
||||||
|
|
||||||
|
return p.NewWalker().
|
||||||
|
RegisterDefaultHandler(func(p *parser.Part) error {
|
||||||
|
encoding := p.Header.Get("Content-Transfer-Encoding")
|
||||||
|
if encoding == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := pmmime.WordDec.DecodeHeader(encoding)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Header.Set("Content-Transfer-Encoding", dec)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
Walk()
|
||||||
|
}
|
||||||
|
|
||||||
func convertForeignEncodings(p *parser.Parser) error {
|
func convertForeignEncodings(p *parser.Parser) error {
|
||||||
logrus.Trace("Converting foreign encodings")
|
logrus.Trace("Converting foreign encodings")
|
||||||
|
|
||||||
@ -104,12 +133,11 @@ func convertForeignEncodings(p *parser.Parser) error {
|
|||||||
return p.ConvertToUTF8()
|
return p.ConvertToUTF8()
|
||||||
}).
|
}).
|
||||||
RegisterDefaultHandler(func(p *parser.Part) error {
|
RegisterDefaultHandler(func(p *parser.Part) error {
|
||||||
t, params, _ := p.ContentType()
|
|
||||||
// multipart/alternative, for example, can contain extra charset.
|
// multipart/alternative, for example, can contain extra charset.
|
||||||
if params != nil && params["charset"] != "" {
|
if _, params, _ := p.ContentType(); params != nil && params["charset"] != "" {
|
||||||
return p.ConvertToUTF8()
|
return p.ConvertToUTF8()
|
||||||
}
|
}
|
||||||
logrus.WithField("type", t).Trace("Not converting part to utf-8")
|
|
||||||
return nil
|
return nil
|
||||||
}).
|
}).
|
||||||
Walk()
|
Walk()
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/emersion/go-message"
|
"github.com/emersion/go-message"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
@ -33,8 +34,15 @@ func New(r io.Reader) (*Parser, error) {
|
|||||||
p := new(Parser)
|
p := new(Parser)
|
||||||
|
|
||||||
entity, err := message.Read(newEndOfMailTrimmer(r))
|
entity, err := message.Read(newEndOfMailTrimmer(r))
|
||||||
if err != nil && !message.IsUnknownCharset(err) {
|
if err != nil {
|
||||||
return nil, err
|
switch {
|
||||||
|
case message.IsUnknownCharset(err):
|
||||||
|
logrus.WithError(err).Warning("Message has an unknown charset")
|
||||||
|
case message.IsUnknownEncoding(err):
|
||||||
|
logrus.WithError(err).Warning("Message has an unknown encoding")
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.parseEntity(entity); err != nil {
|
if err := p.parseEntity(entity); err != nil {
|
||||||
|
|||||||
@ -480,6 +480,37 @@ func TestParseWithTrailingEndOfMailIndicator(t *testing.T) {
|
|||||||
assert.Equal(t, "boo!", plainBody)
|
assert.Equal(t, "boo!", plainBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseEncodedContentType(t *testing.T) {
|
||||||
|
f := getFileReader("rfc2047-content-transfer-encoding.eml")
|
||||||
|
|
||||||
|
m, _, plainBody, _, err := Parse(f, "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, `"Sender" <sender@sender.com>`, m.Sender.String())
|
||||||
|
assert.Equal(t, `<user@somewhere.org>`, m.ToList[0].String())
|
||||||
|
|
||||||
|
assert.Equal(t, "bodybodybody\n", plainBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNonEncodedContentType(t *testing.T) {
|
||||||
|
f := getFileReader("non-encoded-content-transfer-encoding.eml")
|
||||||
|
|
||||||
|
m, _, plainBody, _, err := Parse(f, "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, `"Sender" <sender@sender.com>`, m.Sender.String())
|
||||||
|
assert.Equal(t, `<user@somewhere.org>`, m.ToList[0].String())
|
||||||
|
|
||||||
|
assert.Equal(t, "bodybodybody\n", plainBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseEncodedContentTypeBad(t *testing.T) {
|
||||||
|
f := getFileReader("rfc2047-content-transfer-encoding-bad.eml")
|
||||||
|
|
||||||
|
_, _, _, _, err := Parse(f, "", "") // nolint[dogsled]
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func getFileReader(filename string) io.Reader {
|
func getFileReader(filename string) io.Reader {
|
||||||
f, err := os.Open(filepath.Join("testdata", filename))
|
f, err := os.Open(filepath.Join("testdata", filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
10
pkg/message/testdata/non-encoded-content-transfer-encoding.eml
vendored
Normal file
10
pkg/message/testdata/non-encoded-content-transfer-encoding.eml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
To: user@somewhere.org
|
||||||
|
Subject: =?utf-8?Q?aoeuaoeuaoeu?=
|
||||||
|
Date: Sat, 16 Jun 2020 17:36:02 +0200
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="utf-8"
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
From: =?utf-8?Q?Sender?= <sender@sender.com>
|
||||||
|
|
||||||
|
bodybodybody
|
||||||
10
pkg/message/testdata/rfc2047-content-transfer-encoding-bad.eml
vendored
Normal file
10
pkg/message/testdata/rfc2047-content-transfer-encoding-bad.eml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
To: user@somewhere.org
|
||||||
|
Subject: =?utf-8?Q?aoeuaoeuaoeu?=
|
||||||
|
Date: Sat, 16 Jun 2020 17:36:02 +0200
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="utf-8"
|
||||||
|
Content-Transfer-Encoding: =?utf-8?Q?8bit
|
||||||
|
From: =?utf-8?Q?Sender?= <sender@sender.com>
|
||||||
|
|
||||||
|
bodybodybody
|
||||||
10
pkg/message/testdata/rfc2047-content-transfer-encoding.eml
vendored
Normal file
10
pkg/message/testdata/rfc2047-content-transfer-encoding.eml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
To: user@somewhere.org
|
||||||
|
Subject: =?utf-8?Q?aoeuaoeuaoeu?=
|
||||||
|
Date: Sat, 16 Jun 2020 17:36:02 +0200
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="utf-8"
|
||||||
|
Content-Transfer-Encoding: =?utf-8?Q?8bit?=
|
||||||
|
From: =?utf-8?Q?Sender?= <sender@sender.com>
|
||||||
|
|
||||||
|
bodybodybody
|
||||||
@ -43,7 +43,7 @@ func startServer() {
|
|||||||
_, _ = w.Write([]byte("OK"))
|
_, _ = w.Write([]byte("OK"))
|
||||||
})
|
})
|
||||||
http.HandleFunc("/timeout", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/timeout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(testRequestTimeout + time.Second) // Add extra second to be sure it will timeout.
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = w.Write([]byte("OK"))
|
_, _ = w.Write([]byte("OK"))
|
||||||
})
|
})
|
||||||
|
|||||||
@ -67,6 +67,14 @@ func (req *ImportReq) WriteTo(w *multipart.Writer) (err error) {
|
|||||||
if _, err = fw.Write(msg.Body); err != nil {
|
if _, err = fw.Write(msg.Body); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adding new line to properly fetch the whole body on the API side.
|
||||||
|
// The reason is the bug in PHP: https://bugs.php.net/bug.php?id=75923
|
||||||
|
// Messages generated by PM already have it but importing already
|
||||||
|
// encrypted messages might not have it.
|
||||||
|
if _, err = fw.Write([]byte("\r\n")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -127,7 +127,7 @@ func TestClient_Import(t *testing.T) { // nolint[funlen]
|
|||||||
t.Error("Expected no error while reading second part body, got:", err)
|
t.Error("Expected no error while reading second part body, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(b) != string(testImportReqs[0].Body) {
|
if string(b) != string(testImportReqs[0].Body)+"\r\n" {
|
||||||
t.Errorf("Invalid message body: expected %v but got %v", string(testImportReqs[0].Body), string(b))
|
t.Errorf("Invalid message body: expected %v but got %v", string(testImportReqs[0].Body), string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -253,6 +253,10 @@ func (m *Message) HasLabelID(labelID string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Message) IsEncrypted() bool {
|
||||||
|
return strings.HasPrefix(m.Header.Get("Content-Type"), "multipart/encrypted") || m.IsBodyEncrypted()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Message) IsBodyEncrypted() bool {
|
func (m *Message) IsBodyEncrypted() bool {
|
||||||
trimmedBody := strings.TrimSpace(m.Body)
|
trimmedBody := strings.TrimSpace(m.Body)
|
||||||
return strings.HasPrefix(trimmedBody, MessageHeader) &&
|
return strings.HasPrefix(trimmedBody, MessageHeader) &&
|
||||||
|
|||||||
@ -26,10 +26,15 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
skippedFunctions = []string{} //nolint[gochecknoglobals]
|
||||||
|
)
|
||||||
|
|
||||||
// ReportSentryCrash reports a sentry crash.
|
// ReportSentryCrash reports a sentry crash.
|
||||||
func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error) (err error) {
|
func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error) error {
|
||||||
|
SkipDuringUnwind()
|
||||||
if reportErr == nil {
|
if reportErr == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
@ -40,16 +45,71 @@ func ReportSentryCrash(clientID, appVersion, userAgent string, reportErr error)
|
|||||||
"UserID": "",
|
"UserID": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reportID string
|
||||||
sentry.WithScope(func(scope *sentry.Scope) {
|
sentry.WithScope(func(scope *sentry.Scope) {
|
||||||
|
SkipDuringUnwind()
|
||||||
scope.SetTags(tags)
|
scope.SetTags(tags)
|
||||||
sentry.CaptureException(reportErr)
|
eventID := sentry.CaptureException(reportErr)
|
||||||
|
if eventID != nil {
|
||||||
|
reportID = string(*eventID)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if !sentry.Flush(time.Second * 10) {
|
if !sentry.Flush(time.Second * 10) {
|
||||||
log.WithField("error", reportErr).Error("failed to report sentry error")
|
log.WithField("error", reportErr).Error("Failed to report sentry error")
|
||||||
return errors.New("failed to report sentry error")
|
return errors.New("failed to report sentry error")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithField("error", reportErr).Warn("reported sentry error")
|
log.WithField("error", reportErr).WithField("id", reportID).Warn("Sentry error reported")
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipDuringUnwind removes caller from the traceback.
|
||||||
|
func SkipDuringUnwind() {
|
||||||
|
pcs := make([]uintptr, 2)
|
||||||
|
n := runtime.Callers(2, pcs)
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frames := runtime.CallersFrames(pcs)
|
||||||
|
frame, _ := frames.Next()
|
||||||
|
if isFunctionFilteredOut(frame.Function) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
skippedFunctions = append(skippedFunctions, frame.Function)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnhanceSentryEvent swaps type with value and removes panic handlers from the stacktrace.
|
||||||
|
func EnhanceSentryEvent(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
||||||
|
for idx, exception := range event.Exception {
|
||||||
|
exception.Type, exception.Value = exception.Value, exception.Type
|
||||||
|
if exception.Stacktrace != nil {
|
||||||
|
exception.Stacktrace.Frames = filterOutPanicHandlers(exception.Stacktrace.Frames)
|
||||||
|
}
|
||||||
|
event.Exception[idx] = exception
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterOutPanicHandlers(frames []sentry.Frame) []sentry.Frame {
|
||||||
|
newFrames := []sentry.Frame{}
|
||||||
|
for _, frame := range frames {
|
||||||
|
// Sentry splits runtime.Frame.Function into Module and Function.
|
||||||
|
function := frame.Module + "." + frame.Function
|
||||||
|
if !isFunctionFilteredOut(function) {
|
||||||
|
newFrames = append(newFrames, frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newFrames
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFunctionFilteredOut(function string) bool {
|
||||||
|
for _, skipFunction := range skippedFunctions {
|
||||||
|
if function == skipFunction {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
66
pkg/sentry/report_test.go
Normal file
66
pkg/sentry/report_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sentry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
r "github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSkipDuringUnwind(t *testing.T) {
|
||||||
|
// More calls in one function adds it only once.
|
||||||
|
SkipDuringUnwind()
|
||||||
|
SkipDuringUnwind()
|
||||||
|
func() {
|
||||||
|
SkipDuringUnwind()
|
||||||
|
SkipDuringUnwind()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wantSkippedFunctions := []string{
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind",
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind.func1",
|
||||||
|
}
|
||||||
|
r.Equal(t, wantSkippedFunctions, skippedFunctions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterOutPanicHandlers(t *testing.T) {
|
||||||
|
skippedFunctions = []string{
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/config.(*PanicHandler).HandlePanic",
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/config.HandlePanic",
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash",
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash.func1",
|
||||||
|
}
|
||||||
|
|
||||||
|
frames := []sentry.Frame{
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/internal/cmd", Function: "main"},
|
||||||
|
{Module: "github.com/urfave/cli", Function: "(*App).Run"},
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/internal/cmd", Function: "RegisterHandlePanic"},
|
||||||
|
{Module: "github.com/ProtonMail/pkg", Function: "HandlePanic"},
|
||||||
|
{Module: "main", Function: "run"},
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/pkg/config", Function: "(*PanicHandler).HandlePanic"},
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/pkg/config", Function: "HandlePanic"},
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash"},
|
||||||
|
{Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash.func1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
gotFrames := filterOutPanicHandlers(frames)
|
||||||
|
r.Equal(t, frames[:5], gotFrames)
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
• Bridge crashes related to labels handling
|
• AppleMail crashes (timestamp related)
|
||||||
• GUI popup related to TLS connection error
|
• Encoding errors
|
||||||
• An issue where a random session key is included in the data payload
|
• Installation issues on linux
|
||||||
• Error handling (including improved detection)
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
• Installation issues on linux
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
• Improved package creation logic
|
• Support read confirmations
|
||||||
• Refactor of sending functions to simplify code maintenance
|
• Adding GPLv3 licence button to the GUI
|
||||||
• Added tests for package creation
|
• Improved testing
|
||||||
• For more detailed summary of the changes see https://github.com/ProtonMail/proton-bridge/blob/master/Changelog.md
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
• Further improvements to address and date parsing
|
• Allow an import of already encrypted messages (as cypher text)
|
||||||
• Better handling and displaying of skipped messages
|
• Cosmetic GUI changes
|
||||||
• Improved error reporting
|
• Better error handling
|
||||||
|
|||||||
@ -68,10 +68,11 @@ type TestContext struct {
|
|||||||
smtpLastResponses map[string]*mocks.SMTPResponse
|
smtpLastResponses map[string]*mocks.SMTPResponse
|
||||||
|
|
||||||
// Transfer related variables.
|
// Transfer related variables.
|
||||||
transferLocalRootForImport string
|
transferLocalRootForImport string
|
||||||
transferLocalRootForExport string
|
transferLocalRootForExport string
|
||||||
transferRemoteIMAPServer *mocks.IMAPServer
|
transferRemoteIMAPServer *mocks.IMAPServer
|
||||||
transferProgress *transfer.Progress
|
transferProgress *transfer.Progress
|
||||||
|
transferSkipEncryptedMessages bool
|
||||||
|
|
||||||
// Store releated variables.
|
// Store releated variables.
|
||||||
bddMessageIDsToAPIIDs map[string]string
|
bddMessageIDsToAPIIDs map[string]string
|
||||||
|
|||||||
@ -37,6 +37,16 @@ func (ctx *TestContext) GetTransferProgress() *transfer.Progress {
|
|||||||
return ctx.transferProgress
|
return ctx.transferProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTransferSkipEncryptedMessages sets whether encrypted messages will be skipped.
|
||||||
|
func (ctx *TestContext) SetTransferSkipEncryptedMessages(value bool) {
|
||||||
|
ctx.transferSkipEncryptedMessages = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransferSkipEncryptedMessages gets whether encrypted messages will be skipped.
|
||||||
|
func (ctx *TestContext) GetTransferSkipEncryptedMessages() bool {
|
||||||
|
return ctx.transferSkipEncryptedMessages
|
||||||
|
}
|
||||||
|
|
||||||
// GetTransferLocalRootForImport creates temporary root for importing
|
// GetTransferLocalRootForImport creates temporary root for importing
|
||||||
// if it not exists yet, and returns its path.
|
// if it not exists yet, and returns its path.
|
||||||
func (ctx *TestContext) GetTransferLocalRootForImport() string {
|
func (ctx *TestContext) GetTransferLocalRootForImport() string {
|
||||||
|
|||||||
@ -20,8 +20,10 @@ package fakeapi
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
messageUtils "github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -139,6 +141,7 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
|
|||||||
}
|
}
|
||||||
message.ID = ctl.messageIDGenerator.next("")
|
message.ID = ctl.messageIDGenerator.next("")
|
||||||
message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel)
|
message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel)
|
||||||
|
message.Header = mail.Header(messageUtils.GetHeader(message))
|
||||||
ctl.messagesByUsername[username] = append(ctl.messagesByUsername[username], message)
|
ctl.messagesByUsername[username] = append(ctl.messagesByUsername[username], message)
|
||||||
ctl.resetUsers()
|
ctl.resetUsers()
|
||||||
return message.ID, nil
|
return message.ID, nil
|
||||||
|
|||||||
@ -60,3 +60,17 @@ Feature: IMAP fetch messages
|
|||||||
When IMAP client fetches by UID "1:*"
|
When IMAP client fetches by UID "1:*"
|
||||||
Then IMAP response is "OK"
|
Then IMAP response is "OK"
|
||||||
And IMAP response has 2 message
|
And IMAP response has 2 message
|
||||||
|
|
||||||
|
Scenario: Fetch of very old message sent from the moon succeeds with modified date
|
||||||
|
Given there are messages in mailbox "Folders/mbox" for "user"
|
||||||
|
| from | to | subject | time |
|
||||||
|
| john.doe@mail.com | user@pm.me | foo | 1969-07-20T00:00:00 |
|
||||||
|
And there is IMAP client logged in as "user"
|
||||||
|
And there is IMAP client selected in "Folders/mbox"
|
||||||
|
When IMAP client sends command "FETCH 1:* rfc822"
|
||||||
|
Then IMAP response is "OK"
|
||||||
|
And IMAP response contains "Date: Fri, 13 Aug 1982"
|
||||||
|
And IMAP response contains "X-Original-Date: Sun, 20 Jul 1969"
|
||||||
|
# We had bug to incorectly set empty date, so let's make sure
|
||||||
|
# there is no reference anywhere in the response.
|
||||||
|
And IMAP response does not contain "Date: Thu, 01 Jan 1970"
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
Feature: SMTP auth
|
Feature: SMTP auth
|
||||||
Scenario: Ask EHLO
|
Scenario: Ask EHLO
|
||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
When SMTP client sends EHLO
|
When SMTP client sends "EHLO example.com"
|
||||||
Then SMTP response is "OK"
|
Then SMTP response is "OK"
|
||||||
|
|
||||||
Scenario: Authenticates successfully and EHLO successfully
|
Scenario: Authenticates successfully and EHLO successfully
|
||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
When SMTP client authenticates "user"
|
When SMTP client authenticates "user"
|
||||||
Then SMTP response is "OK"
|
Then SMTP response is "OK"
|
||||||
When SMTP client sends EHLO
|
When SMTP client sends "EHLO example.com"
|
||||||
Then SMTP response is "OK"
|
Then SMTP response is "OK"
|
||||||
|
|
||||||
Scenario: Authenticates with bad password
|
Scenario: Authenticates with bad password
|
||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
When SMTP client authenticates "user" with bad password
|
When SMTP client authenticates "user" with bad password
|
||||||
Then SMTP response is "SMTP error: 454 backend/credentials: incorrect password"
|
Then SMTP response is "SMTP error: 454 4.7.0 backend/credentials: incorrect password"
|
||||||
|
|
||||||
Scenario: Authenticates with disconnected user
|
Scenario: Authenticates with disconnected user
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
When SMTP client authenticates "user"
|
When SMTP client authenticates "user"
|
||||||
Then SMTP response is "SMTP error: 454 account is logged out, use the app to login again"
|
Then SMTP response is "SMTP error: 454 4.7.0 account is logged out, use the app to login again"
|
||||||
|
|
||||||
Scenario: Authenticates with no user
|
Scenario: Authenticates with no user
|
||||||
When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"
|
When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"
|
||||||
Then SMTP response is "SMTP error: 454 user user@pm.me not found"
|
Then SMTP response is "SMTP error: 454 4.7.0 user user@pm.me not found"
|
||||||
|
|
||||||
Scenario: Authenticates with capital letter
|
Scenario: Authenticates with capital letter
|
||||||
Given there is connected user "userAddressWithCapitalLetter"
|
Given there is connected user "userAddressWithCapitalLetter"
|
||||||
@ -43,7 +43,7 @@ Feature: SMTP auth
|
|||||||
Scenario: Authenticates with more addresses - disabled address
|
Scenario: Authenticates with more addresses - disabled address
|
||||||
Given there is connected user "userMoreAddresses"
|
Given there is connected user "userMoreAddresses"
|
||||||
When SMTP client authenticates "userMoreAddresses" with address "disabled"
|
When SMTP client authenticates "userMoreAddresses" with address "disabled"
|
||||||
Then SMTP response is "SMTP error: 454 user .* not found"
|
Then SMTP response is "SMTP error: 454 4.7.0 user .* not found"
|
||||||
|
|
||||||
@ignore-live
|
@ignore-live
|
||||||
Scenario: Authenticates with secondary address of account with disabled primary address
|
Scenario: Authenticates with secondary address of account with disabled primary address
|
||||||
|
|||||||
83
test/features/bridge/smtp/init.feature
Normal file
83
test/features/bridge/smtp/init.feature
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
Feature: SMTP initiation
|
||||||
|
Scenario: Empty FROM
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
|
||||||
|
Scenario: Send without FROM and TO
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "DATA"
|
||||||
|
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command."
|
||||||
|
|
||||||
|
Scenario: Reset is the same as without FROM and TO
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<user@pm.me>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "RCPT TO:<user@pm.me>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "RSET"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "DATA"
|
||||||
|
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command"
|
||||||
|
|
||||||
|
Scenario: Send without FROM
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "RCPT TO:<user@pm.me>"
|
||||||
|
Then SMTP response is "SMTP error: 502 5.5.1 Missing MAIL FROM command."
|
||||||
|
|
||||||
|
Scenario: Send without TO
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<user@pm.me>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "DATA"
|
||||||
|
Then SMTP response is "SMTP error: 502 5.5.1 Missing RCPT TO command."
|
||||||
|
|
||||||
|
Scenario: Send with empty FROM
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "RCPT TO:<user@pm.me>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "DATA"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "hello\r\n."
|
||||||
|
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: missing sender"
|
||||||
|
|
||||||
|
Scenario: Send with empty TO
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<user@pm.me>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "RCPT TO:<>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "DATA"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "hello\r\n."
|
||||||
|
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: missing recipient"
|
||||||
|
|
||||||
|
Scenario: Allow BODY parameter of MAIL FROM command
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<user@pm.me> BODY=7BIT"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
|
||||||
|
Scenario: Allow AUTH parameter of MAIL FROM command
|
||||||
|
Given there is connected user "user"
|
||||||
|
When SMTP client authenticates "user"
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
When SMTP client sends "MAIL FROM:<user@pm.me> AUTH=<>"
|
||||||
|
Then SMTP response is "OK"
|
||||||
@ -38,4 +38,4 @@ Feature: SMTP wrong messages
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Then SMTP response is "SMTP error: 554 Error: transaction failed, blame it on the weather: failed to create new parser: unexpected EOF"
|
Then SMTP response is "SMTP error: 554 5.0.0 Error: transaction failed, blame it on the weather: failed to create new parser: unexpected EOF"
|
||||||
|
|||||||
183
test/features/ie/transfer/import_encrypted.feature
Normal file
183
test/features/ie/transfer/import_encrypted.feature
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
Feature: Import from EML files
|
||||||
|
Background:
|
||||||
|
Given there is connected user "user"
|
||||||
|
And there is EML file "Inbox/clear.eml"
|
||||||
|
"""
|
||||||
|
Subject: clear
|
||||||
|
From: Bridge Test <bridgetest@pm.test>
|
||||||
|
To: Internal Bridge <test@protonmail.com>
|
||||||
|
|
||||||
|
secret
|
||||||
|
"""
|
||||||
|
And there is EML file "Inbox/encrypted.eml"
|
||||||
|
"""
|
||||||
|
Subject: encrypted
|
||||||
|
From: Bridge Test <bridgetest@pm.test>
|
||||||
|
To: Internal Bridge <test@protonmail.com>
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
hQEMA7hGUUsYs0fEAQgA10NwJSNTLm3vpxVtoYBaA9AjFI5H4hKuV3/f2NHbWb2s
|
||||||
|
k5enK3tEIOYdFdrO+NLrV6weHq3Dgu4er3URTQ62tFwjSJyeXxmY0d9J+JdxibJg
|
||||||
|
wqZubuSHYzQHpFqJgoUUWEVEsv0Ao8yQo8BE9iybCKoZf6KojROUuE748oxlxJnV
|
||||||
|
m1XuaVIzgw4xN0GUA5sLLuWeL94b2dZe5SDDQE5POzDgueZ7faefX8U1pGErCRJ0
|
||||||
|
sO6FSw3SF4NpvrxVESWgCmsG5pcuxE2JqB0UoHnNDcqsW8w1Q+GabAPo6UqHhgIg
|
||||||
|
56MRCWeou2djHIIj9TMUIVFzSG/HvTYQWVS+i4Z7AdJJAXr53GgbZQznO80Qxwcb
|
||||||
|
FFdlwOXHuaXqhqCb338jlQWnbcnUsuJWxBAxkHrlP/nluFqPdIBOglC38kdYSBed
|
||||||
|
3YwuEB9sXV/fcw==
|
||||||
|
=B05V
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
"""
|
||||||
|
And there is EML file "Inbox/encrypted-mime.eml"
|
||||||
|
"""
|
||||||
|
Subject: encrypted mime
|
||||||
|
From: Bridge Test <bridgetest@pm.test>
|
||||||
|
To: Internal Bridge <test@protonmail.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="WLjzd46aUAiOcuNXjWTJItBZonI56MuAk"
|
||||||
|
|
||||||
|
--WLjzd46aUAiOcuNXjWTJItBZonI56MuAk
|
||||||
|
Content-Type: application/pgp-encrypted
|
||||||
|
Content-Description: PGP/MIME version identification
|
||||||
|
|
||||||
|
|
||||||
|
--WLjzd46aUAiOcuNXjWTJItBZonI56MuAk
|
||||||
|
Content-Type: application/octet-stream; name="encrypted.asc"
|
||||||
|
Content-Description: OpenPGP encrypted message
|
||||||
|
Content-Disposition: inline; filename="encrypted.asc"
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
hQEMA1ppSfinU0f4AQf9HDkojTV3SspsnhaB0HAsKIrUd+AAdSm49ndnJyjYb210
|
||||||
|
GFIDE/TqcXmoeOcaJIRWaEOZzdcnixplJHjwp5dvDyCaYQSqYxUQ5Z/JfKbtsDyV
|
||||||
|
HbQzAh833SBCFlNNTnmF/Onu7yRNje1k8U36bY1VUX1QlerT9HDm2QTMRheuPDUR
|
||||||
|
H9OvGkuBXRpWRSPyXlPONPQOZTbUxvkuMGgDY0N2wt6kKQsrtduQNC157EJOErq0
|
||||||
|
Zlhu9CnAyezDupMkSoikR1uyxo7GhyXNxi70Ol3tN7E2fnzeBCjUgmliYTABOGSH
|
||||||
|
nuPpTNk3/YoLEHXK18E/qR3vJTTl6AFIbOcfRCqpQIUBDAOjbxn1yC74AQEH/0kB
|
||||||
|
CiNDwPepRxwzv3EZT7V0YPuTCD18m9BZ4W5lVEvMNP7HJnCILJT8QJhLQ+AVBUuw
|
||||||
|
jhJqxAahssOGQ5BVxnWj4qwM+WzBOplH9Zt9bKTie8IdAJsl5GysL19jc4fjnvsK
|
||||||
|
weBQiR3Y+lEGEBrCajVrUkrXRHyA0fmel8aPfhiHxbh+jRtY8BWdBeX3gIfjwVKf
|
||||||
|
mMuTmHQ6ERv9CGpIy7mxRF67EIaVRhQzjNNnRlCIqgZHOpS72SKc6DtyCiR+ECjq
|
||||||
|
UAKNwOjTNZEzAjczyIB5Hkkw1trtVOZEqdacy0CM/SJxjRA8HQ7/0pjtqjOvcpZU
|
||||||
|
IB6z7IbZLH7krqJY4ZHS6gFvH3B7YOksaQsQL0x4GsdYY4mGUj/18Dzzw2YscUjs
|
||||||
|
HCOuN9zwAxEIztSQFFZ8vShbpk73fu80X5qRoCQ9708+sKdO92oDY9oZBQkcUl1T
|
||||||
|
qhpSdApN9mJl2n+uHfSDy63YynhT/bMMrh0AfZjB4ssX9jNkH2knS/FVFUjFUHVh
|
||||||
|
6boXr0q9xdxt64onx8BrpWOBCqqXjRWUR2n/+y+zw+YgjqUWjpVmsQoF7wQQ3xo6
|
||||||
|
Yb8y2WguTG9K6m9rS96dOtkXWJgZOVYZ5zlRqdbGZzlfei1890QfnRsNJQhhwkLq
|
||||||
|
CJV5bhy6AGZxk9JK/RW33g//i2GDfUx4HptRPEgGWu3ZdQskKwyZB7dc6NMtT2as
|
||||||
|
tOP6z/wgLIPVlLJEY0jXHkmbGf7Oj9JpBSCQBz57rmZunsTgy/jDIuL6mzeuVdYN
|
||||||
|
lVHqVao23aTZRPaCmwYqWW254oCKaeE7X8nRaQF+9L2nK4YbUf0+KbDGhjnQy8Qg
|
||||||
|
K1cQt51NcWsM28jNV6Puww7MS+K0NaMjr1fTHdomfHI27C0Dr1e85BWkDnesLqtw
|
||||||
|
2s5S/8KdYMdBLuzyfT4UQkYTmtxibRXQR9+TxDmNQ/luMuFTCowgGfebAMOCrwU7
|
||||||
|
NxrgSyuTmAC1Je1glSMMQghHwBCUB2BUCn/vFlMwHdl1waKrUpRaKQRI3iPhMjMw
|
||||||
|
91Fsv5cUc6uD2pO7vb+vOm2O7+i08KtBpttjk+ANDJjxiGT0V/omlh40T80vN0h6
|
||||||
|
yk8ZNTq8MqqvLMyH2wKqmmEjll73AWkHATLawRD3ckmlEF+ywc6J91CAYXokWuHc
|
||||||
|
N7CBL3vRhEJppZ3rmKNw3ani+ThQLTqnGxzxuB+P5IBO6RGXvjYfiUC3Nb0o1Q6X
|
||||||
|
+QD5BZlvVkklG4bwRdcn87wSlarA8T/nqlZ388ajNaE1Y2+zyJnJyOUEk3nLcgI8
|
||||||
|
ovaVF/G3PG4yhPR+oOgE7IdWwp+WFa15OF2iLn8ByQa3V8fsWczXHu/iXLyr0KKl
|
||||||
|
MJCR4bsCv2hcOFTlYSRMyBs+A9gXA9pT+ljv0g7/Z9BuFSmr6pRzgK/guk6WzoTt
|
||||||
|
m+TxDn1hEovo62KkhAyMtD1hbYO/5GDB6X8tI0YM0kRk8E+H8fuxl43uUE+y9B0X
|
||||||
|
7Qmkf1Oym9x23S+372MiEa/avAWZTtHhhii37lWkKU+pkx+aiMrfJyozafx6cAaQ
|
||||||
|
Rxx5uv+8lXEZy4qNEXop7yKDz2agSd6XdZziSIO69BF3x6DMKZdBJtyc5V2RqibU
|
||||||
|
t80ziVK0IStJmNUPZ1DSMXiwN3yzkQ/bm9RH3x3PPvaVNjISHdl85wlDFc8FM2m0
|
||||||
|
Q0RM40lj5XAEs1O8iBk5m9yCNMSKQLq5vOhmbygK3ILp4dBoYr6EGZjz+Nq4M+ws
|
||||||
|
n/dzdR62oCVuKYvVyJVUkmt4DGTo7Pi9ngjAdmLu/RLL8M0/MG9wbu2adT7c2ypj
|
||||||
|
HM3lUqm+KEf9CdpJBVj0RH5BDWKwDpWx6g6np+GoXsj9nkXYv5qxzVNwgpjTRHwH
|
||||||
|
xJE+1nFStBtiWunP6eqd8Fl99/jATgVU9ytp+Q+nnZPZn+KHCZEl3CF/TBKsNl7S
|
||||||
|
QUwdepNF80MDYFi2r685SqM6fvefur0sqyeDwsBOM4GBU88FH9GnWJhQqKVEmQH2
|
||||||
|
PV/UzkCPpj0ngkQiQjGMQjOKmI6npljOWbIw7LrhggOnfFnP2iTO0B4aAx1h6Ppi
|
||||||
|
3+jkrdJEuxB89f8P/W8ChtOw7s53YTwYtxmZ+/x0e1G4Nh8pPcFRFF2t/UHEav5v
|
||||||
|
s3CyH7reAIXDclHH46wbrczvcf6FzS+o8ypIRFAapamUhPqpksuIvyoUeQv8WW/Q
|
||||||
|
m2tFOPp9wJp/+GAEbuZTyTd/o7Cms3Zl0EOQB9tgqWyqWhasPd40/SCdeXzqpEMS
|
||||||
|
5Io0tE0ohY9DzN96kn2+07FUSqOYInup3+EXUhCGF8K/i1dny6/o7ZxDjW5xsTdb
|
||||||
|
AZxd0UEdhvJtvtKhckLhICzImeLGrCUz/zuJBvTR08ir8Rm8kkAmHBn9/jf8+42J
|
||||||
|
X3TSTes7+k+DtZP6VL6RKhTAzFIEWLQZ+38nzGPfM0BUKf0sGW3wlWFQREU9k9QX
|
||||||
|
S/idPNOqdNHz/l0eUwf3/bjfAB6OqitPWYH23d6zMkMEgwx/gJmboOfiYu9FKvXJ
|
||||||
|
tvRgOHb6Rww8fUQhlDOhVupo0DFTIghdeXjeVn/CxIUO67Ns+PI5IB+/sw1KxTIp
|
||||||
|
kZjjft/l3+mSnyJVyqvzKyfA1WhaXLXJfcJyeGt/Y45RiYnkbSdcbuNhngn+NpZZ
|
||||||
|
SAcS4vyUqkDQ6RvWU+fww8EYxptNALt9hnc1wD+e8b3Gz2citRrLrc4AhiZwafp8
|
||||||
|
gj4HtKBXFz3oX2vhHgubTLuiEKhGc2dYXL9Z2PlXOWZhauTO00iVYfbWPsdTRSvi
|
||||||
|
QmKIP9QVzDq6StfHxs2x47yxrHrEtCjsHjDz3d+r+p6i6O212EHlCQewaPfAieBp
|
||||||
|
lw01cJB1KwyeoYgTczQkz6hhM+fj1RBMNDqxTHBVb3GGNh1nxu+4UR+tgQG/V/Ot
|
||||||
|
M/1NE8+yeRktzukDX1toXfCFXvRL3ijriHliaivWww==
|
||||||
|
=4M3l
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
|
||||||
|
--WLjzd46aUAiOcuNXjWTJItBZonI56MuAk--
|
||||||
|
"""
|
||||||
|
And there is EML file "Inbox/signed.eml"
|
||||||
|
"""
|
||||||
|
Subject: signed
|
||||||
|
From: Bridge Test <bridgetest@pm.test>
|
||||||
|
To: Internal Bridge <test@protonmail.com>
|
||||||
|
|
||||||
|
-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
|
Hash: SHA256
|
||||||
|
|
||||||
|
secret
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQEzBAEBCAAdFiEENaE2ZPemenI4pZah/SJcGo7SJWIFAl+cCAgACgkQ/SJcGo7S
|
||||||
|
JWKsOQf/YakNXkMNjZIu8Hf1WflxtiDXVzTugOicC05k5W64oIqSHt0xNaFKE37k
|
||||||
|
//3eDMWbHvqHKFVdg7qcLsVPeVBaW3bdZUiexGM24OiGgyEitufnHQLOtEDTound
|
||||||
|
JyH5nUeHpvpBKIIOJZNBDM0HsRYnwKwrOWk3N2VRwog4J8J3cmJ/f9bPWNI/0OPT
|
||||||
|
qmtVGRVg6Ge83nZn51Vof//jFzkO4wGYCsE0aF0Ywc7nISZuyKQzmu/qgmwzDG50
|
||||||
|
PjpvIQ/ygisRPNdRlylXEqyoIDCQ+v0AnxhhwX/5dbt6xMuMMOxBrFSC94Zce1Vj
|
||||||
|
x2ssXlT4ONPnkI/YWwhtQPLU628IMg==
|
||||||
|
=GiS3
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
"""
|
||||||
|
And there is EML file "Inbox/encrypted-signed.eml"
|
||||||
|
"""
|
||||||
|
Subject: encrypted and signed
|
||||||
|
From: Bridge Test <bridgetest@pm.test>
|
||||||
|
To: Internal Bridge <test@protonmail.com>
|
||||||
|
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
hQEMA7hGUUsYs0fEAQf/dppHciWIf+o4l0gEfHeyHV/HVhG4es0aVQYrwFQlSWVx
|
||||||
|
estMuyLBSMfrsQXLago7Q9ZNo/XnKszzprCXxxYH52hAg64oAsjKB3jgRmVizs8b
|
||||||
|
8lj0BRf003wUluS/0msV9SiEZBGeL8jGq6Te9vaM8OHHhIVzVjGnRdTSC0jBE6cS
|
||||||
|
vy8IBHXYe0LfdZiPojPDPGQdSej+H3uu7eZGBvVHTDeQLPDel4k7Ykdr0qlNXs6O
|
||||||
|
5XpM5YG4w+t0aG+YROPH+BUj8PpPojQ/lrv/yFISTRbHlEd8N50w8BNTnBet+9Vm
|
||||||
|
oPcyvN+RQxBlvRuPpDjUmREvmtObKZV6+m6gocemx9LAzQEeVLcpjO/hJhl8gX72
|
||||||
|
MNz3McU7aXf5sSoOPdHDNx8T2NON/2bwG5FE+PRMuVywTKhCB7o8VAsJpGMQ8xRM
|
||||||
|
5WCNhow0AI7kni8yZA+GbvspnJWfit9tCTR5MIFHCSH9J3kJJnWkxQSN04GGpBcd
|
||||||
|
n43GWn7O7ufA4lMMZiGXMdi/J1iV9waAsIfMPk29BMq6xK0/jJYdHqQS+vNsSnF5
|
||||||
|
xL/Ir4RYq4SFFA06A/E7HpXr2ruZhBQCkzaIIdrVJR/Lp2VLJIVulTBQK8y2AFtj
|
||||||
|
JeeKS0kIuC/7UPF2O624kwNr8dmIhDJYusFs6ZeED/nAKwDO/vP2CSwVC3sUjn3N
|
||||||
|
u+sWqQUTxSmjhRVf9b0+VyTh0mXCovJQXomL6Zz6lxXuJqqzELIOfCxYD1z9GwTG
|
||||||
|
cT08Aa2eEpf3agdLCTxvjO3iq9FksMHvIN+LSCQ6Pw+aTByjrk0oMmvGbANAogTk
|
||||||
|
yrplG/iRVlmq0p/Cfl5UEjKqT/nt5j9zbpeuYXmhjiBT9SBE07oUVLY1VT7ihcY=
|
||||||
|
=qYnL
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: Import encrypted
|
||||||
|
Given there is skip encrypted messages set to "false"
|
||||||
|
When user "user" imports local files
|
||||||
|
Then progress result is "OK"
|
||||||
|
And transfer failed for 0 messages
|
||||||
|
And transfer exported 5 messages
|
||||||
|
And transfer imported 5 messages
|
||||||
|
And transfer skipped 0 messages
|
||||||
|
And API mailbox "INBOX" for "user" has messages
|
||||||
|
| from | to | subject |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | clear |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | encrypted |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | encrypted mime |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | signed |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | encrypted and signed |
|
||||||
|
|
||||||
|
Scenario: Skip encrypted
|
||||||
|
Given there is skip encrypted messages set to "true"
|
||||||
|
When user "user" imports local files
|
||||||
|
Then progress result is "OK"
|
||||||
|
And transfer failed for 0 messages
|
||||||
|
And transfer exported 5 messages
|
||||||
|
And transfer imported 2 messages
|
||||||
|
And transfer skipped 3 messages
|
||||||
|
And API mailbox "INBOX" for "user" has messages
|
||||||
|
| from | to | subject |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | clear |
|
||||||
|
| bridgetest@pm.test | test@protonmail.com | signed |
|
||||||
@ -32,6 +32,8 @@ func IMAPChecksFeatureContext(s *godog.Suite) {
|
|||||||
s.Step(`^IMAP response to "([^"]*)" contains "([^"]*)"$`, imapResponseNamedContains)
|
s.Step(`^IMAP response to "([^"]*)" contains "([^"]*)"$`, imapResponseNamedContains)
|
||||||
s.Step(`^IMAP response has (\d+) message(?:s)?$`, imapResponseHasNumberOfMessages)
|
s.Step(`^IMAP response has (\d+) message(?:s)?$`, imapResponseHasNumberOfMessages)
|
||||||
s.Step(`^IMAP response to "([^"]*)" has (\d+) message(?:s)?$`, imapResponseNamedHasNumberOfMessages)
|
s.Step(`^IMAP response to "([^"]*)" has (\d+) message(?:s)?$`, imapResponseNamedHasNumberOfMessages)
|
||||||
|
s.Step(`^IMAP response does not contain "([^"]*)"$`, imapResponseDoesNotContain)
|
||||||
|
s.Step(`^IMAP response to "([^"]*)" does not contain "([^"]*)"$`, imapResponseNamedDoesNotContain)
|
||||||
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsReadWithin)
|
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsReadWithin)
|
||||||
s.Step(`^IMAP client "([^"]*)" receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin)
|
s.Step(`^IMAP client "([^"]*)" receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin)
|
||||||
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as unread within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsUnreadWithin)
|
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as unread within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsUnreadWithin)
|
||||||
@ -73,6 +75,16 @@ func imapResponseNamedHasNumberOfMessages(clientID string, expectedCount int) er
|
|||||||
return ctx.GetTestingError()
|
return ctx.GetTestingError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func imapResponseDoesNotContain(notExpectedResponse string) error {
|
||||||
|
return imapResponseNamedDoesNotContain("imap", notExpectedResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imapResponseNamedDoesNotContain(clientID, notExpectedResponse string) error {
|
||||||
|
res := ctx.GetIMAPLastResponse(clientID)
|
||||||
|
res.AssertNotSections(notExpectedResponse)
|
||||||
|
return ctx.GetTestingError()
|
||||||
|
}
|
||||||
|
|
||||||
func imapClientReceivesUpdateMarkingMessageSeqAsReadWithin(messageSeq string, seconds int) error {
|
func imapClientReceivesUpdateMarkingMessageSeqAsReadWithin(messageSeq string, seconds int) error {
|
||||||
return imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin("imap", messageSeq, seconds)
|
return imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin("imap", messageSeq, seconds)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,6 +160,16 @@ func (ir *IMAPResponse) AssertSections(wantRegexps ...string) *IMAPResponse {
|
|||||||
return ir
|
return ir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertNotSections is similar to AssertSections but is the opposite.
|
||||||
|
// It means it just tries to find all "regexps" in the response.
|
||||||
|
func (ir *IMAPResponse) AssertNotSections(unwantedRegexps ...string) *IMAPResponse {
|
||||||
|
ir.wait()
|
||||||
|
for _, unwantedRegexp := range unwantedRegexps {
|
||||||
|
a.Error(ir.t, ir.hasSectionRegexp(unwantedRegexp), "regexp %v found\nSections: %v", unwantedRegexp, ir.sections)
|
||||||
|
}
|
||||||
|
return ir
|
||||||
|
}
|
||||||
|
|
||||||
// WaitForSections is the same as AssertSections but waits for `timeout` before giving up.
|
// WaitForSections is the same as AssertSections but waits for `timeout` before giving up.
|
||||||
func (ir *IMAPResponse) WaitForSections(timeout time.Duration, wantRegexps ...string) {
|
func (ir *IMAPResponse) WaitForSections(timeout time.Duration, wantRegexps ...string) {
|
||||||
a.Eventually(ir.t, func() bool {
|
a.Eventually(ir.t, func() bool {
|
||||||
|
|||||||
@ -33,10 +33,10 @@ func SMTPActionsAuthFeatureContext(s *godog.Suite) {
|
|||||||
s.Step(`^SMTP client authenticates with username "([^"]*)" and password "([^"]*)"$`, smtpClientAuthenticatesWithUsernameAndPassword)
|
s.Step(`^SMTP client authenticates with username "([^"]*)" and password "([^"]*)"$`, smtpClientAuthenticatesWithUsernameAndPassword)
|
||||||
s.Step(`^SMTP client logs out$`, smtpClientLogsOut)
|
s.Step(`^SMTP client logs out$`, smtpClientLogsOut)
|
||||||
s.Step(`^SMTP client sends message$`, smtpClientSendsMessage)
|
s.Step(`^SMTP client sends message$`, smtpClientSendsMessage)
|
||||||
s.Step(`^SMTP client sends EHLO$`, smtpClientSendsEHLO)
|
|
||||||
s.Step(`^SMTP client "([^"]*)" sends message$`, smtpClientNamedSendsMessage)
|
s.Step(`^SMTP client "([^"]*)" sends message$`, smtpClientNamedSendsMessage)
|
||||||
s.Step(`^SMTP client sends message with bcc "([^"]*)"$`, smtpClientSendsMessageWithBCC)
|
s.Step(`^SMTP client sends message with bcc "([^"]*)"$`, smtpClientSendsMessageWithBCC)
|
||||||
s.Step(`^SMTP client "([^"]*)" sends message with bcc "([^"]*)"$`, smtpClientNamedSendsMessageWithBCC)
|
s.Step(`^SMTP client "([^"]*)" sends message with bcc "([^"]*)"$`, smtpClientNamedSendsMessageWithBCC)
|
||||||
|
s.Step(`^SMTP client sends "([^"]*)"$`, smtpClientSendsCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
func smtpClientAuthenticates(bddUserID string) error {
|
func smtpClientAuthenticates(bddUserID string) error {
|
||||||
@ -93,12 +93,6 @@ func smtpClientSendsMessage(message *gherkin.DocString) error {
|
|||||||
return smtpClientNamedSendsMessage("smtp", message)
|
return smtpClientNamedSendsMessage("smtp", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func smtpClientSendsEHLO() error {
|
|
||||||
res := ctx.GetSMTPClient("smtp").SendCommands("EHLO ateist.test")
|
|
||||||
ctx.SetSMTPLastResponse("smtp", res)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func smtpClientNamedSendsMessage(clientID string, message *gherkin.DocString) error {
|
func smtpClientNamedSendsMessage(clientID string, message *gherkin.DocString) error {
|
||||||
return smtpClientNamedSendsMessageWithBCC(clientID, "", message)
|
return smtpClientNamedSendsMessageWithBCC(clientID, "", message)
|
||||||
}
|
}
|
||||||
@ -112,3 +106,11 @@ func smtpClientNamedSendsMessageWithBCC(clientID, bcc string, message *gherkin.D
|
|||||||
ctx.SetSMTPLastResponse(clientID, res)
|
ctx.SetSMTPLastResponse(clientID, res)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func smtpClientSendsCommand(command string) error {
|
||||||
|
command = strings.ReplaceAll(command, "\\r", "\r")
|
||||||
|
command = strings.ReplaceAll(command, "\\n", "\n")
|
||||||
|
res := ctx.GetSMTPClient("smtp").SendCommands(command)
|
||||||
|
ctx.SetSMTPLastResponse("smtp", res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package tests
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"net/textproto"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -97,6 +98,7 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
|||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
AddressID: account.AddressID(),
|
AddressID: account.AddressID(),
|
||||||
}
|
}
|
||||||
|
header := make(textproto.MIMEHeader)
|
||||||
|
|
||||||
if message.HasLabelID(pmapi.SentLabel) {
|
if message.HasLabelID(pmapi.SentLabel) {
|
||||||
message.Flags |= pmapi.FlagSent
|
message.Flags |= pmapi.FlagSent
|
||||||
@ -143,12 +145,14 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
|
|||||||
return internalError(err, "parsing time")
|
return internalError(err, "parsing time")
|
||||||
}
|
}
|
||||||
message.Time = date.Unix()
|
message.Time = date.Unix()
|
||||||
|
header.Set("Date", date.Format(time.RFC1123Z))
|
||||||
case "deleted":
|
case "deleted":
|
||||||
hasDeletedFlag = cell.Value == "true"
|
hasDeletedFlag = cell.Value == "true"
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
message.Header = mail.Header(header)
|
||||||
lastMessageID, err := ctx.GetPMAPIController().AddUserMessage(account.Username(), message)
|
lastMessageID, err := ctx.GetPMAPIController().AddUserMessage(account.Username(), message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return internalError(err, "adding message")
|
return internalError(err, "adding message")
|
||||||
|
|||||||
@ -143,6 +143,7 @@ func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTra
|
|||||||
if err := setRules(transferrer, rules); err != nil {
|
if err := setRules(transferrer, rules); err != nil {
|
||||||
return internalError(err, "failed to set rules")
|
return internalError(err, "failed to set rules")
|
||||||
}
|
}
|
||||||
|
transferrer.SetSkipEncryptedMessages(ctx.GetTransferSkipEncryptedMessages())
|
||||||
progress := transferrer.Start()
|
progress := transferrer.Start()
|
||||||
ctx.SetTransferProgress(progress)
|
ctx.SetTransferProgress(progress)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -40,6 +40,7 @@ func TransferChecksFeatureContext(s *godog.Suite) {
|
|||||||
s.Step(`^progress result is "([^"]*)"$`, progressFinishedWith)
|
s.Step(`^progress result is "([^"]*)"$`, progressFinishedWith)
|
||||||
s.Step(`^transfer exported (\d+) messages$`, transferExportedNumberOfMessages)
|
s.Step(`^transfer exported (\d+) messages$`, transferExportedNumberOfMessages)
|
||||||
s.Step(`^transfer imported (\d+) messages$`, transferImportedNumberOfMessages)
|
s.Step(`^transfer imported (\d+) messages$`, transferImportedNumberOfMessages)
|
||||||
|
s.Step(`^transfer skipped (\d+) messages$`, transferSkippedNumberOfMessages)
|
||||||
s.Step(`^transfer failed for (\d+) messages$`, transferFailedForNumberOfMessages)
|
s.Step(`^transfer failed for (\d+) messages$`, transferFailedForNumberOfMessages)
|
||||||
s.Step(`^transfer exported messages$`, transferExportedMessages)
|
s.Step(`^transfer exported messages$`, transferExportedMessages)
|
||||||
s.Step(`^exported messages match the original ones$`, exportedMessagesMatchTheOriginalOnes)
|
s.Step(`^exported messages match the original ones$`, exportedMessagesMatchTheOriginalOnes)
|
||||||
@ -77,6 +78,13 @@ func transferImportedNumberOfMessages(wantCount int) error {
|
|||||||
return ctx.GetTestingError()
|
return ctx.GetTestingError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transferSkippedNumberOfMessages(wantCount int) error {
|
||||||
|
progress := ctx.GetTransferProgress()
|
||||||
|
counts := progress.GetCounts()
|
||||||
|
a.Equal(ctx.GetTestingT(), uint(wantCount), counts.Skipped)
|
||||||
|
return ctx.GetTestingError()
|
||||||
|
}
|
||||||
|
|
||||||
func transferFailedForNumberOfMessages(wantCount int) error {
|
func transferFailedForNumberOfMessages(wantCount int) error {
|
||||||
progress := ctx.GetTransferProgress()
|
progress := ctx.GetTransferProgress()
|
||||||
failedMessages := progress.GetFailedMessages()
|
failedMessages := progress.GetFailedMessages()
|
||||||
|
|||||||
@ -41,6 +41,7 @@ func TransferSetupFeatureContext(s *godog.Suite) {
|
|||||||
s.Step(`^there are IMAP mailboxes$`, thereAreIMAPMailboxes)
|
s.Step(`^there are IMAP mailboxes$`, thereAreIMAPMailboxes)
|
||||||
s.Step(`^there are IMAP messages$`, thereAreIMAPMessages)
|
s.Step(`^there are IMAP messages$`, thereAreIMAPMessages)
|
||||||
s.Step(`^there is IMAP message in mailbox "([^"]*)" with seq (\d+), uid (\d+), time "([^"]*)" and subject "([^"]*)"$`, thereIsIMAPMessage)
|
s.Step(`^there is IMAP message in mailbox "([^"]*)" with seq (\d+), uid (\d+), time "([^"]*)" and subject "([^"]*)"$`, thereIsIMAPMessage)
|
||||||
|
s.Step(`^there is skip encrypted messages set to "([^"]*)"$`, thereIsSkipEncryptedMessagesSetTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func thereAreEMLFiles(messages *gherkin.DataTable) error {
|
func thereAreEMLFiles(messages *gherkin.DataTable) error {
|
||||||
@ -259,3 +260,15 @@ func createFile(fileName, body string) error {
|
|||||||
_, err = f.WriteString(body)
|
_, err = f.WriteString(body)
|
||||||
return internalError(err, "failed to write to file")
|
return internalError(err, "failed to write to file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func thereIsSkipEncryptedMessagesSetTo(value string) error {
|
||||||
|
switch value {
|
||||||
|
case "true":
|
||||||
|
ctx.SetTransferSkipEncryptedMessages(true)
|
||||||
|
case "false":
|
||||||
|
ctx.SetTransferSkipEncryptedMessages(false)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("expected either true or false, was %v", value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,6 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|||||||
140
utils/changelog_linter.sh
Executable file
140
utils/changelog_linter.sh
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
CHANGELOG_FILE="`dirname $0`/../Changelog.md"
|
||||||
|
|
||||||
|
ERROR_COUNT_FILE="`mktemp`"
|
||||||
|
echo "0">$ERROR_COUNT_FILE
|
||||||
|
|
||||||
|
##########################
|
||||||
|
# -- Helper functions -- #
|
||||||
|
##########################
|
||||||
|
|
||||||
|
# err print out a given error ($2) and line where it hapens ($1),
|
||||||
|
# also it increases count of errors.
|
||||||
|
err () {
|
||||||
|
echo "CHANGELOG-LINTER: $2 on the following line:"
|
||||||
|
echo "$1"
|
||||||
|
|
||||||
|
echo $((`cat $ERROR_COUNT_FILE` + 1)) > $ERROR_COUNT_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
# trim removes extra whitespaces.
|
||||||
|
trim () {
|
||||||
|
echo "$1"|sed 's/[ \t]*$//g;s/^[ \t]//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
# paragraph_continues checks that given line ends with sentence ending symbol like [.!?:].
|
||||||
|
is_ended() {
|
||||||
|
ENDING="${1: -1}"
|
||||||
|
[ "$ENDING" == "." ] || [ "$ENDING" == "!" ] || [ "$ENDING" == "?" ] || [ "$ENDING" == ":" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# paragraph_continues checks that given line starts with a small letter.
|
||||||
|
# (so it's not a start of a new sentence or block.)
|
||||||
|
paragraph_continues() {
|
||||||
|
LINE_START="${1:0:1}"
|
||||||
|
echo "$LINE_START" | grep -q "[a-z]"
|
||||||
|
}
|
||||||
|
|
||||||
|
##########################
|
||||||
|
# -- Linter functions -- #
|
||||||
|
##########################
|
||||||
|
|
||||||
|
# check_lists checks a format of lists.
|
||||||
|
# - Starting with " * " (minus lists is not allowed).
|
||||||
|
# - Containing whole sentence with first capital letter and dot at the end.
|
||||||
|
check_lists () {
|
||||||
|
if [ "${1:0:2}" == "- " ]; then
|
||||||
|
err "$1" '"-" lists is not allowed, use "*" instead'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${1:0:2}" != "* " ]; then # It's not a list. Skipping...
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_ended "$1" && ! paragraph_continues "$2"; then
|
||||||
|
err "$1" "List should contain a full sentence ending with one of the [!?.:] symbols"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! (echo "${1:0:3}" | grep -q "[A-Z\#\`\"]"); then
|
||||||
|
err "$1" "List should contain a full sentence starting with a capital letter"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check_change_types checks a format change type headings.
|
||||||
|
# - See https://keepachangelog.com/en/1.0.0/ for allowed formats.
|
||||||
|
check_change_types () {
|
||||||
|
if [ "${1:0:4}" != "### " ]; then # It's not a type heading. Skipping...
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
"### Added"|"### Changed"|"### Deprecated"|"### Removed"|"### Fixed"|"### Security") ;; # Standard keepachangelog.com compliant types.
|
||||||
|
"### Release notes"|"### Fixed bugs") ;; # Bridge aditional in app release notes types.
|
||||||
|
"### Guiding Principles"|"### Types of changes") ;; # Ignoring guide at the end of the changelog.
|
||||||
|
*) err "$1" "Change type must be one of the Added, Changed, Deprecated, Removed, Fixed, Hoftix"
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# check_application_name checks if a application name is defined on each record.
|
||||||
|
check_application_name () {
|
||||||
|
if [ "${1:0:4}" != "## [" ]; then # It's not a version heading. Skipping...
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "`echo $1|cut -d"[" -f2|cut -d" " -f1`" in
|
||||||
|
"IE"|"Bridge") ;;
|
||||||
|
*) err "$1" "Either \"IE\" or \"Bridge\" application name should be inside the version header"
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ignored_line determines lines which will not be linted.
|
||||||
|
ignored_line () {
|
||||||
|
[ "$1" == "" ] # Ignoring empty lines.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
######################
|
||||||
|
# -- Main routine -- #
|
||||||
|
######################
|
||||||
|
|
||||||
|
LINE_BEFORE="" # Storing the line before for some multiline operations.
|
||||||
|
cat $CHANGELOG_FILE|while read L; do
|
||||||
|
LINE=`trim "$L"`
|
||||||
|
|
||||||
|
if ignored_line "$LINE"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
check_lists "$LINE_BEFORE" "$LINE"
|
||||||
|
check_change_types "$LINE"
|
||||||
|
check_application_name "$LINE"
|
||||||
|
|
||||||
|
|
||||||
|
LINE_BEFORE="$LINE"
|
||||||
|
done
|
||||||
|
|
||||||
|
ERROR_COUNT=`cat $ERROR_COUNT_FILE`
|
||||||
|
rm $ERROR_COUNT_FILE
|
||||||
|
|
||||||
|
echo "CHANGELOG-LINTER found $ERROR_COUNT problems."|sed "s/found 0 problems/passed successfully ;)/"
|
||||||
|
exit $ERROR_COUNT
|
||||||
Reference in New Issue
Block a user