Compare commits

...

344 Commits

Author SHA1 Message Date
fd100eecc2 chore: update changelog for Quebec 3.1.0. 2023-04-03 14:16:30 +02:00
a11559fe58 chore: merge branch release/perth_narrows into release/Quebec 2023-04-03 14:08:05 +02:00
7d8e71c9ea fix(GODT-2505): show notification only for cases when user needs to do actions. 2023-04-03 11:21:46 +02:00
8b80938e49 fix(GODT-2516): log error when the vault key cannot be created/loaded from the keychain.
Backported from release/perth-narrows.
2023-04-03 08:44:26 +00:00
9bbeabcf50 feat(GODT-2523): use software QML rendering backend by default on Windows.
(cherry picked from commit 934749b278e95a9d69818ddf6b45ee7bb896af03)
2023-04-03 09:55:19 +02:00
de5fd07a22 feat(GODT-2500): Reorganise async methods. 2023-04-03 07:07:22 +02:00
ec92c918cd feat(GODT-2500): Add panic handlers everywhere. 2023-04-03 06:38:31 +02:00
9f59e61b14 feat(GODT-2511): add bridge-gui switches to permanently select the QML rendering backend.
(cherry picked from commit 03c3e95b34)
2023-03-30 20:16:38 +02:00
9756a7b51f fix(GODT-2526): Fix high memory usage with fetch/search
https://github.com/ProtonMail/gluon/pull/333
2023-03-29 14:49:36 +02:00
579e996d3a fix(GODT-2514): Apply Retry-After to 503
https://github.com/ProtonMail/go-proton-api/pull/67
2023-03-27 16:07:54 +02:00
7ba523393c fix(GODT-2524): Preserve old vault values
Keep the record of old vault settings alive to avoid issues when one
downgrades from a 3.1 release to a 3.0.x release.
2023-03-27 13:00:56 +00:00
0a0bcd7b90 fix(GODT-2508): Handle Address Updated for none-existing address
If we receive an address update and the address does not exist, attempt
to create it.
2023-03-27 13:49:27 +02:00
ee87e60239 fix(GODT-2504): Fix missing attachments in imported message
https://github.com/ProtonMail/go-proton-api/pull/64
2023-03-23 13:14:13 +01:00
babb4412ae chore: Bridge Perth Narrows 3.0.21 2023-03-23 10:10:07 +01:00
2166224e91 fix(GODT-2513): Scanner Crash in Gluon
Gluon MR: https://github.com/ProtonMail/gluon/pull/330
2023-03-22 13:19:15 +01:00
60df01eece fix(GODT-2513): Crash in scanner
Gluon MR: https://github.com/ProtonMail/gluon/pull/330
2023-03-22 13:12:34 +01:00
f1989193c0 feat(GODT-2509): Migrate TLS cert from v1/v2 location during upgrade to v3 2023-03-22 11:00:45 +01:00
4e7acd9091 feat(GODT-2509): Migrate TLS cert from v1/v2 location during upgrade to v3 2023-03-22 10:26:22 +01:00
3ca5d0af71 fix(GODT-2516): log error when the vault key cannot be created/loaded from the keychain. 2023-03-21 17:25:35 +01:00
9425e091d8 fix(GODT-2481): Fix DBUS Secert Service
Fix the path we are checking for was not updated for V3.

Ensure that we only inspect items that start with the correct prefix.
Some implementation (e.g.: KeepassXC) return some values which are not
valid.

Finally, remove unnecessary attributes.
2023-03-21 15:43:41 +01:00
bb770f3871 fix(GODT-2512): Catch unhandled API errors
Bump GPA https://github.com/ProtonMail/go-proton-api/pull/63
2023-03-21 13:46:55 +00:00
b1ad0ab6dc test: Add 503 request test for message create event
Simulate 503 status during a message create event when the message
data is being downloaded.
2023-03-21 14:37:20 +01:00
b63b56960e fix(GODT-2512): Catch unhandled API errors
Bump GPA https://github.com/ProtonMail/go-proton-api/pull/63
2023-03-21 11:56:32 +01:00
2d6e0c66a5 fix(GODT-2507): Memory consumption bug
Fix was made in Gluon: https://github.com/ProtonMail/gluon/pull/329
2023-03-21 08:45:01 +01:00
ed05823c78 fix(GODT-2418): Do not issue connector renames for inferiors
Bump Gluon (https://github.com/ProtonMail/gluon/pull/328) and add test.
2023-03-20 13:19:05 +01:00
ec351330f1 chore: Replace go-rfc5322 with gluon's rfc5322 parser
Bumps Gluon version in order to get the fixes from
https://github.com/ProtonMail/gluon/pull/327.
2023-03-20 10:25:41 +01:00
dd7c81ca4b fix(GODT-2497): Do not report EOF and network errors 2023-03-16 14:40:51 +01:00
f71a89c265 fix(GODT-2481): Fix DBUS Secert Service
Fix the path we are checking for was not updated for V3.

Ensure that we only inspect items that start with the correct prefix.
Some implementation (e.g.: KeepassXC) return some values which are not
valid.

Finally, remove unnecessary attributes.
2023-03-16 13:30:25 +01:00
31de358bfd feat(GODT-2487): add windows test job and worker. 2023-03-15 06:19:39 +00:00
212eac0925 chore: Bridge Quebec 3.1.0 - update changelog 2023-03-15 07:18:46 +01:00
e36c2b3d6e chore: Update GPA to include detailed error messages
https://github.com/ProtonMail/go-proton-api/pull/62
2023-03-14 15:47:48 +01:00
8b33d56b59 fix(GODT-2479): Ensure messages always have a text body part
Proton Backend requires that all messages have at least one text/plain
or text/html body part, even if it is empty.
2023-03-14 14:29:42 +01:00
48274ee178 feat(GODT-2482): more attachment to relevant exceptions. 2023-03-14 12:43:35 +00:00
4273405393 chore: Bridge Quebec 3.1.0 - update changelog 2023-03-13 16:51:33 +01:00
3a85de2f3c chore: merge branch release/perth_narrows into devel 2023-03-13 15:36:37 +01:00
a3aafabde3 feat(GODT-2455): upper limit for number of merged events. 2023-03-13 12:45:36 +00:00
30c1c14505 fix(GODT-2480): Do not override X-Original-Date with invalid Date 2023-03-13 12:37:27 +01:00
8164c41728 chore: merge release/perth_narrows into devel 2023-03-13 11:44:57 +01:00
1820af5021 chore: merge release/perth_narrows into devel 2023-03-13 11:40:54 +01:00
b57ca1506d fix(GODT-2473): Fix handling of complex mime types
When rebuilding attachments, ensure that more complicated mime types are
properly re-constructed.

If we fail to parse the mime type, set the value as is.
2023-03-13 10:10:36 +01:00
7eaf1655b2 chore: Bump Gluon for fixes
- GODT-2442: Handle DB deletes on windows
- GODT-2419: Improve Error Messages
- GODT-2430: Collect more info to diagnose issue
2023-03-10 16:35:33 +01:00
f627151d04 fix(GODT-2469): Fix sentry revision hash for cmake on windows. 2023-03-09 14:52:34 +01:00
7c232b1331 fix(GODT-2469): Fix sentry revision hash for cmake on windows. 2023-03-09 14:50:34 +01:00
7be46a4740 fix(GODT-2467): elide long email adresses in 'bad event' QML notification dialog. 2023-03-09 12:16:26 +01:00
b57c7abe92 fix(GODT-2442): Ensure DB gets moved during RemoveUser 2023-03-09 11:54:25 +01:00
c86c428718 chore: Bridge Perth Narrows 3.0.20 2023-03-09 07:26:47 +01:00
ed6e17a0ab fix(GODT-2442): bump GPA: refresh All cleans event history. 2023-03-08 17:53:59 +01:00
c3454360fc fix(GODT-2442): Remove unnecessary call to go-sync
This is not required in this call since address mode changes are always
proceeded by the removal of the IMAP user and then the creation of a new
IMAP user which will trigger the sync again.
2023-03-08 17:39:08 +01:00
182dab18a6 fix(GODT-2442): Handle event poll not starting after resync
It is possible, on slower machines, that the new event poll task is not
yet registered and attempts to cancel have nothing to cancel.

In this case, we need the refresh event to cancel the task, at that
point it is guaranteed that the task exists.
2023-03-08 17:39:04 +01:00
13c8a98389 fix(GODT-2442): move gluon DB before removal. 2023-03-08 17:21:03 +01:00
05a2c9d254 fix(GODT-2442): cli error 2023-03-08 15:32:25 +01:00
d926dd3806 chore: refactor: error cause type. 2023-03-08 11:57:19 +01:00
7cc2f3361d feat(GODT-2444): added queue system for UserBadEvent from different accounts. 2023-03-08 09:50:38 +01:00
c496d6c71c fix(GODT-2442): GUI changes for new bad event dialog. 2023-03-07 20:39:15 +01:00
7fc907a874 fix(GODT-2442): must publish loggedOut event. 2023-03-07 18:56:06 +01:00
b953468af2 fix(GODT-2419): avoid context canceled reports 2023-03-07 18:32:33 +01:00
9f4caa4948 feat(GODT-2442): add notification and feedback to CLI. 2023-03-07 17:59:04 +01:00
86630ce137 chore(GODT-2442): improve naming, remove unrelated changes 2023-03-07 17:59:04 +01:00
2c9477d65c fix(GODT-2442): WIP: bad events just aborts polls, feedback processed in separete channel. 2023-03-07 17:59:04 +01:00
34c002ff68 test(GODT-2442): test bad event feedback and clean-up. 2023-03-07 17:59:04 +01:00
f03688ba72 feat(GODT-2442): add gRPC interface to send feedback. 2023-03-07 17:59:04 +01:00
8c0bb22de3 feat(GODT-2442): handle bad event resync resolution. 2023-03-07 17:59:04 +01:00
53c2cbcaee test(GODT-2442): test refresh event 2023-03-07 17:59:04 +01:00
07339aff21 fix(GODT-2458): Wait for both bridge and bridge-gui to be ended before restarting on crash. 2023-03-07 16:47:07 +00:00
3ca56cfab3 fix(GODT-2458): Wait for both bridge and bridge-gui to be ended before restarting on crash. 2023-03-07 15:44:58 +00:00
59cf5e890b fix(GODT-2419): Reduce error spam an improve error messages
https://github.com/ProtonMail/gluon/pull/319
https://github.com/ProtonMail/gluon/pull/320
2023-03-07 13:32:04 +01:00
70950e0048 fix(GODT-2457): Include address if GetPublickKeys() error message 2023-03-07 12:58:12 +01:00
2781f06549 chore: Bridge Quebec v3.1.0. 2023-03-07 11:53:02 +01:00
febc994cec feat(GODT-2446): Attach logs to sentry reports for relevant bridge-gui exceptions.
Cherry picked from release/perth_narrows (2aa4e7c)

# Conflicts:
#	internal/frontend/bridge-gui/bridge-gui/AppController.cpp
#	internal/frontend/bridge-gui/bridge-gui/AppController.h
#	internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp
#	internal/frontend/bridge-gui/bridge-gui/LogUtils.h
#	internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp
#	internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp
#	internal/frontend/bridge-gui/bridge-gui/SentryUtils.h
#	internal/frontend/bridge-gui/bridge-gui/main.cpp
2023-03-07 10:04:21 +01:00
21ffde316d fix(GODT-2449): fix bug in Bridge-GUI's Exception::what().
Cherry picked from release/perth_narrows (1d426e6)
2023-03-07 08:52:57 +01:00
2aa4e7c9da feat(GODT-2446): Attach logs to sentry reports for relevant bridge-gui exceptions. 2023-03-06 18:36:40 +01:00
9058544f5c feat(GODT-2448): Supported Answered flag
Bump gluon and implement changes required for the flag changes.
2023-03-06 16:04:49 +01:00
227bbf1c03 fix(GODT-2425): Out of sync messages and read status
Apply fix required for gluon update.

Update Gluon to include revision where the fix was made. Includes:
 * https://github.com/ProtonMail/gluon/pull/313
 * https://github.com/ProtonMail/gluon/pull/316
 * https://github.com/ProtonMail/gluon/pull/317
2023-03-06 12:18:03 +00:00
667998c207 feat(GODT-2435): Group report exception by message if exception message looks corrupted. 2023-03-06 13:04:29 +01:00
1d426e621c fix(GODT-2449): fix bug in Bridge-GUI's Exception::what(). 2023-03-06 12:01:56 +00:00
6e4dcdb93b feat(GODT-2435): Group report exception by message if exception message looks corrupted. 2023-03-06 09:33:20 +01:00
20f35edc83 chore: fix missing import after cherry-pick. 2023-03-06 08:25:24 +00:00
26cf684fb8 chore: fill sentry user.id with hostname. 2023-03-06 08:25:24 +00:00
2a4cb6a916 chore: fix sentry tag for dev and release on GUI side. 2023-03-06 08:25:24 +00:00
caa4a5cbdb feat(GODT-2356): unify sentry release description and add more context to it. 2023-03-06 08:25:24 +00:00
91900a7942 feat(GODT-2357): Hide DSN_SENTRY and use single setting point for DSN_SENTRY. 2023-03-06 08:25:24 +00:00
04a7a81e27 chore(GODT-2444): Bad event info 2023-03-03 13:02:09 +00:00
53d5619c51 fix(GODT-2447): Don't assume timestamp exists in log filename 2023-03-03 10:27:21 +01:00
f793b71569 fix(GODT-2447): Don't assume timestamp exists in log filename 2023-03-03 10:10:06 +01:00
8aec11a634 chore: Bump Gluon for GODT-2427, GODT-2419, GODT-2429
https://github.com/ProtonMail/gluon/pull/313
https://github.com/ProtonMail/gluon/pull/312
2023-03-02 12:50:22 +00:00
30651a531b fix(GODT-2427): Fix header parser
Fix is included in gluon
2023-03-02 13:48:14 +01:00
91ab77dce9 fix(GODT-2424): Sync Builder Message Split
Incorrect math was causing some messages to not be built and be missing
from Gluon.
2023-03-01 13:13:20 +01:00
69aa784d32 fix(GODT-2426): Fix crash on user delete
Ensure we are always acquiring a write lock when modifying the user's
`updateCh` contents.
2023-03-01 12:02:12 +01:00
825031e052 fix(GODT-2426): Fix crash on user delete
Ensure we are always acquiring a write lock when modifying the user's
`updateCh` contents.
2023-03-01 11:22:05 +01:00
ee4a8939d5 fix(GODT-2419): Use connector.ErrOperationNotAllowed
Return this error when we detect operations that we know are not allowed
so that gluon does not report them to sentry.

Includes Gluon update for the connector error
(https://github.com/ProtonMail/gluon/pull/309)
2023-03-01 10:44:18 +01:00
89117bbd59 fix(GODT-2413): use qEnvironmentVariable() instead of qgetenv().
(cherry picked from commit 10cf153678)
2023-03-01 07:51:03 +01:00
9e4310712c fix(GODT-2419): Use connector.ErrOperationNotAllowed
Return this error when we detect operations that we know are not allowed
so that gluon does not report them to sentry.

Includes Gluon update for the connector error
(https://github.com/ProtonMail/gluon/pull/309)
2023-02-28 17:02:35 +01:00
0b35b275d3 fix(GODT-2333): Do not allow modifications to All Mail label
Rather than waiting for API to reply, prevent these operations from
taking place in the first place.
2023-02-28 16:51:23 +01:00
d6acb0fb19 fix(GODT-2418): Ensure child folders are updated when parent is 2023-02-28 14:25:11 +01:00
2db5a04e7a fix(GODT-2417): Gluon update to address ticket issues
* GODT-2417: Fixes connector requests for recovered messages.
* GODT-2416: Allow message updates to work if the literal is missing.
2023-02-28 13:41:15 +01:00
d7bfee2414 feat(GODT-2382): Added bridge-gui settings file with 'UseSoftwareRenderer' value. 2023-02-28 13:04:49 +01:00
4a6460f3ed feat(GODT-2411): allow qmake executable to be named qmake6.
And a free a typo fix.

(cherry picked from commit 4153a69253)
2023-02-28 09:05:50 +01:00
c6f1f159f3 chore: Bridge Perth Narrows 3.0.18 2023-02-28 06:53:16 +01:00
82af4e01bc feat(GODT-2364): wait and retry once if the gRPC service config file exists but cannot be opened. 2023-02-28 06:21:36 +01:00
9ad5f74409 feat(GODT-2364): added optional details to C++ exceptions. 2023-02-28 06:21:25 +01:00
10cf153678 fix(GODT-2413): use qEnvironmentVariable() instead of qgetenv(). 2023-02-27 15:41:26 +01:00
2b09796d1c fix(GODT-2399): Fix immediate message deletion during updates
Fix was applied in Gluon.
2023-02-27 15:09:11 +01:00
5ba07db7e3 chore: Bump Gluon for GODT-2399, GODT-2400 and GODT-2414
fix(GODT-2399): Defer updated message deletion
fix(GODT-2400): Allow state updates to be applied if command fails
fix(GODT-2414): Multiple deletion bug in WriteControlledStore
2023-02-27 14:53:37 +01:00
ad0d4ebd36 fix(GODT-2412): Don't treat context cancellation as BadEvent 2023-02-27 14:34:35 +01:00
9f3c14ab1e fix(GODT-2404): Handle unexpected EOF
When fetching too many attachment bodies at once, the read can fail with
io.ErrUnexpectedEOF. In that case, we returun an error so the fetch is retried.
2023-02-27 14:33:44 +01:00
74cf5d422b fix(GODT-2390): Missing changes from pervious commit
Always reports error type to sentry.

Add error checks for get event as well.
2023-02-27 14:33:38 +01:00
dcf694588c fix(GODT-2390): Add reports for uncaught json and net.opErr
Report to sentry if we see some uncaught network err, but don't force
the user logout.

If we catch an uncaught json parser error we report the error to sentry
and let the user be logged out later.

Finally this patch also prints the error type in UserBadEvent sentry
report to further help diagnose issues.
2023-02-27 14:33:21 +01:00
a2b9fc3dee feat(GODT-2273): menu with "Close window" and "Quit Bridge" button in main window. 2023-02-27 14:19:00 +01:00
761c16d8cd fix(GODT-2412): Don't treat context cancellation as BadEvent 2023-02-27 12:48:22 +00:00
810705ba01 fix(GODT-1945): Handle disabled addresses correctly
When listing all a user's email addresses (e.g. for apple mail autoconf)
we need to exclude disabled addresses. Similarly, we need to remove
them from gluon if using split mode.
2023-02-27 12:52:39 +01:00
c15917aba4 fix(GODT-2404): Handle unexpected EOF
When fetching too many attachment bodies at once, the read can fail with
io.ErrUnexpectedEOF. In that case, we returun an error so the fetch is retried.
2023-02-24 16:02:21 +00:00
51cbb91513 Revert GODT-2373 (bridgelib). 2023-02-24 15:22:12 +01:00
f8bfbaf361 fix(GODT-2390): Missing changes from pervious commit
Always reports error type to sentry.

Add error checks for get event as well.
2023-02-24 12:45:13 +01:00
3e878058e7 fix(GODT-2390): Add reports for uncaught json and net.opErr
Report to sentry if we see some uncaught network err, but don't force
the user logout.

If we catch an uncaught json parser error we report the error to sentry
and let the user be logged out later.

Finally this patch also prints the error type in UserBadEvent sentry
report to further help diagnose issues.
2023-02-24 11:01:44 +01:00
065dcd4d47 fix(GODT-2393): improved handling of unrecoverable error.
Fix a bug introduced in 265af2d. In the top level exception handler, we may not have add the time to establish connection, so we should not try to use the gRPC service to quit bridge.
2023-02-23 18:08:23 +01:00
00256fafe8 fix(GODT-2394): Bump Gluon for golang.org/x/text DoS risk
This also fixes a harmless error related to multiple deletions in the store.
2023-02-23 13:09:06 +01:00
671db7c516 chore: Fix typo loggin back in 2023-02-23 11:53:41 +01:00
e9f20aee7a fix(GODT-2387): Ensure vault can be unlocked after factory reset
When performing a factory reset, we don't want to wipe all keychain
entries. The only keychain entry should be the vault's passphrase,
and we need this to be able to decrypt the vault at next startup
(to avoid it being reported as corrupt).
2023-02-23 08:11:20 +00:00
8534da98ea chore: fix missing windows header for bridgelib.
bridgelib was moved from bridge-gui to bridgepp project, and windows.h is not auto-included anymore (only Qt core is in the pre-compiled headers).
2023-02-23 06:54:05 +00:00
265af2d299 fix(GODT-2389): close bridge on exception and add max termination wait time. 2023-02-23 07:22:30 +01:00
82c388a0dd chore: Bridge Perth Narrows 3.0.18 2023-02-23 06:58:54 +01:00
89112baf96 feat(GODT-2261): sync progress in GUI. 2023-02-22 12:11:42 +00:00
5007d451c2 feat(GODT-2385): Gluon cache fallback
Update Gluon to have access to the cache fallback reader.

Provide fallback reader to handle old cache file format.

Remove the old logic to erase all cache files on start as the fallback
option renders this irrelevant.
2023-02-22 12:00:23 +01:00
4775fb4b22 fix(GODT-2201): Add missing rfc5322.CharsetReader initialization 2023-02-22 09:07:21 +01:00
94ed09b437 feat(GODT-2366): Handle failed message updates as creates
This handles the following case:
- event says message was created
- we try to fetch the message but API says the doesn’t exist yet — we skip applying the “message created” update
- event then says message was updated at some point in the future
- we try to handle it but fail because we don’t have the message — we should treat it as a creation
2023-02-21 16:07:27 +01:00
57962e5757 chore: Bump gluon to create missing messages during MessageUpdated 2023-02-21 16:07:27 +01:00
8a5c8eaf6e chore: Use gluon temp/hotfix-perth-narrows branch 2023-02-21 16:07:27 +01:00
a75a72a2b9 chore: Bump Gluon version for Go rfc5322 date parser 2023-02-21 15:46:24 +01:00
038eb6d243 feat(GODT-2366): Handle failed message updates as creates
This handles the following case:
- event says message was created
- we try to fetch the message but API says the doesn’t exist yet — we skip applying the “message created” update
- event then says message was updated at some point in the future
- we try to handle it but fail because we don’t have the message — we should treat it as a creation
2023-02-21 15:20:05 +01:00
2bd8f6938a chore: Bump gluon to create missing messages during MessageUpdated 2023-02-21 12:53:05 +01:00
889f3286b5 chore: Bump gluon to use new address parser 2023-02-21 12:15:37 +01:00
9dfdd07f7a fix(GODT-2381): Unset draft flag on sent messages 2023-02-20 15:37:36 +01:00
0b796f4401 feat(GODT-2373): bridgelib tests. 2023-02-20 14:47:14 +01:00
a741ffb595 feat(GODT-2373): introducing bridgelib Go dynamic library in bridge-gui. 2023-02-20 12:21:02 +01:00
cf8284a489 fix(GODT-2380): Only set external ID in header if non-empty 2023-02-20 11:16:50 +01:00
6a9f6a173a feat(GODT-2201): Bump Gluon to use pure Go IMAP parser 2023-02-17 14:25:16 +00:00
54c013012e feat(GODT-2374): Import TLS certs via shell 2023-02-17 13:49:04 +00:00
cac0cf35f6 feat(GODT-2361): Bump GPA to use simple encrypter 2023-02-17 14:06:15 +01:00
30029f489e doc: changelog typo 2023-02-17 13:34:03 +01:00
2faeebe9e7 chore: Bridge Perth Narrows 3.0.16/17 2023-02-16 17:46:31 +01:00
f6727a56d2 fix(GODT-2371): Continue, not return, when handling draft 2023-02-16 17:46:24 +01:00
4c24c004db fix(GODT-2371): Continue, not return, when handling draft 2023-02-16 17:23:22 +01:00
571133f2ff chore: added bridge-gui CMake variable for crashpad_handler path.
Ignored in release mode.
2023-02-15 17:49:15 +00:00
0207fa04f1 feat(GODT-2364): wait and retry once if the gRPC service config file exists but cannot be opened. 2023-02-15 17:56:56 +01:00
98fdf45fa3 feat(GODT-2364): added optional details to C++ exceptions. 2023-02-15 16:25:21 +00:00
21b3a4bca3 chore: fill sentry user.id with hostname. 2023-02-15 16:11:04 +00:00
7225fc31da chore: fix sentry tag for dev and release on GUI side. 2023-02-15 16:10:58 +01:00
eca4810f19 test: step definitions changed 2023-02-15 14:56:46 +01:00
249658c05b chore: merge branch release/perth_narrows to devel 2023-02-15 14:39:28 +01:00
da82d7a107 fix(GODT-2365): Use predictable remote ID for placeholder mailboxes 2023-02-15 10:42:47 +01:00
08dab2d115 feat(GODT-1264): constraint on Scheduled mailbox in connector + Integration tests. 2023-02-15 07:37:09 +00:00
13db1b0db8 feat(GODT-2356): unify sentry release description and add more context to it. 2023-02-14 16:27:55 +00:00
c1921a811b ci: always use the 'eventually' variant step and remove others. 2023-02-14 17:08:10 +01:00
473be3d485 feat(GODT-2357): Hide DSN_SENTRY and use single setting point for DSN_SENTRY. 2023-02-13 20:31:32 +01:00
d7fd39503f chore: Bridge Perth Narrows 3.0.15/16 2023-02-13 15:06:36 +01:00
b4b66f94ec feat(GODT-2355): improve wording and actions on bad event 2023-02-13 14:27:34 +01:00
0823d393ed feat(GODT-1264): creation and visibility of the 'Scheduled' system label.
feat(GODT-1264): typo in error message
feat(GODT-1264): fix split mode broken by previous commit.
2023-02-10 15:24:31 +01:00
cbd36184bd feat(GODT-2354): report failed to load users. 2023-02-10 11:53:05 +00:00
465f754803 feat(GODT-2353): show popup only after 3.0.16 2023-02-09 17:00:58 +01:00
8b9265ad96 feat(GODT-2283): Limit max import size to 30MB (bump GPA to v0.4.0) 2023-02-09 16:35:08 +01:00
5f930c262c feat(GODT-2352): only copy resource file when needed. 2023-02-09 13:01:38 +01:00
afa95d4799 fix(GODT-2351): Bump GPA to automatically retry on net.OpError 2023-02-09 12:36:44 +01:00
2fa7c97f39 fix(GODT-2351): Bump GPA to better handle net.OpError 2023-02-09 12:04:39 +01:00
2b75fcf773 feat(GODT-2352): use go-build-finalize macro to build vault-editor for both mac arch 2023-02-09 10:44:18 +00:00
cdff2ef792 fix(GODT-2351): Bump GPA to properly handle net.OpError and add tests 2023-02-08 15:16:28 +00:00
a740a8f962 feat(GODT-2278): properly override server_name for go. 2023-02-08 15:28:58 +01:00
d1f1c390f6 test: Bump test timeout from 5m to 10m 2023-02-08 13:17:31 +00:00
1c88ce3cc0 feat(GODT-2255): Randomize the focus service port. 2023-02-08 10:06:53 +00:00
c4ef1a24c0 fix(GODT-2347): Prevent updates from being dropped if goroutine doesn't start fast
If the install handler goroutine is busy, the update is dropped.
This was intended to prevent two installs from happening at once.
However, it also means that updates can be dropped at startup if the
goroutine isn't spawned soon enough.

A fix is to allow all jobs through and just reject ones that are
for an old version.
2023-02-07 18:26:13 +01:00
a79fce907e test: Bump GPA to fix flaky test TestBridge_User_BadMessage_NoBadEvent
It was necessary to return an actual proton.APIError json object
in order for our detection to work properly in one of the tests.
2023-02-07 18:26:13 +01:00
c9d496956c test: Refactor account management, fix map-random-order race condition
Some SMTP tests made use of disabled addresses. We stored addresses
in a map, meaning the order was randomized. This lead to tests sometimes
attempting to authenticate over SMTP using a disabled address, failing.
2023-02-07 18:26:13 +01:00
31dce41276 test: Fix race condition with initialization of messageIDs
Need to make sure the messageIDs slice is created properly before
it is used async in a different goroutine.
2023-02-07 18:03:36 +01:00
c6576dfc4b fix(GODT-2327): Remove unnecessary sync when changing address mode 2023-02-07 17:53:53 +01:00
9048b14fdb chore: Bridge Perth Narrows v3.0.14 2023-02-07 16:36:38 +01:00
43100d11bf fix(GODT-2323): Fix Expunge not issued for move
When moving between system labels the expunge commands were not being
issued.
2023-02-07 15:09:23 +01:00
4876314cf5 fix(GODT-2341): Handle URL error 2023-02-07 14:31:06 +01:00
2f75131710 fix(GODT-2340): improve logging 2023-02-07 14:31:06 +01:00
1e09fd6662 feat(GODT-2278): improve sentry logs. 2023-02-07 14:31:06 +01:00
48f2c56caa fix(GODT-2327): Better sleep (with context) 2023-02-07 14:31:06 +01:00
20d83dd476 fix(GODT-2327): Loop to retry until sync has complete 2023-02-07 14:31:06 +01:00
9c6be78b4c fix(GODT-2327): Don't retry with abortable context because it's canceled 2023-02-07 14:31:06 +01:00
0a8e71771e fix(GODT-2327): Fix lint issue 2023-02-07 14:31:06 +01:00
29d1c7bccd fix(GODT-2327): Remove unnecessary sync abort call 2023-02-07 14:31:06 +01:00
ca1996a670 fix(GODT-2327): Properly cancel event stream when handling refresh 2023-02-07 14:31:06 +01:00
ab1c1c474a fix(GODT-2327): Clear update channels whenever clearing sync status 2023-02-07 14:31:06 +01:00
d7cac8a8f0 fix(GODT-2327): avoid windows delete all deadlock 2023-02-07 14:31:06 +01:00
63bc87cc86 fix(GODT-2327): Only start processing events once sync is finished 2023-02-07 14:31:06 +01:00
232875d5cc fix(GODT-2327): Delay event processing until gluon user exists
We don't want to start processing events until those events have
somewhere to be sent to.

Also, to be safe, ensure remove and re-add the gluon user while
clearing its sync status. This shouldn't be necessary.
2023-02-07 14:31:02 +01:00
f3c5e300cd fix(GODT-2327): Delay event processing until gluon user exists
We don't want to start processing events until those events have
somewhere to be sent to.

Also, to be safe, ensure remove and re-add the gluon user while
clearing its sync status. This shouldn't be necessary.

fix(GODT-2327): Only start processing events once sync is finished

fix(GODT-2327): avoid windows delete all deadlock

fix(GODT-2327): Clear update channels whenever clearing sync status

fix(GODT-2327): Properly cancel event stream when handling refresh

fix(GODT-2327): Remove unnecessary sync abort call

fix(GODT-2327): Fix lint issue

fix(GODT-2327): Don't retry with abortable context because it's canceled

fix(GODT-2327): Loop to retry until sync has complete

fix(GODT-2327): Better sleep (with context)
2023-02-07 13:41:16 +01:00
29072f0285 fix(GODT-2343): Only poll after send if sync is complete 2023-02-06 16:39:32 +00:00
5ea53ea5c0 fix(GODT-2318): Remove gluon DB if label sync was incomplete 2023-02-06 16:36:15 +00:00
40aca0fe73 fix(GODT-2336): Recover from changed address order while bridge is down 2023-02-06 16:03:33 +00:00
f4a2fb9687 test: Add failing test for changing address order while bridge is down 2023-02-06 16:03:33 +00:00
367c505444 fix(GODT-1804): Use cherry-picked mail settings in GPA 2023-02-06 15:57:24 +00:00
3bd39b3ea5 fix(GODT-1804): Only promote content headers if non-empty
When attaching public key, we take the root mime part, create a new root,
and put the old root alongside an additional public key mime part.
But when moving the root, we would copy all content headers, even empty ones.
So we’d be left with Content-Disposition: "" which would fail to parse.
2023-02-06 15:57:24 +00:00
e89dcb2cca fix(GODT-2343): Only poll after send if sync is complete 2023-02-06 16:33:53 +01:00
ad65bdde9d fix(GODT-2326): Fix potential Win32 API deadlock
Update gluon so that the store implementation uses `os.Remove` instead
of `os.RemoveAll`. The latter has an issue where it can deadlock on
windows. See https://github.com/golang/go/issues/36375 for more details.
2023-02-06 16:30:03 +01:00
34cd611a8b chore: Disable funlen linter 2023-02-06 14:29:13 +00:00
d82b71de89 fix(GODT-1804): Only promote content headers if non-empty
When attaching public key, we take the root mime part, create a new root,
and put the old root alongside an additional public key mime part.
But when moving the root, we would copy all content headers, even empty ones.
So we’d be left with Content-Disposition: "" which would fail to parse.
2023-02-06 13:18:08 +00:00
8894a982f2 ci(GODT-2287): add test coverage on devel 2023-02-06 10:50:47 +01:00
2cb2ca15c7 fix(GODT-2336): Recover from changed address order while bridge is down 2023-02-03 16:05:30 +01:00
db41645159 test: Add failing test for changing address order while bridge is down 2023-02-03 15:51:18 +01:00
a74d1ce9ca feat(GODT-2144): Handle IMAP/SMTP server errors via event stream 2023-02-03 14:11:53 +00:00
2e832520e6 chore: Remove panics from SetGluonDir 2023-02-03 14:11:53 +00:00
62285a141e feat(GODT-2144): Delay IMAP/SMTP server start until all users are loaded 2023-02-03 14:11:53 +00:00
c3d5a0b8f8 feat(GODT-2295): notifications for IMAP login when signed out. 2023-02-03 11:18:59 +01:00
a36dbbf422 fix(GODT-2333): Do not allow modifications to All Mail label
Rather than waiting for API to reply, prevent these operations from
taking place in the first place.
2023-02-03 05:26:42 +00:00
4cf23bb2e6 fix(GODT-2328): Ignore labels that aren't part of user label set 2023-02-02 16:59:07 +01:00
e2c1f38ed3 fix(GODT-2328): Ignore labels that aren't part of user label set 2023-02-02 15:39:33 +00:00
5ec1da34b4 feat(GODT-2278): improve sentry logs. 2023-02-02 15:36:37 +00:00
ea11c1046a chore: Bridge Perth Narrows v3.0.13 2023-02-02 16:30:45 +01:00
df40f27069 test(GODT-2326): Remove user tests
These tests no longer work due to sync only being started after an
account has been added. Functionality of these tests is covered in the
bridge unit tests.
2023-02-02 16:26:38 +01:00
76d732f247 fix(GODT-2326): Only run sync after addIMAPUser()
There is concurrency bug due to competing sync calls that can occur when
we clear the sync status in the Vault. Running sync at the end of
addIMAPUser() avoids the problem.

This patch also remove the execution of a sync task for
`user.ClearSyncStatus()`
2023-02-02 16:26:12 +01:00
219400de8d test(GODT-2298): add integration test for In-Reply-To header use cases. 2023-02-02 15:25:27 +00:00
8901d83c94 test(GODT-2326): Remove user tests
These tests no longer work due to sync only being started after an
account has been added. Functionality of these tests is covered in the
bridge unit tests.
2023-02-02 13:04:05 +01:00
0c8d4e8dd8 fix(GODT-2326): Only run sync after addIMAPUser()
There is concurrency bug due to competing sync calls that can occur when
we clear the sync status in the Vault. Running sync at the end of
addIMAPUser() avoids the problem.

This patch also remove the execution of a sync task for
`user.ClearSyncStatus()`
2023-02-02 11:32:54 +01:00
a955dcbaa9 fix(GODT-2323): Fix Expunge not issued for move
When moving between system labels the expunge commands were not being
issued.
2023-02-02 07:21:01 +01:00
fbac5134ca fix(GODT-2224): Properly handle context cancellation during sync
There was an issue where new attachment download requests would hang
forever due to not checking whether the context was cancelled. At this
point there were no more workers to consume to channel messages.
2023-02-01 15:08:41 +01:00
45ec6b6e74 feat(GODT-2289): UIDValidity as Timestamp
Update UIDValidity to be timestamp with the number of seconds since
the 1st of February 2023. This avoids the problem where we lose the
last UIDValidity value due to the vault being missing/corrupted/deleted.
2023-02-01 14:04:45 +01:00
b17bdad864 chore: Bridge Perth Narrows v3.0.13 2023-02-01 10:42:33 +01:00
52daa165a2 fix(GODT-2319): seed the math/rand RNG on app startup. 2023-02-01 10:42:09 +01:00
4c5ba04822 fix(GODT-1804): Preserve MIME parameters when uploading attachments 2023-02-01 10:41:55 +01:00
79c2523585 chore: Add recipient to SMTP error message 2023-01-31 17:35:51 +01:00
f14ad8b3fa chore: README update. 2023-01-31 15:48:07 +00:00
590fdacba3 fix(GODT-2319): seed the math/rand RNG on app startup. 2023-01-31 15:28:03 +00:00
342a2a5568 fix(GODT-2272): use shorter filename for gRPC file socket. 2023-01-31 15:51:06 +01:00
e382687168 fix(GODT-2318): Remove gluon DB if label sync was incomplete 2023-01-31 12:40:42 +00:00
4577a40b1e fix(GODT-2224): Restore parallel attachment download
Feature was not restored in previous MR. Attachment are now download in
parallel. There is a pool of maxParallelDownloads attachment downloaders
shared with all message downloads.
2023-01-31 12:25:20 +01:00
c0aacb7d62 GODT-2224: Allow the user to specify max sync memory usage in Vault 2023-01-31 09:40:22 +01:00
c8065c8092 GODT-2312: used space is properly updated. 2023-01-31 07:22:03 +00:00
ce03bfbf0f GODT-1804: Preserve MIME parameters when uploading attachments 2023-01-30 20:12:41 +01:00
0182e2c0bc GODT-2287: Add code coverage to artifacts and pipeline. 2023-01-30 16:07:11 +00:00
e464e11ab9 GODT-2224: Refactor bridge sync to use less memory
Updates go-proton-api and Gluon to includes memory reduction changes and
modify the sync process to take into account how much memory is used
during the sync stage.

The sync process now has an extra stage which first download the message
metada to ensure that we only download up to `syncMaxDownloadRequesMem`
messages or 250 messages total. This allows for scaling the download
request automatically to accommodate many small or few very large
messages.

The IDs are then sent to a download go-routine which downloads the
message and its attachments. The result is then forwarded to another
go-routine which builds the actual message. This stage tries to ensure
that we don't use more than `syncMaxMessageBuildingMem` to build these
messages.

Finally the result is sent to a last go-routine which applies the
changes to Gluon and waits for them to be completed.

The new process is currently limited to 2GB. Dynamic scaling will be
implemented in a follow up. For systems with less than 2GB of memory we
limit the values to a set of values that is known to work.
2023-01-30 15:05:43 +01:00
d7ff54d679 Other: Bridge Perth Narrows v3.0.1 hotfix 2 2023-01-30 08:35:22 +01:00
4aa1091f62 GODT-2210: fix splash screen always showing on CentOS and Ubuntu. 2023-01-27 14:10:14 +01:00
6d024d2055 Other: Other: Bridge Perth Narrows v3.0.1 hotfix 2023-01-27 10:15:53 +01:00
c86cdf737f GODT-2311: Fix missing headers in re-downloaded Gluon messages 2023-01-27 09:55:02 +01:00
7e36b215fe GODT-1453: clicking 'Sign in' from status window now selects the right account. 2023-01-26 17:27:55 +01:00
badebbef9f GODT-2297: More significantly improve GPA's paging algorithm 2023-01-26 12:43:30 +01:00
5e072c3282 Other: Slightly improve GPA's paging algorithm 2023-01-26 09:48:19 +01:00
048c3a900c GODT-2145: Fix button spacing w/ Qt 6.4. 2023-01-25 17:35:21 +01:00
88a9fe410c Other: Bridge Perth Narrows v3.0.12 2023-01-25 15:14:30 +01:00
cf32b84257 GODT-2305: Detect missing gluon DB 2023-01-25 14:55:13 +01:00
e7dea0a77f GODT-2223(test): Fix array access off-by-one in test code 2023-01-25 13:05:05 +01:00
160489a771 GODT-2223: Testing event processing scenarios. 2023-01-25 13:05:04 +01:00
996c6826b9 GODT-2223: Handle gracefully multiple create events with same id. 2023-01-25 13:04:04 +01:00
43b871a124 GODT-2210: Typo fix 2023-01-25 09:51:18 +00:00
d1bf186040 Other: changed splash screen icon. 2023-01-25 09:42:00 +00:00
24c68f100e GODT-2210: v3.0 splash screen.
Other: new splash screen content.
2023-01-25 09:42:00 +00:00
1bfabf9a83 GODT-2296: Log error rather than fail if cannot get parent ID 2023-01-25 09:09:14 +00:00
e8a778feca GODT-2266: Pause event stream while sending 2023-01-25 09:47:27 +01:00
5d4c10c56e Other(test): Fix some more integration test placeholders 2023-01-24 16:58:25 +00:00
60b1c4d8f7 GODT-2177: Use correct attachment disposition when content ID is set 2023-01-24 16:31:14 +01:00
f1404cd3ee GODT-1556: If no references, use the in-reply-to header as ParentID. 2023-01-24 15:36:23 +01:00
ee4da8a89c GODT-2291: Change gluon store default location from Cache to Data. 2023-01-24 13:35:45 +00:00
5a70a16149 GODT-1770: handle UserBadEvent in CLI and gRPC. 2023-01-24 13:07:27 +01:00
f019ba3713 Other: Disable dialer test until badssl cert is bumbed. 2023-01-24 09:59:32 +00:00
4b966c4845 GODT-2292: Updated BUILDS.md doc. 2023-01-24 09:28:43 +01:00
94703bcf37 GODT-2223: Treat label/message deletion errors as non-critical 2023-01-23 21:25:15 +01:00
40cc6b54c9 Other: make GUI Tester more resilient to Bridge abrupt termination. 2023-01-23 08:26:52 +01:00
584ea7e9f8 GODT-2275: fixed location of bridge-gui log files. 2023-01-20 15:38:38 +01:00
cbdbc124db GODT-2258: suggest email as login when signing in via status window. 2023-01-20 15:02:44 +01:00
b9c3fa9401 GODT-2266: Add test for sent message flags 2023-01-20 13:04:01 +00:00
0e4ec8a8b8 Other: Ensure SMTP debug dump works on windows 2023-01-20 13:33:14 +01:00
c3e4bb80a8 Other: Fix MaxLogs off-by-one limit and bump limit to 10 2023-01-20 12:52:22 +01:00
6459840507 GODT-2253: Restart Launcher from the gui when GUI crashes. 2023-01-20 07:43:02 +00:00
87abbe9396 Other: Report corrupt and/or insecure vaults to sentry 2023-01-20 08:00:52 +01:00
d26a8319b6 Other: Better user load logs 2023-01-19 17:47:45 +01:00
c9c80fd861 Other(test): Make All Mail copy test more robust 2023-01-19 15:09:56 +00:00
fea4cc7b3b Other: fix path of temp folder in README. 2023-01-19 14:40:13 +00:00
cba5da22ae Other(CI): Make race checks manual 2023-01-19 14:22:43 +00:00
59a29da054 Other(debug): Dump raw SMTP input to user's home dir 2023-01-19 13:30:28 +01:00
c70674471e Other: Remove old cert/key file location handling. 2023-01-19 09:28:44 +00:00
7882324439 GODT-2271: Update README with new system files path. 2023-01-19 09:11:14 +00:00
1f8866a48a Other: Bridge Perth Narrows v3.0.11 2023-01-18 16:18:39 +01:00
faf28a6d4e GODT-2223: Fix mutex double lock 2023-01-18 14:10:23 +00:00
59745e6fb6 GODT-2223: Ensure apiLabels map is properly up to date 2023-01-18 14:10:23 +00:00
c8925cd270 GODT-2223(test): Assert that bad request errors lead to user logout 2023-01-18 14:10:23 +00:00
e35f3b6056 GODT-2223: Fix handling of create/update label events 2023-01-18 14:10:23 +00:00
d68014ec7b GODT-2223: Handle attempting to fetch a message that was just deleted 2023-01-18 14:10:23 +00:00
b63029054d GODT-2223: Fix handling deletion of nonexistent labels 2023-01-18 14:10:23 +00:00
849c8bee78 GODT-2223: Handle bad events by logging user out 2023-01-18 14:10:23 +00:00
70f0384cc3 GODT-2165: Reduce UTF8 parsing errors from TLS header input
Updates Gluon to include fix which does not report UTF-8 errors when we
parse a TLS header on a non TLS connection.
2023-01-18 13:06:14 +01:00
5459720523 Others: chores fix a QML warning when no account is present, and a few typos in QML. 2023-01-18 11:04:13 +01:00
a00e2acb5c Other: Don't clean settings path on teardown 2023-01-18 08:24:31 +00:00
1d405076e6 Other(test): Fix integration test steps 2023-01-18 07:16:24 +00:00
7119c566ef Other: Bump GPA to v0.3.0 2023-01-17 14:10:07 +00:00
0e9428aaae GODT-2252: Recover from deleted cached messages
Update to latest Gluon version and implement the new
`Connector.GetMessageLiteral` function.
2023-01-17 14:02:39 +01:00
fe009ca235 GODT-2258: change login label and suggest email instead of username. 2023-01-17 13:13:43 +01:00
a377384553 Other: added user's primary email address to the vault. 2023-01-17 11:27:54 +01:00
03c8c323bc GODT-2251: Store gluon cache in user cache rather than user data 2023-01-16 16:27:41 +01:00
fdbc380421 GODT-2251: Store gluon DB in user config rather than cache directory
Gluon data was stored in the user's "data dir". This is
~/.local/share on linux, but was the user's "cache dir" on windows/mac.
As a result, it would sometimes be deleted to reclaim disk space.

This change ensures the "data dir" is persistent on windows/mac.
2023-01-16 15:14:00 +00:00
7056134b24 GODT-2093: use the primary email address in the account view and status view. 2023-01-16 14:45:15 +01:00
93c7552a41 GODT-2202: Report update errors from Gluon
For every update sent to gluon wait and check the error code to see if
an error occurred.

Note: Updates can't be inspect on the call site as it can lead to
deadlocks.
2023-01-13 15:54:31 +01:00
931ed119bb GODT-2226: Fix moving drafts to trash
Only handle draft updates if the event was a message update. Also
includes Gluon update.
2023-01-12 14:25:27 +01:00
0580842ad2 GODT-2229: Own the full path for gluon and do not change Database path. 2023-01-12 13:23:09 +00:00
8d9db83a87 GODT-2246: do not report API error 422 when using an invalid email address. 2023-01-12 12:36:46 +01:00
c3eb6b2dbf GODT-1797: copyright notice shows a date range with the build year. 2023-01-11 16:59:05 +01:00
777ad369a2 Other: Bridge Perth Narrows v3.0.10, scope change 2023-01-11 10:24:25 +01:00
715efaa087 Revert "GODT-2229: Allow changing cache folder to a non-empty folder."
This reverts commit b19e16e4b8.
2023-01-11 10:19:38 +01:00
606a8f134d Revert "Other: Update Gluon"
This reverts commit 761b98f02f.
2023-01-11 10:19:16 +01:00
84e92ca69f Other: Bridge Perth Narrows v3.0.10 2023-01-11 09:42:31 +01:00
0f0f8b3461 GODT-2205: use lock file in bridge-gui to detect orphan bridge. 2023-01-11 08:22:46 +01:00
761b98f02f Other: Update Gluon
Includes fix for not panicking on out of or UID insertion.
2023-01-10 17:54:53 +01:00
b19e16e4b8 GODT-2229: Allow changing cache folder to a non-empty folder. 2023-01-10 16:40:52 +00:00
407c9fe1a6 GODT-2181: Empty but not nil address from API 2023-01-10 14:54:29 +00:00
0b61f8f146 GODT-2242: Bump GPA - Don't send any 2fa information if not needed. 2023-01-10 13:23:17 +00:00
06eee89479 GODT-1817: Port old user feature tests 2023-01-10 11:47:05 +01:00
e3a43e4ca8 GODT-2179: added handler for exceptions in QML backend methods.
GODT-2179: added custom QApplication class to handle exceptions.
GODT-2179: wired sentry report in AppController error handler.
2023-01-10 08:33:42 +01:00
f876ffab52 GODT-1817: Add missing IMAP auth tests with disabled & secondary accounts 2023-01-09 15:10:39 +00:00
0dcd4ca133 GODT-1817: Restore missing SMTP feature tests
Requires update to GPA to set disabled state on addresses.
2023-01-09 15:10:39 +00:00
2562d1e77d GODT-1817: Do not allow authentication of disabled accounts 2023-01-09 15:10:39 +00:00
e1531c200c GODT-1817: Delete old smtp send feature tests
All these tests have already been ported.
2023-01-09 15:10:39 +00:00
c09bc742d8 GODT-1817: Delete on update and spam test features
These are handled by Gluon and the update_spam feature is not compatible
with the current architecture. Do note messages that IMAP client move
messages to the Folder with the \Junk attribute, which is correctly
mapped into Gluon.
2023-01-09 15:10:39 +00:00
29e8d07693 GODT-1817: Delete old tests that are already ported or handled in Gluon 2023-01-09 15:10:39 +00:00
4fd4e8a16e GODT-2181: Add env proxy support for integration tests. 2023-01-09 13:26:08 +01:00
30d627c2be Other: reorganised QMLBackend class code. 2023-01-09 10:15:21 +01:00
9390cb64b4 GODT-1817: Restore move related feature tests
Gluon updated to latest dev commit, required for feature. Checks from
move_local_folder.feature are implemented in Gluon.
2023-01-06 10:58:07 +01:00
d720feaa6d Other: Flag messages imported into "Sent" mailbox as Sent 2023-01-06 10:58:07 +01:00
9f7cda3b69 Other: Fix testCtx.getMBoxID()
Ensure we always translate the labels to their full name so they match
properly on all commands.
2023-01-06 10:58:07 +01:00
878f67a051 GODT-1817: Delete old fetch test
These are tested in Gluon instead.
2023-01-06 10:58:07 +01:00
7fb8550c97 GODT-1817: Port missing import feature tests 2023-01-06 10:58:07 +01:00
700836aea0 Other: fIxed GUI Tester to comply with latest gRPC changes. 2023-01-06 08:24:34 +01:00
16aaa1b050 GODT-2010: add Cocoa app delegate handler for second application instance. 2023-01-05 17:12:02 +01:00
8790d3cfcf Other: C++ Code reformat. 2023-01-05 08:37:38 +01:00
bb07138fb0 GODT-2236: add log entry when SMTP / IMAP serve method fails. 2023-01-04 16:45:34 +01:00
37c650e490 GODT-1817: Remove deleted check from copy.feature message tests
This check is only possible if the messages are imported via imap APPEND
commands and not through the API client. The latter has no way to
express this state.
2023-01-04 13:37:28 +01:00
272e3895fd GODT-1817: Restore old date message feature test + fix
This patch also fixes the message builder to not override other headers
that already exist to avoid overriding sanitized header entries.
2023-01-04 13:37:28 +01:00
6e7f374b0d GODT-1817: Remove old Drafts and Delete tests.
Test have been ported and other features are validated in Gluon.
2023-01-04 13:37:28 +01:00
3743e45566 GODT-2221: Set DOH off by default. 2023-01-04 12:08:06 +00:00
b10e8abde0 GODT-2234: added command-line switch to force Qt to use software rendering for QML. 2023-01-03 17:54:57 +01:00
5dab4422e9 Other: added C/C++ header template file (*.h.in) type to missing_license.sh script. 2023-01-03 17:42:53 +01:00
82b6037a00 GODT-1817: Add create check to validate mailbox creation
Only allow mailboxes with the "Folder" or "Label/" prefix.
2023-01-03 10:19:11 +01:00
1bdb8b2724 GODT-1817: Add tests skips reporter checks to feature tests
Some tests on failure will produce sentry reports. Add a way to skip
the check to see if any reports are produce when we know they will be
triggered.
2023-01-03 10:19:05 +01:00
8c905e4f42 GODT-1817: Port missing IMAP create feature test 2023-01-02 13:37:40 +01:00
e9e59a2704 GODT-1817: Port over missing IMAP copy feature test 2023-01-02 13:37:40 +01:00
e3a1482b8f Other: Fix double close on event channels 2023-01-02 13:37:40 +01:00
9539b24d64 GODT-1817: Delete old feature test files
All these feature test have either been ported or are already tested in
Gluon.
2023-01-02 13:37:40 +01:00
87caeef0af GODT-1817: Delete unnecessary IDLE tests
Gluon tests already cover this.
2023-01-02 13:37:40 +01:00
757e8a02ec GODT-2233: Fix sub folder creation bug
Sub folders with more than 2 levels of depth (e.g.: Folders/first/second)
could not be created since we did not update the known label list we use
to validate the request.
2023-01-02 11:41:49 +01:00
6d0a128111 Other: Update copyright year 2023-01-02 11:09:11 +01:00
28b36d379b GODT-1817: Update IMAP commands to push errors to error stack 2022-12-21 14:29:42 +01:00
038b5d1437 GODT-2222: Dot not error on unknown Address Events
Prevent infinite error loop in event parsing by not returning errors if
we already have or do not have a given address. This occurs since we
sync the latest state at Bridge startup but still receive the events
which contain these changes later.
2022-12-21 10:16:02 +01:00
038e1794eb GODT-2218: Fix invalid UID ranges
Fix applied in Gluon
2022-12-21 09:15:54 +01:00
551 changed files with 20302 additions and 14887 deletions

View File

@ -32,7 +32,7 @@ stages:
.rules-branch-and-MR-always:
rules:
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
allow_failure: false
- when: never
@ -54,16 +54,27 @@ stages:
allow_failure: true
- when: never
.rules-branch-manual-MR-always-allow-failure:
.rules-branch-manual-MR-and-devel-always:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
allow_failure: true
allow_failure: false
- if: $CI_COMMIT_BRANCH
when: manual
allow_failure: true
- when: never
.after-script-code-coverage:
after_script:
- go get github.com/boumenot/gocover-cobertura
- go run github.com/boumenot/gocover-cobertura < /tmp/coverage.out > coverage.xml
- "go tool cover -func=/tmp/coverage.out | grep total:"
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
# Stage: TEST
@ -76,46 +87,62 @@ lint:
tags:
- medium
test-linux:
.test-base:
stage: test
extends:
- .rules-branch-manual-MR-always
script:
- make test
test-linux:
extends:
- .test-base
- .rules-branch-manual-MR-and-devel-always
- .after-script-code-coverage
tags:
- medium
test-linux-race:
stage: test
extends:
- .rules-branch-manual-MR-always-allow-failure
- test-linux
- .rules-branch-and-MR-manual
script:
- make test-race
tags:
- medium
test-integration:
stage: test
extends:
- .rules-branch-manual-MR-always
- test-linux
script:
- make test-integration
tags:
- large
test-integration-race:
stage: test
extends:
- .rules-branch-manual-MR-always-allow-failure
- test-integration
- .rules-branch-and-MR-manual
script:
- make test-integration-race
tags:
- large
dependency-updates:
.windows-base:
before_script:
- export GOROOT=/c/Go1.18
- export PATH=$GOROOT/bin:$PATH
- export GOARCH=amd64
- export GOPATH=~/go18
- export GO111MODULE=on
- export PATH=$GOPATH/bin:$PATH
- export MSYSTEM=
tags:
- windows-bridge
test-windows:
extends:
- .rules-branch-manual-MR-always
- .windows-base
stage: test
script:
- make updates
- make test
# Stage: BUILD
@ -131,31 +158,20 @@ dependency-updates:
when: manual
allow_failure: true
- when: never
before_script:
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
- export PATH=$PATH:$QT6DIR/bin
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
script:
- make build
- git diff && git diff-index --quiet HEAD
- make vault-editor
artifacts:
# 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.
expire_in: 1 day
when: always
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
- vault-editor
tags:
- large
build-linux:
extends: .build-base
.linux-build-setup:
image: gitlab.protontech.ch:4567/go/bridge-internal:qt6
variables:
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
@ -164,19 +180,29 @@ build-linux:
paths:
- .cache
when: 'always'
artifacts:
name: "bridge-linux-$CI_COMMIT_SHORT_SHA"
before_script:
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
- export PATH=$PATH:$QT6DIR/bin
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
tags:
- large
build-linux:
extends:
- .build-base
- .linux-build-setup
build-linux-qa:
extends: build-linux
extends:
- build-linux
variables:
BUILD_TAGS: "build_qa"
artifacts:
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
.build-darwin-base:
extends: .build-base
.darwin-build-setup:
before_script:
- export PATH=/usr/local/bin:$PATH
- export PATH=/usr/local/opt/git/bin:$PATH
@ -188,30 +214,22 @@ build-linux-qa:
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
script:
- go version
- make build-nogui
- git diff && git diff-index --quiet HEAD
- make vault-editor
cache: {}
tags:
- macOS
build-darwin:
extends: .build-darwin-base
artifacts:
name: "bridge-darwin-$CI_COMMIT_SHORT_SHA"
extends:
- .build-base
- .darwin-build-setup
build-darwin-qa:
extends: .build-darwin-base
extends:
- build-darwin
variables:
BUILD_TAGS: "build_qa"
artifacts:
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
.build-windows-base:
extends: .build-base
.windows-build-setup:
before_script:
- export GOROOT=/c/Go1.18/
- export PATH=$GOROOT/bin:$PATH
@ -225,23 +243,19 @@ build-darwin-qa:
- export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH"
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
script:
- make build-nogui
- git diff && git diff-index --quiet HEAD
- make vault-editor
cache: {}
tags:
- windows-bridge
build-windows:
extends: .build-windows-base
artifacts:
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
extends:
- .build-base
- .windows-build-setup
build-windows-qa:
extends: .build-windows-base
extends:
- build-windows
variables:
BUILD_TAGS: "build_qa"
artifacts:
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
# TODO: PUT BACK ALL THE JOBS! JUST DID THIS FOR NOW TO GET CI WORKING AGAIN...

View File

@ -23,7 +23,6 @@ issues:
- path: _test\.go
linters:
- dupl
- funlen
- gochecknoglobals
- gochecknoinits
- gosec
@ -32,7 +31,6 @@ issues:
- path: test
linters:
- dupl
- funlen
- gochecknoglobals
- gochecknoinits
- gosec
@ -64,7 +62,6 @@ linters:
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]

View File

@ -5,13 +5,13 @@
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
* Go 1.18
* Bash with basic build utils: make, gcc, sed, find, grep, ...
- For Windows it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
* GCC (linux), msvc (windows) or Xcode (macOS)
* Windres (windows)
* libglvnd and libsecret development files (linux)
To enable the sending of crash reports using Sentry please set the
`main.DSNSentry` value with the client key of your sentry project before build.
`DSN_SENTRY` environment variable with the client key of your sentry project before build.
Otherwise, the sending of crash reports will be disabled.
## Build
@ -44,9 +44,10 @@ make build
make build-nogui
```
* Bridge without GUI will start by default without any interface (i.e., there is no way to add or remove client, get bridge password, etc)
* Bridge always has the option (whether built with Qt or without) to use a CLI interface by starting it with the argument `-c`
* NOTE: You still need to setup supported keychain on your system
* To launch Bridge without GUI, you can invoke the `bridge` executable with one the following command-line switches:
* `--noninteractive` or `-n` to start Bridge without any interface (i.e., there is no way to add or remove client, get bridge password, etc.)
* `--cli` or `-c` to start Bridge with an interactive terminal interface.
* NOTE: You still need to set up a supported keychain on your system.
## Launchers
Launchers are only included in official distributions and provide the public

View File

@ -26,7 +26,6 @@ Proton Mail Bridge includes the following 3rd party software:
* [gluon](https://github.com/ProtonMail/gluon) available under [license](https://github.com/ProtonMail/gluon/blob/master/LICENSE)
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
* [go-proton-api](https://github.com/ProtonMail/go-proton-api) available under [license](https://github.com/ProtonMail/go-proton-api/blob/master/LICENSE)
* [go-rfc5322](https://github.com/ProtonMail/go-rfc5322) available under [license](https://github.com/ProtonMail/go-rfc5322/blob/master/LICENSE)
* [gopenpgp](https://github.com/ProtonMail/gopenpgp/v2) available under [license](https://github.com/ProtonMail/gopenpgp/v2/blob/master/LICENSE)
* [goquery](https://github.com/PuerkitoBio/goquery) available under [license](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE)
* [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE)
@ -53,6 +52,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
* [profile](https://github.com/pkg/profile) available under [license](https://github.com/pkg/profile/blob/master/LICENSE)
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
@ -76,8 +76,9 @@ Proton Mail Bridge includes the following 3rd party software:
* [readline](https://github.com/abiosoft/readline) available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
* [levenshtein](https://github.com/agext/levenshtein) available under [license](https://github.com/agext/levenshtein/blob/master/LICENSE)
* [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
* [antlr](https://github.com/antlr/antlr4/runtime/Go/antlr) available under [license](https://github.com/antlr/antlr4/runtime/Go/antlr/blob/master/LICENSE)
* [go-textseg](https://github.com/apparentlymart/go-textseg/v13) available under [license](https://github.com/apparentlymart/go-textseg/v13/blob/master/LICENSE)
* [sonic](https://github.com/bytedance/sonic) available under [license](https://github.com/bytedance/sonic/blob/master/LICENSE)
* [base64x](https://github.com/chenzhuoyu/base64x) available under [license](https://github.com/chenzhuoyu/base64x/blob/master/LICENSE)
* [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
* [circl](https://github.com/cloudflare/circl) available under [license](https://github.com/cloudflare/circl/blob/master/LICENSE)
* [go-md2man](https://github.com/cpuguy83/go-md2man/v2) available under [license](https://github.com/cpuguy83/go-md2man/v2/blob/master/LICENSE)
@ -88,6 +89,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/blob/master/LICENSE)
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
* [sse](https://github.com/gin-contrib/sse) available under [license](https://github.com/gin-contrib/sse/blob/master/LICENSE)
* [gin](https://github.com/gin-gonic/gin) available under [license](https://github.com/gin-gonic/gin/blob/master/LICENSE)
@ -97,6 +99,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
@ -104,6 +107,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [hcl](https://github.com/hashicorp/hcl/v2) available under [license](https://github.com/hashicorp/hcl/v2/blob/master/LICENSE)
* [multierror](https://github.com/joeshaw/multierror) available under [license](https://github.com/joeshaw/multierror/blob/master/LICENSE)
* [go](https://github.com/json-iterator/go) available under [license](https://github.com/json-iterator/go/blob/master/LICENSE)
* [cpuid](https://github.com/klauspost/cpuid/v2) available under [license](https://github.com/klauspost/cpuid/v2/blob/master/LICENSE)
* [go-urn](https://github.com/leodido/go-urn) available under [license](https://github.com/leodido/go-urn/blob/master/LICENSE)
* [go-colorable](https://github.com/mattn/go-colorable) available under [license](https://github.com/mattn/go-colorable/blob/master/LICENSE)
* [go-isatty](https://github.com/mattn/go-isatty) available under [license](https://github.com/mattn/go-isatty/blob/master/LICENSE)
@ -114,25 +118,26 @@ Proton Mail Bridge includes the following 3rd party software:
* [reflect2](https://github.com/modern-go/reflect2) available under [license](https://github.com/modern-go/reflect2/blob/master/LICENSE)
* [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE)
* [go-toml](https://github.com/pelletier/go-toml/v2) available under [license](https://github.com/pelletier/go-toml/v2/blob/master/LICENSE)
* [lz4](https://github.com/pierrec/lz4/v4) available under [license](https://github.com/pierrec/lz4/v4/blob/master/LICENSE)
* [go-difflib](https://github.com/pmezard/go-difflib) available under [license](https://github.com/pmezard/go-difflib/blob/master/LICENSE)
* [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE)
* [uniseg](https://github.com/rivo/uniseg) available under [license](https://github.com/rivo/uniseg/blob/master/LICENSE)
* [blackfriday](https://github.com/russross/blackfriday/v2) available under [license](https://github.com/russross/blackfriday/v2/blob/master/LICENSE)
* [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE)
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
* [golang-asm](https://github.com/twitchyliquid64/golang-asm) available under [license](https://github.com/twitchyliquid64/golang-asm/blob/master/LICENSE)
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
* [go-cty](https://github.com/zclconf/go-cty) available under [license](https://github.com/zclconf/go-cty/blob/master/LICENSE)
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
* [genproto](https://google.golang.org/genproto)
gopkg.in/yaml.v2
gopkg.in/yaml.v3
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
* [go-imap](https://github.com/ProtonMail/go-imap) available under [license](https://github.com/ProtonMail/go-imap/blob/master/LICENSE)
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
<!-- END AUTOGEN -->

View File

@ -1,7 +1,286 @@
# Proton Mail Bridge and Import-Export app Changelog
# Proton Mail Bridge Changelog
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [Bridge 3.1.0] Quebec
### Changed
* GODT-2523: Use software QML rendering backend by default on Windows.
* GODT-2500: Reorganise async methods.
* GODT-2500: Add panic handlers everywhere.
* GODT-2511: Add bridge-gui switches to permanently select the QML rendering backend.
* GODT-2509: Migrate TLS cert from v1/v2 location during upgrade to v3.
* GODT-2487: Add windows test job and worker.
* Update GPA to include detailed error messages.
* GODT-2479: Ensure messages always have a text body part.
* GODT-2482: More attachment to relevant exceptions.
* GODT-2224: Refactor bridge sync to use less memory.
* GODT-2448: Supported Answered flag.
* GODT-2382: Added bridge-gui settings file with 'UseSoftwareRenderer' value.
* GODT-2411: Allow qmake executable to be named qmake6.
* GODT-2273: Menu with "Close window" and "Quit Bridge" button in main window.
* GODT-2261: Sync progress in GUI.
* GODT-2385: Gluon cache fallback.
* GODT-2366: Handle failed message updates as creates.
* GODT-2201: Bump Gluon to use pure Go IMAP parser.
* GODT-2374: Import TLS certs via shell.
* GODT-2361: Bump GPA to use simple encrypter.
* GODT-1264: Constraint on Scheduled mailbox in connector + Integration tests.
* GODT-1264: Creation and visibility of the 'Scheduled' system label.
* GODT-2283: Limit max import size to 30MB (bump GPA to v0.4.0).
* GODT-2352: Only copy resource file when needed.
* GODT-2352: Use go-build-finalize macro to build vault-editor for both mac arch.
* GODT-2278: Properly override server_name for go.
* GODT-2255: Randomize the focus service port.
* GODT-2144: Handle IMAP/SMTP server errors via event stream.
* GODT-2144: Delay IMAP/SMTP server start until all users are loaded.
* GODT-2295: Notifications for IMAP login when signed out.
* GODT-2278: Improve sentry logs.
* GODT-2289: UIDValidity as Timestamp.
### Fixed
* GODT-2505: Show notification only for cases when user needs to do actions.
* GODT-2516: Log error when the vault key cannot be created/loaded from the keychain.
* GODT-2526: Fix high memory usage with fetch/search.
* GODT-2514: Apply Retry-After to 503.
* GODT-2524: Preserve old vault values.
* GODT-2508: Handle Address Updated for none-existing address.
* GODT-2504: Fix missing attachments in imported message.
* GODT-2513: Scanner Crash in Gluon.
* GODT-2512: Catch unhandled API errors.
* GODT-2507: Memory consumption bug.
* GODT-2497: Do not report EOF and network errors.
* GODT-2481: Fix DBUS Secert Service.
* GODT-2455: Upper limit for number of merged events.
* GODT-2480: Do not override X-Original-Date with invalid Date.
* GODT-2473: Fix handling of complex mime types.
* GODT-2469: Fix sentry revision hash for cmake on windows.
* GODT-2424: Sync Builder Message Split.
* GODT-2419: Use connector.ErrOperationNotAllowed.
* GODT-2418: Ensure child folders are updated when parent is.
* GODT-1945: Handle disabled addresses correctly.
* GODT-2390: Add reports for uncaught json and net.opErr.
* GODT-2393: Improved handling of unrecoverable error.
* GODT-2394: Bump Gluon for golang.org/x/text DoS risk.
* GODT-2387: Ensure vault can be unlocked after factory reset.
* GODT-2389: Close bridge on exception and add max termination wait time.
* GODT-2201: Add missing rfc5322.CharsetReader initialization.
* GODT-1804: Preserve MIME parameters when uploading attachments.
* GODT-2312: Used space is properly updated.
* GODT-2319: Seed the math/rand RNG on app startup.
* GODT-2272: Use shorter filename for gRPC file socket.
* GODT-2318: Remove gluon DB if label sync was incomplete.
* GODT-2326: Only run sync after addIMAPUser().
* GODT-2323: Fix Expunge not issued for move.
* GODT-2224: Properly handle context cancellation during sync.
* GODT-2328: Ignore labels that aren't part of user label set.
* GODT-2326: Fix potential Win32 API deadlock.
* GODT-1804: Only promote content headers if non-empty.
* GODT-2327: Remove unnecessary sync when changing address mode.
* GODT-2343: Only poll after send if sync is complete.
* GODT-2336: Recover from changed address order while bridge is down.
* GODT-2347: Prevent updates from being dropped if goroutine doesn't start fast.
* GODT-2351: Bump GPA to properly handle net.OpError and add tests.
* GODT-2351: Bump GPA to automatically retry on net.OpError.
* GODT-2365: Use predictable remote ID for placeholder mailboxes.
* GODT-2381: Unset draft flag on sent messages.
* GODT-2380: Only set external ID in header if non-empty.
## [Bridge 3.0.21] Perth Narrows
### Added
* GODT-2509: Migrate TLS cert from v1/v2 location during upgrade to v3.
### Changed
* GODT-2516: log error when the vault key cannot be created/loaded from the keychain.
### Fixed
* GODT-2501: Remove additional .desktop file.
* GODT-2513: Crash in scanner.
* GODT-2481: Fix DBUS Secert Service.
* GODT-2512: Catch unhandled API errors.
* GODT-2469: Fix sentry revision hash for cmake on windows.
## [Bridge 3.0.20] Perth Narrows
### Added
* GODT-2442: Allow user to re-sync DB without logout.
### Changed
* GODT-2419: Reduce sentry reports.
* GODT-2458: Wait for both bridge and bridge-gui to be ended before restarting on crash.
* GODT-2457: Include address if GetPublickKeys() error message.
* GODT-2446: Attach logs to sentry reports for relevant bridge-gui exceptions.
* GODT-2425: Out of sync messages and read status.
* GODT-2435: Group report exception by message if exception message looks corrupted.
* GODT-2356: Unify sentry release description and add more context to it.
* GODT-2357: Hide DSN_SENTRY and use single setting point for DSN_SENTRY.
* GODT-2444: Bad event info.
* GODT-2447: Don't assume timestamp exists in log filename.
* GODT-2333: Do not allow modifications to All Mail label.
* GODT-2429: Do not report context cancel to sentry.
### Fixed
* GODT-2467: elide long email adresses in 'bad event' QML notification dialog.
* GODT-2449: fix bug in Bridge-GUI's Exception::what().
* GODT-2427: Parsing header issues.
* GODT-2426: Fix crash on user delete.
* GODT-2417: Do not request gluon recovered message from API.
## [Bridge 3.0.19] Perth Narrows
### Fixed
* GODT-2364: Wait and retry once if the gRPC service config file exists but cannot be opened.
* GODT-2364: Added optional details to C++ exceptions.
* GODT-2413: Use qEnvironmentVariable() instead of qgetenv().
* GODT-2412: Don't treat context cancellation as BadEvent.
* GODT-2404: Handle unexpected EOF.
* GODT-2400: Allow state updates to be applied if command fails.
* GODT-2399: Fix immediate message deletion during updates.
* GODT-2390: Missing changes from pervious commit.
* GODT-2390: Add reports for uncaught json and net.opErr.
* GODT-2414: Multiple deletion bug in WriteControlledStore.
## [Bridge 3.0.18] Perth Narrows
### Fixed
* GODT-2392: Create message if gluon updateMessage returns `no such message`.
* GODT-2391: Create draft if missing during message update on gluon side.
## [Bridge 3.0.16/17] Perth Narrows
### Fixed
* GODT-2371: Continue, not return, when handling draft.
## [Bridge 3.0.15] Perth Narrows
### Changed
* GODT-2355: Improve wording and actions on bad event.
### Fixed
* GODT-2354: Report failed load users.
* GODT-2353: Show popup only after 3.0.16.
* GODT-2351: Bump GPA to better handle net.OpError.
## [Bridge 3.0.14] Perth Narrows
### Fixed
* GODT-2323: Fix Expunge not issued for move.
* GODT-2341: Handle URL error.
* GODT-2340: Improve logging.
* GODT-2278: Improve sentry logs.
* GODT-2327: Sync issues when migrating DB.
* GODT-2318: Remove gluon DB if label sync was incomplete.
* GODT-1804: Only promote content headers if non-empty.
* GODT-2343: Only poll after send if sync is complete.
* GODT-2336: Recover from changed address order while bridge is down.
## [Bridge 3.0.13] Perth Narrows
### Fixed
GODT-2328: Ignore labels that aren't part of user label set.
GODT-2326: Sync issue on missing fresh DB file.
GODT-2319: Seed the math/rand RNG on app startup.
GODT-1804: Preserve MIME parameters when uploading attachments.
## [Bridge 3.0.12] Perth Narrows
### Added
* GODT-2210: v3.0 splash screen.
* GODT-1770: handle UserBadEvent in CLI and gRPC.
### Changed
* GODT-2311: Fix missing headers in re-downloaded Gluon messages.
* GODT-1453: clicking 'Sign in' from status window now selects the right account.
* GODT-2297: More significantly improve GPA's paging algorithm.
* GODT-2145: Fix button spacing w/ Qt 6.4.
* GODT-2223: Improve event handling.
* GODT-2305: Detect missing gluon DB.
* GODT-2291: Change gluon store default location from Cache to Data.
* Other: Disable dialer test until badssl cert is bumbed.
* GODT-2292: Updated BUILDS.md doc.
* GODT-2258: suggest email as login when signing in via status window.
* Other: Report corrupt and/or insecure vaults to sentry.
* Other: Better user load logs.
* GODT-2253: Restart Launcher from the gui when GUI crashes.
* Other(test): Make All Mail copy test more robust.
* Other(CI): Make race checks manual.
* Other: Remove old cert/key file location handling.
* GODT-2271: Update README with new system files path.
### Fixed
* GODT-2210: Fix splash screen always showing on CentOS and Ubuntu.
* GODT-2296: Log error rather than fail if cannot get parent ID.
* GODT-2266: Pause event stream while sending.
* GODT-2266: Add test for sent message flags.
* Other(test): Fix some more integration test placeholders.
* GODT-2177: Use correct attachment disposition when content ID is set.
* GODT-1556: If no references, use the in-reply-to header as ParentID.
* Other: make GUI Tester more resilient to Bridge abrupt termination.
* GODT-2275: fixed location of bridge-gui log files.
* Other: Ensure SMTP debug dump works on windows.
* Other: Fix MaxLogs off-by-one limit and bump limit to 10.
* Other: fix path of temp folder in README.
* Other(debug): Dump raw SMTP input to user's home dir.
## [Bridge 3.0.11] Perth Narrows
### Changed
* GODT-2252: Recover from deleted cached messages.
* GODT-2258: change login label and suggest email instead of username.
* Other: Don't clean settings path on teardown.
* Other: Bump GPA to v0.3.0.
* Other: added user's primary email address to the vault.
* GODT-2251: gluon store and DB separated.
* GODT-2093: use the primary email address in the account view and status view.
* GODT-2202: Report update errors from Gluon.
* GODT-2229: Own the full path for gluon and do not change Database path.
* GODT-1797: copyright notice shows a date range with the build year.
### Fixed
* GODT-2223: Handle bad events by logging user out.
* GODT-2165: Reduce UTF8 parsing errors from TLS header input.
* Others: chores fix a QML warning when no account is present* and a few typos in QML.
* Other(test): Fix integration test steps.
* GODT-2226: Fix moving drafts to trash.
* GODT-2246: do not report API error 422 when using an invalid email address.
## [Bridge 3.0.10] Perth Narrows
### Changed
* GODT-2205: use lock file in bridge-gui to detect orphan bridge.
* GODT-2242: Bump GPA - Don't send any 2fa information if not needed.
* GODT-2179: added handler for exceptions in QML backend methods.
* GODT-2181: Match live API behaviour.
* GODT-2221: Set DOH off by default.
* GODT-1817: Re-enable all integration tests.
* Other: C++ Code reformat.
* GODT-2234: added command-line switch to force Qt to use software rendering for QML.
* Other: added C/C++ header template file (*.h.in) type to missing_license.sh script.
* GODT-2236: add log entry when SMTP / IMAP serve method fails.
* Other: reorganised QMLBackend class code.
### Fixed
* Other: Flag messages imported into "Sent" mailbox as Sent.
* Other: Fix testCtx.getMBoxID().
* Other: Fixed GUI Tester to comply with latest gRPC changes.
* GODT-2010: add Cocoa app delegate handler for second application instance.
* Other: Fix double close on event channels.
* GODT-2233: Fix sub folder creation bug.
* GODT-2222: Dot not error on unknown Address Events.
* GODT-2218: Fix invalid UID ranges.
## [Bridge 3.0.9] Perth Narrows
### Changed

View File

@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: build build-gui build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.0.9+git
BRIDGE_APP_VERSION?=3.1.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG
@ -23,16 +23,21 @@ REVISION:=$(shell git rev-parse --short=10 HEAD)
BUILD_TIME:=$(shell date +%FT%T%z)
MACOS_MIN_VERSION_ARM64=11.0
MACOS_MIN_VERSION_AMD64=10.15
BUILD_ENV?=dev
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
BUILD_FLAGS_GUI:=-tags='${BUILD_TAGS} build_qt'
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/v3/internal/constants., Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
GO_LDFLAGS+=-X "github.com/ProtonMail/proton-bridge/v3/internal/constants.FullAppName=${APP_FULL_NAME}"
ifneq "${BUILD_LDFLAGS}" ""
GO_LDFLAGS+=${BUILD_LDFLAGS}
ifneq "${DSN_SENTRY}" ""
GO_LDFLAGS+=-X github.com/ProtonMail/proton-bridge/v3/internal/constants.DSNSentry=${DSN_SENTRY}
endif
ifneq "${BUILD_ENV}" ""
GO_LDFLAGS+=-X github.com/ProtonMail/proton-bridge/v3/internal/constants.BuildEnv=${BUILD_ENV}
endif
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
ifeq "${TARGET_OS}" "windows"
#GO_LDFLAGS+=-H=windowsgui # Disabled so we can inspect trace logs from the bridge for debugging.
@ -40,7 +45,6 @@ ifeq "${TARGET_OS}" "windows"
endif
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS_GUI+=-ldflags "${GO_LDFLAGS}"
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
DIRNAME:=$(shell basename ${CURDIR})
@ -96,9 +100,9 @@ endif
ifeq "${GOOS}" "windows"
go-build-finalize= \
powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} && \
$(call go-build,$(1),$(2),$(3)) && \
powershell Remove-Item ${4} -Force
$(if $(4),powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} &&,) \
$(call go-build,$(1),$(2),$(3)) \
$(if $(4), && powershell Remove-Item ${4} -Force,)
endif
${EXE_NAME}: gofiles ${RESOURCE_FILE}
@ -112,7 +116,7 @@ versioner:
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
vault-editor:
go build -tags debug -o vault-editor utils/vault-editor/main.go
$(call go-build-finalize,"-tags=debug","vault-editor","./utils/vault-editor/main.go")
hasher:
go build -o hasher utils/hasher/main.go
@ -154,8 +158,10 @@ ${EXE_TARGET}: check-build-essentials ${EXE_NAME}
BRIDGE_VENDOR="${APP_VENDOR}" \
BRIDGE_APP_VERSION=${APP_VERSION} \
BRIDGE_REVISION=${REVISION} \
BRIDGE_BUILD_TIME=${BUILD_TIME} \
BRIDGE_DSN_SENTRY=${DSN_SENTRY} \
BRIDGE_BUILD_TIME=${BUILD_TIME} \
BRIDGE_GUI_BUILD_CONFIG=Release \
BRIDGE_BUILD_ENV=${BUILD_ENV} \
BRIDGE_INSTALL_PATH=${ROOT_DIR}/${DEPLOY_DIR}/${GOOS} \
./build.sh install
mv "${ROOT_DIR}/${BRIDGE_EXE}" "$(ROOT_DIR)/${EXE_TARGET}"
@ -222,13 +228,13 @@ change-copyright-year:
./utils/missing_license.sh change-year
test: gofiles
go test -v -timeout=5m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/...
go test -v -timeout=10m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/...
test-race: gofiles
go test -v -timeout=30m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/...
test-integration: gofiles
go test -v -timeout=10m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests
go test -v -timeout=20m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests
test-integration-debug: gofiles
dlv test github.com/ProtonMail/proton-bridge/v3/tests -- -test.v -test.timeout=10m -test.parallel=1 -test.count=1
@ -247,7 +253,7 @@ coverage: test
mocks:
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/bridge TLSReporter,ProxyController,Autostarter > tmp
mv tmp internal/bridge/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/async PanicHandler > internal/bridge/mocks/async_mocks.go
mockgen --package mocks github.com/ProtonMail/gluon/async PanicHandler > internal/bridge/mocks/async_mocks.go
mockgen --package mocks github.com/ProtonMail/gluon/reporter Reporter > internal/bridge/mocks/gluon_mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/updater Downloader,Installer > internal/updater/mocks/mocks.go
@ -294,7 +300,7 @@ gofiles: ./internal/bridge/credits.go
cd ./utils/ && ./credits.sh bridge
## Run and debug
.PHONY: run run-qt run-qt-cli run-nogui run-cli run-noninteractive run-debug run-qml-preview clean-vendor clean-frontend-qt clean-frontend-qt-common clean
.PHONY: run run-qt run-qt-cli run-nogui run-cli run-noninteractive run-debug run-gui-tester clean-vendor clean-frontend-qt clean-frontend-qt-common clean
LOG?=debug
LOG_IMAP?=client # client/server/all, or empty to turn it off
@ -321,12 +327,26 @@ run-nogui: build-nogui clean-vendor gofiles
run-debug:
dlv debug ./cmd/Desktop-Bridge/main.go -- -l=debug
ifeq "${TARGET_OS}" "windows"
EXE_SUFFIX=.exe
endif
bridge-gui-tester: build-gui
cp ./cmd/Desktop-Bridge/deploy/${TARGET_OS}/bridge-gui${EXE_SUFFIX} .
cd ./internal/frontend/bridge-gui/bridge-gui-tester && cmake . && make
run-gui-tester: bridge-gui-tester
# copying tester as bridge so bridge-gui will start it and connect to it automatically
cp ./internal/frontend/bridge-gui/bridge-gui-tester/bridge-gui-tester${EXE_SUFFIX} bridge${EXE_SUFFIX}
./bridge-gui${EXE_SUFFIX}
clean-vendor:
rm -rf ./vendor
clean-gui:
cd internal/frontend/bridge-gui/ && \
rm -f Version.h && \
rm -f BuildConfig.h && \
rm -rf cmake-build-*/
clean-vcpkg:
@ -349,6 +369,6 @@ clean: clean-vendor clean-gui clean-vcpkg
.PHONY: generate
generate:
go generate ./...
$(MAKE) add-license
$(MAKE) build
.FORCE:

View File

@ -1,5 +1,5 @@
# Proton Mail Bridge and Import Export app
Copyright (c) 2022 Proton AG
Copyright (c) 2023 Proton AG
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
For a detailed build information see [BUILDS](./BUILDS.md).
@ -48,9 +48,6 @@ major problems.
## Environment Variables
### Bridge application
- `BRIDGESTRICTMODE`: tells bridge to turn on `bbolt`'s "strict mode" which checks the database after every `Commit`. Set to `1` to enable.
### Dev build or run
- `APP_VERSION`: set the bridge app version used during testing or building
- `PROTONMAIL_ENV`: when set to `dev` it is not using Sentry to report crashes
@ -62,35 +59,34 @@ major problems.
- `TAGS`: set build tags for tests
- `FEATURES`: set feature dir, file or scenario to test
## Folders
There are now three types of system folders which Bridge recognises:
| | Windows | Mac | Linux | Linux (XDG) |
|--------|-------------------------------------|-----------------------------------------------------|-------------------------------------|---------------------------------------|
| config | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.config/protonmail/bridge-v3 | $XDG_CONFIG_HOME/protonmail/bridge-v3 |
| cache | %LOCALAPPDATA%\protonmail\bridge-v3 | ~/Library/Caches/protonmail/bridge-v3 | ~/.cache/protonmail/bridge-v3 | $XDG_CACHE_HOME/protonmail/bridge-v3 |
| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 |
| temp | %LOCALAPPDATA%\Temp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp |
## Files
### Database
The database stores metadata necessary for presenting messages and mailboxes to an email client:
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/mailbox-<userID>.db` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/mailbox-<userID>.db`
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\mailbox-<userID>.db`
### Preferences
User preferences are stored in json at the following location:
- Linux: `~/.config/protonmail/bridge/prefs.json`
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/prefs.json`
- Windows: `%APPDATA%\protonmail\bridge\prefs.json`
| | Base Dir | Path |
|------------------------|----------|----------------------------|
| bridge lock file | cache | bridge.lock |
| bridge-gui lock file | cache | bridge-gui.lock |
| vault | config | vault.enc |
| gRPC server json | config | grpcServerConfig.json |
| gRPC client json | config | grpcClientConfig_<id>.json |
| gRPC Focus server json | config | grpcFocusServerConfig.json |
| Logs | data | logs |
| gluon DB | data | gluon/backend/db |
| gluon messages | data | gluon/backend/store |
| Update files | data | updates |
| sentry cache | data | sentry_cache |
| Mac/Linux File Socket | temp | bridge{4_DIGITS} |
### IMAP Cache
The currently subscribed mailboxes are held in a json file:
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/user_info.json` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/user_info.json`
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\user_info.json`
### Lock file
Bridge utilises an on-disk lock to ensure only one instance is run at once. The lock file is here:
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/bridge.lock` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/bridge.lock`
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\bridge.lock`
### TLS Certificate and Key
When bridge first starts, it generates a unique TLS certificate and key file at the following locations:
- Linux: `~/.config/protonmail/bridge/{cert,key}.pem` (unless `XDG_CONFIG_HOME` is set, in which case that is used as your `~/.config`)
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/{cert,key}.pem`
- Windows: `%APPDATA%\protonmail\bridge\{cert,key}.pem`

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -59,7 +59,7 @@ func main() { //nolint:funlen
logrus.SetLevel(logrus.DebugLevel)
l := logrus.WithField("launcher_version", constants.Version)
reporter := sentry.NewReporter(appName, constants.Version, useragent.New())
reporter := sentry.NewReporter(appName, useragent.New())
crashHandler := crash.NewHandler(reporter.ReportException)
defer crashHandler.HandlePanic()
@ -127,9 +127,11 @@ func main() { //nolint:funlen
l = l.WithField("exe_path", exe)
args, wait, mainExe := findAndStripWait(args)
args, wait, mainExes := findAndStripWait(args)
if wait {
waitForProcessToFinish(mainExe)
for _, mainExe := range mainExes {
waitForProcessToFinish(mainExe)
}
}
cmd := execabs.Command(exe, appendLauncherPath(launcher, args)...) //nolint:gosec
@ -186,12 +188,11 @@ func findAndStrip[T comparable](slice []T, v T) (strippedList []T, found bool) {
}
// findAndStripWait Check for waiter flag get its value and clean them both.
func findAndStripWait(args []string) ([]string, bool, string) {
func findAndStripWait(args []string) ([]string, bool, []string) {
res := append([]string{}, args...)
hasFlag := false
var value string
values := make([]string, 0)
for k, v := range res {
if v != FlagWait {
continue
@ -200,14 +201,16 @@ func findAndStripWait(args []string) ([]string, bool, string) {
continue
}
hasFlag = true
value = res[k+1]
values = append(values, res[k+1])
}
if hasFlag {
res, _ = findAndStrip(res, FlagWait)
res, _ = findAndStrip(res, value)
for _, v := range values {
res, _ = findAndStrip(res, v)
}
}
return res, hasFlag, value
return res, hasFlag, values
}
func getPathToUpdatedExecutable(

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -56,3 +56,25 @@ func TestFindAndStrip(t *testing.T) {
assert.False(t, found)
assert.True(t, xslices.Equal(result, []string{}))
}
func TestFindAndStripWait(t *testing.T) {
result, found, values := findAndStripWait([]string{"a", "b", "c"})
assert.False(t, found)
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
assert.True(t, xslices.Equal(values, []string{}))
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b"}))
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
}

87
go.mod
View File

@ -4,21 +4,20 @@ go 1.18
require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.1.1
github.com/ProtonMail/gluon v0.14.2-0.20221219120039-76e4e7e2a353
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.15.1-0.20230331095629-e23a7a1be2a8
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.2.3
github.com/ProtonMail/go-rfc5322 v0.11.0
github.com/ProtonMail/gopenpgp/v2 v2.4.10
github.com/PuerkitoBio/goquery v1.8.0
github.com/ProtonMail/go-proton-api v0.4.1-0.20230331115846-7ba084061eaa
github.com/ProtonMail/gopenpgp/v2 v2.5.2
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
github.com/bradenaw/juniper v0.8.0
github.com/bradenaw/juniper v0.10.2
github.com/cucumber/godog v0.12.5
github.com/cucumber/messages-go/v16 v16.0.1
github.com/docker/docker-credential-helpers v0.6.3
github.com/elastic/go-sysinfo v1.8.1
github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317
github.com/emersion/go-imap v1.2.1
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
github.com/emersion/go-message v0.16.0
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
@ -26,7 +25,7 @@ require (
github.com/fatih/color v1.13.0
github.com/getsentry/sentry-go v0.15.0
github.com/go-resty/resty/v2 v2.7.0
github.com/goccy/go-json v0.9.11
github.com/goccy/go-json v0.10.0
github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
@ -35,36 +34,38 @@ require (
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
github.com/keybase/go-keychain v0.0.0
github.com/miekg/dns v1.1.50
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.6.0
github.com/pkg/profile v1.7.0
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0
github.com/urfave/cli/v2 v2.20.3
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.24.4
github.com/vmihailenco/msgpack/v5 v5.3.5
go.uber.org/goleak v1.2.0
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e
golang.org/x/net v0.1.0
golang.org/x/sys v0.1.0
golang.org/x/text v0.4.0
google.golang.org/grpc v1.50.1
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/net v0.7.0
golang.org/x/sys v0.5.0
golang.org/x/text v0.7.0
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
howett.net/plist v1.0.0
)
require (
ariga.io/atlas v0.7.0 // indirect
entgo.io/ent v0.11.2 // indirect
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb // indirect
entgo.io/ent v0.11.8 // indirect
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 // indirect
github.com/ProtonMail/go-srp v0.0.5 // indirect
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/bytedance/sonic v1.8.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/chzyer/test v1.0.0 // indirect
github.com/cloudflare/circl v1.2.0 // indirect
github.com/cloudflare/circl v1.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cronokirby/saferith v0.33.0 // indirect
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
@ -73,54 +74,58 @@ require (
github.com/elastic/go-windows v1.0.1 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.8.1 // indirect
github.com/gin-gonic/gin v1.9.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/gofrs/uuid v4.3.0+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-memdb v1.3.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl/v2 v2.14.0 // indirect
github.com/hashicorp/hcl/v2 v2.16.1 // indirect
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.10 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zclconf/go-cty v1.11.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/zclconf/go-cty v1.12.1 // indirect
golang.org/x/arch v0.2.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 // indirect
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace (
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-20201228133358-4db68cea0cac
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
)

224
go.sum
View File

@ -1,5 +1,5 @@
ariga.io/atlas v0.7.0 h1:daEFdUsyNm7EHyzcMfjWwq/fVv48fCfad+dIGyobY1k=
ariga.io/atlas v0.7.0/go.mod h1:ft47uSh5hWGDCmQC9DsztZg6Xk+KagM5Ts/mZYKb9JE=
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb h1:mbsFtavDqGdYwdDpP50LGOOZ2hgyGoJcZeOpbgKMyu4=
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@ -13,46 +13,41 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
entgo.io/ent v0.11.2 h1:UM2/BUhF2FfsxPHRxLjQbhqJNaDdVlOwNIAMLs2jyto=
entgo.io/ent v0.11.2/go.mod h1:YGHEQnmmIUgtD5b1ICD5vg74dS3npkNnmC5K+0J+IHU=
entgo.io/ent v0.11.8 h1:M/M0QL1CYCUSdqGRXUrXhFYSDRJPsOOrr+RLEej/gyQ=
entgo.io/ent v0.11.8/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18=
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
github.com/ProtonMail/gluon v0.14.2-0.20221219120039-76e4e7e2a353 h1:1ts1i+fMKjp1cILg9AsxuX1JHrjm6RRyp+X3/8T0R4A=
github.com/ProtonMail/gluon v0.14.2-0.20221219120039-76e4e7e2a353/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
github.com/ProtonMail/gluon v0.15.1-0.20230331095629-e23a7a1be2a8 h1:USMR8imbxkP4Ailch4ceV3hCZTaANMIGHhb5rpZFYn4=
github.com/ProtonMail/gluon v0.15.1-0.20230331095629-e23a7a1be2a8/go.mod h1:yA4hk6CJw0BMo+YL8Y3ckCYs5L20sysu9xseshwY3QI=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220822140716-1678d6eb0cbe/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
github.com/ProtonMail/go-crypto v0.0.0-20230124153114-0acdc8ae009b/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
github.com/ProtonMail/go-proton-api v0.2.3 h1:MItHzfA67PRU4CQ9jtuIWD88YQBsyBlAquLBJ/SO2/M=
github.com/ProtonMail/go-proton-api v0.2.3/go.mod h1:JUo5IQG0hNuPRuDpOUsCOvtee6UjTEHHF1QN2i8RSos=
github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY=
github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw=
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 h1:dS7r5z4iGS0qCjM7UwWdsEMzQesUQbGcXdSm2/tWboA=
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230331115846-7ba084061eaa h1:0JKWkz/gIYf+eky0dCFeBWrjEDLf59lS8HOlXtvn6Nk=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230331115846-7ba084061eaa/go.mod h1:RfpLBcTIhfjOIcBhh7f36LtAOEi0mqPd3t8gyLWmCZM=
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs=
github.com/ProtonMail/gopenpgp/v2 v2.4.10 h1:EYgkxzwmQvsa6kxxkgP1AwzkFqKHscF2UINxaSn6rdI=
github.com/ProtonMail/gopenpgp/v2 v2.4.10/go.mod h1:CTRA7/toc/4DxDy5Du4hPDnIZnJvXSeQ8LsRTOUJoyc=
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/ProtonMail/gopenpgp/v2 v2.5.2 h1:97SjlWNAxXl9P22lgwgrZRshQdiEfAht0g3ZoiA1GCw=
github.com/ProtonMail/gopenpgp/v2 v2.5.2/go.mod h1:52qDaCnto6r+CoWbuU50T77XQt99lIs46HtHtvgFO3o=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
@ -65,9 +60,6 @@ github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:2
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37/go.mod h1:6AXRstqK+32jeFmw89QGL2748+dj34Av4xc/I9oo9BY=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220816024939-bc8df83d7b9d/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -77,19 +69,27 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bradenaw/juniper v0.8.0 h1:sdanLNdJbLjcLj993VYIwUHlUVkLzvgiD/x9O7cvvxk=
github.com/bradenaw/juniper v0.8.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
github.com/bradenaw/juniper v0.10.2 h1:EY7r8SJJrigJ7lvWk6ews3K5RD4XTG9z+WSwHJKijP4=
github.com/bradenaw/juniper v0.10.2/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4=
github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk=
github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -98,7 +98,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
@ -122,9 +121,10 @@ github.com/elastic/go-sysinfo v1.8.1 h1:4Yhj+HdV6WjbCRgGdZpPJ8lZQlXZLKDAeIkmQ/VR
github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:43mBoVwooyLm1+1YVf5nvn1pSFWhw7rOpcrp1Jg/qk0=
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:sPwp0FFboaK/bxsrUz1lNrDMUCsZUsKC5YuM4uRVRVs=
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/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
@ -137,6 +137,8 @@ github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a/go.mod h1:HMJKR5
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -145,8 +147,8 @@ github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2Ht
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -154,20 +156,19 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -199,6 +200,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -240,12 +243,13 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc=
github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
github.com/hashicorp/hcl/v2 v2.16.1 h1:BwuxEMD/tsYgbhIW7UuI3crjovf3MzuFWiVgiv57iHg=
github.com/hashicorp/hcl/v2 v2.16.1/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
@ -262,16 +266,17 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
@ -283,13 +288,14 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
@ -315,17 +321,20 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@ -345,9 +354,7 @@ github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -356,7 +363,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -377,24 +383,26 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cma
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.20.3 h1:lOgGidH/N5loaigd9HjFsOIhXSTrzl7tBpHswZ428w4=
github.com/urfave/cli/v2 v2.20.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU=
github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
@ -403,16 +411,20 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY=
github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -421,18 +433,16 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e h1:SkwG94eNiiYJhbeDE018Grw09HIN/KB9NlRmZsrzfWs=
golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -441,18 +451,16 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -472,9 +480,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -484,8 +492,10 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A=
golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -495,7 +505,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -509,15 +518,18 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -525,8 +537,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -547,11 +559,11 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa h1:uKcci2q7Qtp6nMTC/AAvfNUAldFtJuHWV9/5QWiypts=
golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 h1:/X0t/E4VxbZE7MLS7auvE7YICHeVvbIa9vkOVvYW/24=
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -574,13 +586,13 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ=
google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ=
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 h1:muK+gVBJBfFb4SejshDBlN2/UgxCCOKH9Y34ljqEGOc=
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
@ -599,11 +611,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -613,3 +622,4 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -19,11 +19,13 @@ package app
import (
"fmt"
"math/rand"
"net/http"
"net/http/cookiejar"
"os"
"path/filepath"
"runtime"
"time"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
@ -69,16 +71,17 @@ const (
// Hidden flags.
const (
flagLauncher = "launcher"
flagNoWindow = "no-window"
flagParentPID = "parent-pid"
flagLauncher = "launcher"
flagNoWindow = "no-window"
flagParentPID = "parent-pid"
flagSoftwareRenderer = "software-renderer"
)
const (
appUsage = "Proton Mail IMAP and SMTP Bridge"
)
func New() *cli.App { //nolint:funlen
func New() *cli.App {
app := cli.NewApp()
app.Name = constants.FullAppName
@ -140,6 +143,12 @@ func New() *cli.App { //nolint:funlen
Hidden: true,
Value: -1,
},
&cli.BoolFlag{
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
Usage: "GUI is using software renderer",
Hidden: true,
Value: false,
},
}
app.Action = run
@ -147,7 +156,10 @@ func New() *cli.App { //nolint:funlen
return app
}
func run(c *cli.Context) error { //nolint:funlen
func run(c *cli.Context) error {
// Seed the default RNG from the math/rand package.
rand.Seed(time.Now().UnixNano())
// Get the current bridge version.
version, err := semver.NewVersion(constants.Version)
if err != nil {
@ -158,7 +170,7 @@ func run(c *cli.Context) error { //nolint:funlen
identifier := useragent.New()
// Create a new Sentry client that will be used to report crashes etc.
reporter := sentry.NewReporter(constants.FullAppName, constants.Version, identifier)
reporter := sentry.NewReporter(constants.FullAppName, identifier)
// Determine the exe that should be used to restart/autostart the app.
// By default, this is the launcher, if used. Otherwise, we try to get
@ -173,14 +185,14 @@ func run(c *cli.Context) error { //nolint:funlen
exe = os.Args[0]
}
migrationErr := migrateOldVersions()
// Restart the app if requested.
return withRestarter(exe, func(restarter *restarter.Restarter) error {
// Handle crashes with various actions.
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) error {
migrationErr := migrateOldVersions()
// Run with profiling if requested.
return withProfiler(c, func() error {
// Restart the app if requested.
return withRestarter(exe, func(restarter *restarter.Restarter) error {
// Handle crashes with various actions.
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) error {
// Run with profiling if requested.
return withProfiler(c, func() error {
// Load the locations where we store our files.
return WithLocations(func(locations *locations.Locations) error {
// Migrate the keychain helper.
@ -196,30 +208,45 @@ func run(c *cli.Context) error { //nolint:funlen
}
// Ensure we are the only instance running.
return withSingleInstance(locations, version, func() error {
settings, err := locations.ProvideSettingsPath()
if err != nil {
logrus.WithError(err).Error("Failed to get settings path")
}
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
// Unlock the encrypted vault.
return WithVault(locations, func(vault *vault.Vault, insecure, corrupt bool) error {
if !vault.Migrated() {
return WithVault(locations, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
// Report insecure vault.
if insecure {
_ = reporter.ReportMessageWithContext("Vault is insecure", map[string]interface{}{})
}
// Report corrupt vault.
if corrupt {
_ = reporter.ReportMessageWithContext("Vault is corrupt", map[string]interface{}{})
}
if !v.Migrated() {
// Migrate old settings into the vault.
if err := migrateOldSettings(vault); err != nil {
if err := migrateOldSettings(v); err != nil {
logrus.WithError(err).Error("Failed to migrate old settings")
}
// Migrate old accounts into the vault.
if err := migrateOldAccounts(locations, vault); err != nil {
if err := migrateOldAccounts(locations, v); err != nil {
logrus.WithError(err).Error("Failed to migrate old accounts")
}
// The vault has been migrated.
if err := vault.SetMigrated(); err != nil {
if err := v.SetMigrated(); err != nil {
logrus.WithError(err).Error("Failed to mark vault as migrated")
}
}
// Load the cookies from the vault.
return withCookieJar(vault, func(cookieJar http.CookieJar) error {
return withCookieJar(v, func(cookieJar http.CookieJar) error {
// Create a new bridge instance.
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, vault, cookieJar, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
if insecure {
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
b.PushError(bridge.ErrVaultInsecure)
@ -244,15 +271,15 @@ func run(c *cli.Context) error { //nolint:funlen
}
// If there's another instance already running, try to raise it and exit.
func withSingleInstance(locations *locations.Locations, version *semver.Version, fn func() error) error {
func withSingleInstance(settingPath, lockFile string, version *semver.Version, fn func() error) error {
logrus.Debug("Checking for other instances")
defer logrus.Debug("Single instance stopped")
lock, err := checkSingleInstance(locations.GetLockFile(), version)
lock, err := checkSingleInstance(settingPath, lockFile, version)
if err != nil {
logrus.Info("Another instance is already running; raising it")
if ok := focus.TryRaise(); !ok {
if ok := focus.TryRaise(settingPath); !ok {
return fmt.Errorf("another instance is already running but it could not be raised")
}
@ -315,14 +342,7 @@ func WithLocations(fn func(*locations.Locations) error) error {
}
// Create a new locations object that will be used to provide paths to store files.
locations := locations.New(provider, constants.ConfigName)
defer func() {
if err := locations.Clean(); err != nil {
logrus.WithError(err).Error("Failed to clean locations")
}
}()
return fn(locations)
return fn(locations.New(provider, constants.ConfigName))
}
// Start profiling if requested.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -23,6 +23,7 @@ import (
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
@ -40,13 +41,11 @@ import (
"github.com/urfave/cli/v2"
)
const vaultSecretName = "bridge-vault-key"
// deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data.
var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals
// withBridge creates creates and tears down the bridge.
func withBridge( //nolint:funlen
func withBridge(
c *cli.Context,
exe string,
locations *locations.Locations,
@ -79,7 +78,7 @@ func withBridge( //nolint:funlen
)
// Create a proxy dialer which switches to a proxy if the request fails.
proxyDialer := dialer.NewProxyTLSDialer(pinningDialer, constants.APIHost)
proxyDialer := dialer.NewProxyTLSDialer(pinningDialer, constants.APIHost, crashHandler)
// Create the autostarter.
autostarter := newAutostarter(exe)
@ -110,6 +109,7 @@ func withBridge( //nolint:funlen
// Crash and report stuff
crashHandler,
reporter,
imap.DefaultEpochUIDValidityGenerator(),
// The logging stuff.
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -46,7 +46,7 @@ func runFrontend(
switch {
case c.Bool(flagCLI):
return bridgeCLI.New(bridge, restarter, eventCh).Loop()
return bridgeCLI.New(bridge, restarter, eventCh, crashHandler).Loop()
case c.Bool(flagNonInteractive):
select {}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -87,6 +87,11 @@ func migrateOldSettings(v *vault.Vault) error {
return fmt.Errorf("failed to get user config dir: %w", err)
}
return migrateOldSettingsWithDir(configDir, v)
}
// nolint:gosec
func migrateOldSettingsWithDir(configDir string, v *vault.Vault) error {
b, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "prefs.json"))
if errors.Is(err, fs.ErrNotExist) {
return nil
@ -94,7 +99,27 @@ func migrateOldSettings(v *vault.Vault) error {
return fmt.Errorf("failed to read old prefs file: %w", err)
}
return migratePrefsToVault(v, b)
if err := migratePrefsToVault(v, b); err != nil {
return fmt.Errorf("failed to migrate prefs to vault: %w", err)
}
logrus.Info("Migrating TLS certificate")
certPEM, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "cert.pem"))
if errors.Is(err, fs.ErrNotExist) {
return nil
} else if err != nil {
return fmt.Errorf("failed to read old cert file: %w", err)
}
keyPEM, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "key.pem"))
if errors.Is(err, fs.ErrNotExist) {
return nil
} else if err != nil {
return fmt.Errorf("failed to read old key file: %w", err)
}
return v.SetBridgeTLSCertKey(certPEM, keyPEM)
}
func migrateOldAccounts(locations *locations.Locations, v *vault.Vault) error {
@ -147,7 +172,12 @@ func migrateOldAccount(userID string, store *credentials.Store, v *vault.Vault)
return fmt.Errorf("failed to split api token for user %q: %w", userID, err)
}
user, err := v.AddUser(creds.UserID, creds.Name, authUID, authRef, creds.MailboxPassword)
var primaryEmail string
if len(creds.EmailList()) > 0 {
primaryEmail = creds.EmailList()[0]
}
user, err := v.AddUser(creds.UserID, creds.Name, primaryEmail, authUID, authRef, creds.MailboxPassword)
if err != nil {
return fmt.Errorf("failed to add user %q: %w", userID, err)
}
@ -182,7 +212,6 @@ func migrateOldAccount(userID string, store *credentials.Store, v *vault.Vault)
return nil
}
// nolint:funlen
func migratePrefsToVault(vault *vault.Vault, b []byte) error {
var prefs struct {
IMAPPort int `json:"user_port_imap,,string"`
@ -193,11 +222,10 @@ func migratePrefsToVault(vault *vault.Vault, b []byte) error {
UpdateChannel updater.Channel `json:"update_channel"`
UpdateRollout float64 `json:"rollout,,string"`
FirstStart bool `json:"first_time_start,,string"`
FirstStartGUI bool `json:"first_time_start_gui,,string"`
ColorScheme string `json:"color_scheme"`
LastVersion *semver.Version `json:"last_used_version"`
Autostart bool `json:"autostart,,string"`
FirstStart bool `json:"first_time_start,,string"`
ColorScheme string `json:"color_scheme"`
LastVersion *semver.Version `json:"last_used_version"`
Autostart bool `json:"autostart,,string"`
AllowProxy bool `json:"allow_proxy,,string"`
FetchWorkers int `json:"fetch_workers,,string"`
@ -241,10 +269,6 @@ func migratePrefsToVault(vault *vault.Vault, b []byte) error {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start: %w", err))
}
if err := vault.SetFirstStartGUI(prefs.FirstStartGUI); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start GUI: %w", err))
}
if err := vault.SetColorScheme(prefs.ColorScheme); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate color scheme: %w", err))
}
@ -265,14 +289,6 @@ func migratePrefsToVault(vault *vault.Vault, b []byte) error {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate show all mail: %w", err))
}
if err := vault.SetSyncWorkers(prefs.FetchWorkers); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync workers: %w", err))
}
if err := vault.SetSyncAttPool(prefs.AttachmentWorkers); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync attachment pool: %w", err))
}
if err := vault.SetCookies([]byte(prefs.Cookies)); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to migrate cookies: %w", err))
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -25,6 +25,7 @@ import (
"runtime"
"testing"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
@ -38,54 +39,49 @@ import (
"github.com/stretchr/testify/require"
)
func TestMigratePrefsToVault(t *testing.T) {
func TestMigratePrefsToVaultWithKeys(t *testing.T) {
// Create a new vault.
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"))
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
require.NoError(t, err)
require.False(t, corrupt)
// load the old prefs file.
b, err := os.ReadFile(filepath.Join("testdata", "prefs.json"))
require.NoError(t, err)
configDir := filepath.Join("testdata", "with_keys")
// Migrate the old prefs file to the new vault.
require.NoError(t, migratePrefsToVault(vault, b))
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
// Check that the IMAP and SMTP prefs are migrated.
require.Equal(t, 2143, vault.GetIMAPPort())
require.Equal(t, 2025, vault.GetSMTPPort())
require.True(t, vault.GetSMTPSSL())
// Check Json Settings
validateJSONPrefs(t, vault)
// Check that the update channel is migrated.
require.True(t, vault.GetAutoUpdate())
require.Equal(t, updater.EarlyChannel, vault.GetUpdateChannel())
require.Equal(t, 0.4849529004202015, vault.GetUpdateRollout())
cert, key := vault.GetBridgeTLSCert()
// Check the keys were found and collected.
require.Equal(t, "-----BEGIN CERTIFICATE-----", string(cert))
require.Equal(t, "-----BEGIN RSA PRIVATE KEY-----", string(key))
}
// Check that the app settings have been migrated.
require.False(t, vault.GetFirstStart())
require.True(t, vault.GetFirstStartGUI())
require.Equal(t, "blablabla", vault.GetColorScheme())
require.Equal(t, "2.3.0+git", vault.GetLastVersion().String())
require.True(t, vault.GetAutostart())
// Check that the other app settings have been migrated.
require.Equal(t, 16, vault.SyncWorkers())
require.Equal(t, 16, vault.SyncAttPool())
require.False(t, vault.GetProxyAllowed())
require.False(t, vault.GetShowAllMail())
// Check that the cookies have been migrated.
jar, err := cookiejar.New(nil)
func TestMigratePrefsToVaultWithoutKeys(t *testing.T) {
// Create a new vault.
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
require.NoError(t, err)
require.False(t, corrupt)
cookies, err := cookies.NewCookieJar(jar, vault)
require.NoError(t, err)
// load the old prefs file.
configDir := filepath.Join("testdata", "without_keys")
url, err := url.Parse("https://api.protonmail.ch")
require.NoError(t, err)
// Migrate the old prefs file to the new vault.
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
// There should be a cookie for the API.
require.NotEmpty(t, cookies.Cookies(url))
// Migrate the old prefs file to the new vault.
require.NoError(t, migrateOldSettingsWithDir(configDir, vault))
// Check Json Settings
validateJSONPrefs(t, vault)
// Check the keys were found and collected.
cert, key := vault.GetBridgeTLSCert()
require.NotEqual(t, []byte("-----BEGIN CERTIFICATE-----"), cert)
require.NotEqual(t, []byte("-----BEGIN RSA PRIVATE KEY-----"), key)
}
func TestKeychainMigration(t *testing.T) {
@ -102,7 +98,7 @@ func TestKeychainMigration(t *testing.T) {
oldCacheDir := filepath.Join(tmpDir, "protonmail", "bridge")
require.NoError(t, os.MkdirAll(oldCacheDir, 0o700))
oldPrefs, err := os.ReadFile(filepath.Join("testdata", "prefs.json"))
oldPrefs, err := os.ReadFile(filepath.Join("testdata", "without_keys", "protonmail", "bridge", "prefs.json"))
require.NoError(t, err)
require.NoError(t, os.WriteFile(
@ -178,7 +174,7 @@ func TestUserMigration(t *testing.T) {
token, err := crypto.RandomToken(32)
require.NoError(t, err)
v, corrupt, err := vault.New(settingsFolder, settingsFolder, token)
v, corrupt, err := vault.New(settingsFolder, settingsFolder, token, async.NoopPanicHandler{})
require.NoError(t, err)
require.False(t, corrupt)
@ -197,3 +193,38 @@ func TestUserMigration(t *testing.T) {
require.Equal(t, vault.CombinedMode, u.AddressMode())
}))
}
func validateJSONPrefs(t *testing.T, vault *vault.Vault) {
// Check that the IMAP and SMTP prefs are migrated.
require.Equal(t, 2143, vault.GetIMAPPort())
require.Equal(t, 2025, vault.GetSMTPPort())
require.True(t, vault.GetSMTPSSL())
// Check that the update channel is migrated.
require.True(t, vault.GetAutoUpdate())
require.Equal(t, updater.EarlyChannel, vault.GetUpdateChannel())
require.Equal(t, 0.4849529004202015, vault.GetUpdateRollout())
// Check that the app settings have been migrated.
require.False(t, vault.GetFirstStart())
require.Equal(t, "blablabla", vault.GetColorScheme())
require.Equal(t, "2.3.0+git", vault.GetLastVersion().String())
require.True(t, vault.GetAutostart())
// Check that the other app settings have been migrated.
require.False(t, vault.GetProxyAllowed())
require.False(t, vault.GetShowAllMail())
// Check that the cookies have been migrated.
jar, err := cookiejar.New(nil)
require.NoError(t, err)
cookies, err := cookies.NewCookieJar(jar, vault)
require.NoError(t, err)
url, err := url.Parse("https://api.protonmail.ch")
require.NoError(t, err)
// There should be a cookie for the API.
require.NotEmpty(t, cookies.Cookies(url))
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -34,7 +34,7 @@ import (
//
// For macOS and Linux when already running version is older than this instance
// it will kill old and continue with this new bridge (i.e. no error returned).
func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.File, error) {
func checkSingleInstance(settingPath, lockFilePath string, curVersion *semver.Version) (*os.File, error) {
if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil {
logrus.WithField("path", lockFilePath).Debug("Created lock file; no other instance is running")
return lock, nil
@ -44,7 +44,7 @@ func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.F
// We couldn't create the lock file, so another instance is probably running.
// Check if it's an older version of the app.
lastVersion, ok := focus.TryVersion()
lastVersion, ok := focus.TryVersion(settingPath)
if !ok {
return nil, fmt.Errorf("failed to determine version of running instance")
}

View File

@ -0,0 +1 @@
-----BEGIN CERTIFICATE-----

View File

@ -0,0 +1 @@
-----BEGIN RSA PRIVATE KEY-----

View File

@ -0,0 +1,31 @@
{
"allow_proxy": "false",
"attachment_workers": "16",
"autostart": "true",
"autoupdate": "true",
"cache_compression": "true",
"cache_concurrent_read": "16",
"cache_concurrent_write": "16",
"cache_enabled": "true",
"cache_location": "/home/user/.config/protonmail/bridge/cache/c11/messages",
"cache_min_free_abs": "250000000",
"cache_min_free_rat": "",
"color_scheme": "blablabla",
"cookies": "{\"https://api.protonmail.ch\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.ch\",\"Expires\":\"2023-02-19T00:20:40.269424437+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=blablablablablablablablabla; Domain=protonmail.ch; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"default\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:40.269428627+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=default; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}],\"https://protonmail.com\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.com\",\"Expires\":\"2023-02-19T00:20:18.315084712+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=Y3q2Mh-ClvqL6LWeYdfyPgAAABI; Domain=protonmail.com; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"redirect\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:18.315087646+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=redirect; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}]}",
"fetch_workers": "16",
"first_time_start": "false",
"first_time_start_gui": "true",
"imap_workers": "16",
"is_all_mail_visible": "false",
"last_heartbeat": "325",
"last_used_version": "2.3.0+git",
"preferred_keychain": "secret-service",
"rebranding_migrated": "true",
"report_outgoing_email_without_encryption": "false",
"rollout": "0.4849529004202015",
"user_port_api": "1042",
"update_channel": "early",
"user_port_imap": "2143",
"user_port_smtp": "2025",
"user_ssl_smtp": "true"
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -18,26 +18,24 @@
package app
import (
"encoding/base64"
"fmt"
"path"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
)
func WithVault(locations *locations.Locations, fn func(*vault.Vault, bool, bool) error) error {
func WithVault(locations *locations.Locations, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
logrus.Debug("Creating vault")
defer logrus.Debug("Vault stopped")
// Create the encVault.
encVault, insecure, corrupt, err := newVault(locations)
encVault, insecure, corrupt, err := newVault(locations, panicHandler)
if err != nil {
return fmt.Errorf("could not create vault: %w", err)
}
@ -51,7 +49,9 @@ func WithVault(locations *locations.Locations, fn func(*vault.Vault, bool, bool)
if installed := encVault.GetCertsInstalled(); !installed {
logrus.Debug("Installing certificates")
if err := certs.NewInstaller().InstallCert(encVault.GetBridgeTLSCert()); err != nil {
certPEM, _ := encVault.GetBridgeTLSCert()
if err := certs.NewInstaller().InstallCert(certPEM); err != nil {
return fmt.Errorf("failed to install certs: %w", err)
}
@ -67,7 +67,7 @@ func WithVault(locations *locations.Locations, fn func(*vault.Vault, bool, bool)
return fn(encVault, insecure, corrupt)
}
func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error) {
func newVault(locations *locations.Locations, panicHandler async.PanicHandler) (*vault.Vault, bool, bool, error) {
vaultDir, err := locations.ProvideSettingsPath()
if err != nil {
return nil, false, false, fmt.Errorf("could not get vault dir: %w", err)
@ -80,7 +80,8 @@ func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error)
insecure bool
)
if key, err := getVaultKey(vaultDir); err != nil {
if key, err := loadVaultKey(vaultDir); err != nil {
logrus.WithError(err).Error("Could not load/create vault key")
insecure = true
// We store the insecure vault in a separate directory
@ -89,12 +90,12 @@ func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error)
vaultKey = key
}
gluonDir, err := locations.ProvideGluonPath()
gluonCacheDir, err := locations.ProvideGluonCachePath()
if err != nil {
return nil, false, false, fmt.Errorf("could not provide gluon path: %w", err)
}
vault, corrupt, err := vault.New(vaultDir, gluonDir, vaultKey)
vault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
if err != nil {
return nil, false, false, fmt.Errorf("could not create vault: %w", err)
}
@ -102,42 +103,25 @@ func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error)
return vault, insecure, corrupt, nil
}
func getVaultKey(vaultDir string) ([]byte, error) {
func loadVaultKey(vaultDir string) ([]byte, error) {
helper, err := vault.GetHelper(vaultDir)
if err != nil {
return nil, fmt.Errorf("could not get keychain helper: %w", err)
}
keychain, err := keychain.NewKeychain(helper, constants.KeyChainName)
kc, err := keychain.NewKeychain(helper, constants.KeyChainName)
if err != nil {
return nil, fmt.Errorf("could not create keychain: %w", err)
}
secrets, err := keychain.List()
has, err := vault.HasVaultKey(kc)
if err != nil {
return nil, fmt.Errorf("could not list keychain: %w", err)
return nil, fmt.Errorf("could not check for vault key: %w", err)
}
if !slices.Contains(secrets, vaultSecretName) {
tok, err := crypto.RandomToken(32)
if err != nil {
return nil, fmt.Errorf("could not generate random token: %w", err)
}
if err := keychain.Put(vaultSecretName, base64.StdEncoding.EncodeToString(tok)); err != nil {
return nil, fmt.Errorf("could not put keychain item: %w", err)
}
if has {
return vault.GetVaultKey(kc)
}
_, keyEnc, err := keychain.Get(vaultSecretName)
if err != nil {
return nil, fmt.Errorf("could not get keychain item: %w", err)
}
keyDec, err := base64.StdEncoding.DecodeString(keyEnc)
if err != nil {
return nil, fmt.Errorf("could not decode keychain item: %w", err)
}
return keyDec, nil
return vault.NewVaultKey(kc)
}

View File

@ -1,82 +0,0 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package async
import (
"context"
"sync"
)
// Abortable collects groups of functions that can be aborted by calling Abort.
type Abortable struct {
abortFunc []context.CancelFunc
abortLock sync.RWMutex
}
func (a *Abortable) Do(ctx context.Context, fn func(context.Context)) {
fn(a.newCancelCtx(ctx))
}
func (a *Abortable) Abort() {
a.abortLock.RLock()
defer a.abortLock.RUnlock()
for _, fn := range a.abortFunc {
fn()
}
}
func (a *Abortable) newCancelCtx(ctx context.Context) context.Context {
a.abortLock.Lock()
defer a.abortLock.Unlock()
ctx, cancel := context.WithCancel(ctx)
a.abortFunc = append(a.abortFunc, cancel)
return ctx
}
// RangeContext iterates over the given channel until the context is canceled or the
// channel is closed.
func RangeContext[T any](ctx context.Context, ch <-chan T, fn func(T)) {
for {
select {
case v, ok := <-ch:
if !ok {
return
}
fn(v)
case <-ctx.Done():
return
}
}
}
// ForwardContext forwards all values from the src channel to the dst channel until the
// context is canceled or the src channel is closed.
func ForwardContext[T any](ctx context.Context, dst chan<- T, src <-chan T) {
RangeContext(ctx, src, func(v T) {
select {
case dst <- v:
case <-ctx.Done():
}
})
}

View File

@ -1,233 +0,0 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package async
import (
"context"
"math/rand"
"sync"
"time"
)
type PanicHandler interface {
HandlePanic()
}
// Group is forked and improved version of "github.com/bradenaw/juniper/xsync.Group".
//
// It manages a group of goroutines. The main change to original is posibility
// to wait passed function to finish without canceling it's context and adding
// PanicHandler.
type Group struct {
baseCtx context.Context
ctx context.Context
jobCtx context.Context
cancel context.CancelFunc
finish context.CancelFunc
wg sync.WaitGroup
panicHandler PanicHandler
}
// NewGroup returns a Group ready for use. The context passed to any of the f functions will be a
// descendant of ctx.
func NewGroup(ctx context.Context, panicHandler PanicHandler) *Group {
bgCtx, cancel := context.WithCancel(ctx)
jobCtx, finish := context.WithCancel(ctx)
return &Group{
baseCtx: ctx,
ctx: bgCtx,
jobCtx: jobCtx,
cancel: cancel,
finish: finish,
panicHandler: panicHandler,
}
}
// Once calls f once from another goroutine.
func (g *Group) Once(f func(ctx context.Context)) {
g.wg.Add(1)
go func() {
defer g.handlePanic()
f(g.ctx)
g.wg.Done()
}()
}
// jitterDuration returns a random duration in [d - jitter, d + jitter].
func jitterDuration(d time.Duration, jitter time.Duration) time.Duration {
return d + time.Duration(float64(jitter)*((rand.Float64()*2)-1)) //nolint:gosec
}
// Periodic spawns a goroutine that calls f once per interval +/- jitter.
func (g *Group) Periodic(
interval time.Duration,
jitter time.Duration,
f func(ctx context.Context),
) {
g.wg.Add(1)
go func() {
defer g.handlePanic()
defer g.wg.Done()
t := time.NewTimer(jitterDuration(interval, jitter))
defer t.Stop()
for {
if g.ctx.Err() != nil {
return
}
select {
case <-g.jobCtx.Done():
return
case <-t.C:
}
t.Reset(jitterDuration(interval, jitter))
f(g.ctx)
}
}()
}
// Trigger spawns a goroutine which calls f whenever the returned function is called. If f is
// already running when triggered, f will run again immediately when it finishes.
func (g *Group) Trigger(f func(ctx context.Context)) func() {
c := make(chan struct{}, 1)
g.wg.Add(1)
go func() {
defer g.handlePanic()
defer g.wg.Done()
for {
if g.ctx.Err() != nil {
return
}
select {
case <-g.jobCtx.Done():
return
case <-c:
}
f(g.ctx)
}
}()
return func() {
select {
case c <- struct{}{}:
default:
}
}
}
// PeriodicOrTrigger spawns a goroutine which calls f whenever the returned function is called. If
// f is already running when triggered, f will run again immediately when it finishes. Also calls f
// when it has been interval+/-jitter since the last trigger.
func (g *Group) PeriodicOrTrigger(
interval time.Duration,
jitter time.Duration,
f func(ctx context.Context),
) func() {
c := make(chan struct{}, 1)
g.wg.Add(1)
go func() {
defer g.handlePanic()
defer g.wg.Done()
t := time.NewTimer(jitterDuration(interval, jitter))
defer t.Stop()
for {
if g.ctx.Err() != nil {
return
}
select {
case <-g.jobCtx.Done():
return
case <-t.C:
t.Reset(jitterDuration(interval, jitter))
case <-c:
if !t.Stop() {
<-t.C
}
t.Reset(jitterDuration(interval, jitter))
}
f(g.ctx)
}
}()
return func() {
select {
case c <- struct{}{}:
default:
}
}
}
func (g *Group) resetCtx() {
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
g.ctx, g.cancel = context.WithCancel(g.baseCtx)
}
// Cancel is send to all of the spawn goroutines and ends periodic
// or trigger routines.
func (g *Group) Cancel() {
g.cancel()
g.finish()
g.resetCtx()
}
// Finish will ends all periodic or polls routines. It will let
// currently running functions to finish (cancel is not sent).
//
// It is not safe to call Wait concurrently with any other method on g.
func (g *Group) Finish() {
g.finish()
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
}
// CancelAndWait cancels the context passed to any of the spawned goroutines and waits for all spawned
// goroutines to exit.
//
// It is not safe to call Wait concurrently with any other method on g.
func (g *Group) CancelAndWait() {
g.finish()
g.cancel()
g.wg.Wait()
g.resetCtx()
}
// WaitToFinish will ends all periodic or polls routines. It will wait for
// currently running functions to finish (cancel is not sent).
//
// It is not safe to call Wait concurrently with any other method on g.
func (g *Group) WaitToFinish() {
g.finish()
g.wg.Wait()
g.jobCtx, g.finish = context.WithCancel(g.baseCtx)
}
func (g *Group) handlePanic() {
if g.panicHandler != nil {
g.panicHandler.HandlePanic()
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -21,6 +21,7 @@ import (
"net/http"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/sirupsen/logrus"
@ -32,14 +33,14 @@ func defaultAPIOptions(
version *semver.Version,
cookieJar http.CookieJar,
transport http.RoundTripper,
poolSize int,
panicHandler async.PanicHandler,
) []proton.Option {
return []proton.Option{
proton.WithHostURL(apiURL),
proton.WithAppVersion(constants.AppVersion(version.Original())),
proton.WithCookieJar(cookieJar),
proton.WithTransport(transport),
proton.WithAttPoolSize(poolSize),
proton.WithLogger(logrus.StandardLogger()),
proton.WithPanicHandler(panicHandler),
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -23,6 +23,7 @@ import (
"net/http"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/go-proton-api"
)
@ -32,7 +33,7 @@ func newAPIOptions(
version *semver.Version,
cookieJar http.CookieJar,
transport http.RoundTripper,
poolSize int,
panicHandler async.PanicHandler,
) []proton.Option {
return defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize)
return defaultAPIOptions(apiURL, version, cookieJar, transport, panicHandler)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -24,6 +24,7 @@ import (
"os"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/go-proton-api"
)
@ -33,9 +34,9 @@ func newAPIOptions(
version *semver.Version,
cookieJar http.CookieJar,
transport http.RoundTripper,
poolSize int,
panicHandler async.PanicHandler,
) []proton.Option {
opt := defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize)
opt := defaultAPIOptions(apiURL, version, cookieJar, transport, panicHandler)
if host := os.Getenv("BRIDGE_API_HOST"); host != "" {
opt = append(opt, proton.WithHostURL(host))

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -21,6 +21,7 @@ package bridge
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
@ -29,15 +30,17 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon"
"github.com/ProtonMail/gluon/async"
imapEvents "github.com/ProtonMail/gluon/events"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/watcher"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/async"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/bradenaw/juniper/xslices"
@ -90,8 +93,8 @@ type Bridge struct {
// locator is the bridge's locator.
locator Locator
// crashHandler
crashHandler async.PanicHandler
// panicHandler
panicHandler async.PanicHandler
// reporter
reporter reporter.Reporter
@ -108,6 +111,12 @@ type Bridge struct {
logIMAPServer bool
logSMTP bool
// These two variables keep track of the startup values for the two settings of the same name.
// They are updated in the vault on startup so that we're sure they're updated in case of kill/crash,
// but we need to keep their initial value for the current instance of bridge.
firstStart bool
lastVersion *semver.Version
// tasks manages the bridge's goroutines.
tasks *async.Group
@ -116,10 +125,12 @@ type Bridge struct {
// goUpdate triggers a check/install of updates.
goUpdate func()
uidValidityGenerator imap.UIDValidityGenerator
}
// New creates a new bridge.
func New( //nolint:funlen
func New(
locator Locator, // the locator to provide paths to store data
vault *vault.Vault, // the bridge's encrypted data store
autostarter Autostarter, // the autostarter to manage autostart settings
@ -132,17 +143,18 @@ func New( //nolint:funlen
tlsReporter TLSReporter, // the TLS reporter to report TLS errors
roundTripper http.RoundTripper, // the round tripper to use for API requests
proxyCtl ProxyController, // the DoH controller
crashHandler async.PanicHandler,
panicHandler async.PanicHandler,
reporter reporter.Reporter,
uidValidityGenerator imap.UIDValidityGenerator,
logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
logSMTP bool, // whether to log SMTP activity
) (*Bridge, <-chan events.Event, error) {
// api is the user's API manager.
api := proton.New(newAPIOptions(apiURL, curVersion, cookieJar, roundTripper, vault.SyncAttPool())...)
api := proton.New(newAPIOptions(apiURL, curVersion, cookieJar, roundTripper, panicHandler)...)
// tasks holds all the bridge's background tasks.
tasks := async.NewGroup(context.Background(), crashHandler)
tasks := async.NewGroup(context.Background(), panicHandler)
// imapEventCh forwards IMAP events from gluon instances to the bridge for processing.
imapEventCh := make(chan imapEvents.Event)
@ -157,12 +169,13 @@ func New( //nolint:funlen
autostarter,
updater,
curVersion,
crashHandler,
panicHandler,
reporter,
api,
identifier,
proxyCtl,
uidValidityGenerator,
logIMAPClient, logIMAPServer, logSMTP,
)
if err != nil {
@ -177,20 +190,9 @@ func New( //nolint:funlen
return nil, nil, fmt.Errorf("failed to initialize bridge: %w", err)
}
// Start serving IMAP.
if err := bridge.serveIMAP(); err != nil {
bridge.PushError(ErrServeIMAP)
}
// Start serving SMTP.
if err := bridge.serveSMTP(); err != nil {
bridge.PushError(ErrServeSMTP)
}
return bridge, eventCh, nil
}
// nolint:funlen
func newBridge(
tasks *async.Group,
imapEventCh chan imapEvents.Event,
@ -200,12 +202,13 @@ func newBridge(
autostarter Autostarter,
updater Updater,
curVersion *semver.Version,
crashHandler async.PanicHandler,
panicHandler async.PanicHandler,
reporter reporter.Reporter,
api *proton.Manager,
identifier Identifier,
proxyCtl ProxyController,
uidValidityGenerator imap.UIDValidityGenerator,
logIMAPClient, logIMAPServer, logSMTP bool,
) (*Bridge, error) {
@ -214,13 +217,29 @@ func newBridge(
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
gluonDir, err := getGluonDir(vault)
gluonCacheDir, err := getGluonDir(vault)
if err != nil {
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
}
gluonDataDir, err := locator.ProvideGluonDataPath()
if err != nil {
return nil, fmt.Errorf("failed to get Gluon Database directory: %w", err)
}
firstStart := vault.GetFirstStart()
if err := vault.SetFirstStart(false); err != nil {
return nil, fmt.Errorf("failed to save first start indicator: %w", err)
}
lastVersion := vault.GetLastVersion()
if err := vault.SetLastVersion(curVersion); err != nil {
return nil, fmt.Errorf("failed to save last version indicator: %w", err)
}
imapServer, err := newIMAPServer(
gluonDir,
gluonCacheDir,
gluonDataDir,
curVersion,
tlsConfig,
reporter,
@ -228,12 +247,14 @@ func newBridge(
logIMAPServer,
imapEventCh,
tasks,
uidValidityGenerator,
panicHandler,
)
if err != nil {
return nil, fmt.Errorf("failed to create IMAP server: %w", err)
}
focusService, err := focus.NewService(curVersion)
focusService, err := focus.NewService(locator, curVersion, panicHandler)
if err != nil {
return nil, fmt.Errorf("failed to create focus service: %w", err)
}
@ -259,7 +280,7 @@ func newBridge(
newVersion: curVersion,
newVersionLock: safe.NewRWMutex(),
crashHandler: crashHandler,
panicHandler: panicHandler,
reporter: reporter,
focusService: focusService,
@ -270,7 +291,12 @@ func newBridge(
logIMAPServer: logIMAPServer,
logSMTP: logSMTP,
firstStart: firstStart,
lastVersion: lastVersion,
tasks: tasks,
uidValidityGenerator: uidValidityGenerator,
}
bridge.smtpServer = newSMTPServer(bridge, tlsConfig, logSMTP)
@ -278,7 +304,6 @@ func newBridge(
return bridge, nil
}
// nolint:funlen
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Enable or disable the proxy at startup.
if bridge.vault.GetProxyAllowed() {
@ -347,15 +372,32 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
})
})
// Attempt to lazy load users when triggered.
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
logrus.Info("Loading users")
// We need to load users before we can start the IMAP and SMTP servers.
// We must only start the servers once.
var once sync.Once
// Attempt to load users from the vault when triggered.
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
if err := bridge.loadUsers(ctx); err != nil {
logrus.WithError(err).Error("Failed to load users")
} else {
bridge.publish(events.AllUsersLoaded{})
if netErr := new(proton.NetError); !errors.As(err, &netErr) {
sentry.ReportError(bridge.reporter, "Failed to load users", err)
}
return
}
bridge.publish(events.AllUsersLoaded{})
// Once all users have been loaded, start the bridge's IMAP and SMTP servers.
once.Do(func() {
if err := bridge.serveIMAP(); err != nil {
logrus.WithError(err).Error("Failed to start IMAP server")
}
if err := bridge.serveSMTP(); err != nil {
logrus.WithError(err).Error("Failed to start SMTP server")
}
})
})
defer bridge.goLoad()
@ -433,11 +475,6 @@ func (bridge *Bridge) Close(ctx context.Context) {
}
bridge.watchers = nil
// Save the last version of bridge that was run.
if err := bridge.vault.SetLastVersion(bridge.curVersion); err != nil {
logrus.WithError(err).Error("Failed to save last version")
}
}
func (bridge *Bridge) publish(event events.Event) {
@ -459,7 +496,7 @@ func (bridge *Bridge) addWatcher(ofType ...events.Event) *watcher.Watcher[events
bridge.watchersLock.Lock()
defer bridge.watchersLock.Unlock()
watcher := watcher.New(ofType...)
watcher := watcher.New(bridge.panicHandler, ofType...)
bridge.watchers = append(bridge.watchers, watcher)
@ -520,7 +557,7 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) {
}
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert(), vault.GetBridgeTLSKey())
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
if err != nil {
return nil, err
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -21,20 +21,24 @@ import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"runtime"
"path/filepath"
"sync"
"testing"
"time"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/go-proton-api/server/backend"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
@ -45,6 +49,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/tests"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-imap/client"
"github.com/stretchr/testify/require"
)
@ -119,8 +124,11 @@ func TestBridge_Focus(t *testing.T) {
raiseCh, done := bridge.GetEvents(events.Raise{})
defer done()
settingsFolder, err := locator.ProvideSettingsPath()
require.NoError(t, err)
// Simulate a focus event.
focus.TryRaise()
focus.TryRaise(settingsFolder)
// Wait for the event.
require.IsType(t, events.Raise{}, <-raiseCh)
@ -349,7 +357,7 @@ func TestBridge_BadVaultKey(t *testing.T) {
})
}
func TestBridge_MissingGluonDir(t *testing.T) {
func TestBridge_MissingGluonStore(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
var gluonDir string
@ -361,13 +369,36 @@ func TestBridge_MissingGluonDir(t *testing.T) {
require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
// Get the gluon dir.
gluonDir = bridge.GetGluonDir()
gluonDir = bridge.GetGluonCacheDir()
})
// The user removes the gluon dir while bridge is not running.
require.NoError(t, os.RemoveAll(gluonDir))
// Bridge starts but can't find the gluon dir; there should be no error.
// Bridge starts but can't find the gluon store dir; there should be no error.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ...
})
})
}
func TestBridge_MissingGluonDatabase(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
var gluonDir string
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
require.NoError(t, err)
// Get the gluon dir.
gluonDir, err = bridge.GetGluonDataDir()
require.NoError(t, err)
})
// The user removes the gluon dir while bridge is not running.
require.NoError(t, os.RemoveAll(gluonDir))
// Bridge starts but can't find the gluon database dir; there should be no error.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ...
})
@ -456,41 +487,158 @@ func TestBridge_FactoryReset(t *testing.T) {
})
}
func TestBridge_ChangeCacheDirectoryFailsBetweenDifferentVolumes(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Test only necessary on windows")
}
func TestBridge_InitGluonDirectory(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
configDir, err := b.GetGluonDataDir()
require.NoError(t, err)
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
require.False(t, os.IsNotExist(err))
_, err = os.ReadDir(bridge.ApplyGluonConfigPathSuffix(configDir))
require.False(t, os.IsNotExist(err))
})
})
}
func TestBridge_LoginFailed(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Change directory
err := bridge.SetGluonDir(ctx, "XX:\\")
require.Error(t, err)
failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{}))
defer done()
imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
require.NoError(t, err)
require.Error(t, imapClient.Login("badUser", "badPass"))
require.Equal(t, "badUser", (<-failCh).Username)
})
})
}
func TestBridge_ChangeCacheDirectory(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userID, addrID, err := s.CreateUser("imap", password)
require.NoError(t, err)
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
require.NoError(t, err)
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, labelID, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
newCacheDir := t.TempDir()
currentCacheDir := bridge.GetGluonDir()
currentCacheDir := b.GetGluonCacheDir()
configDir, err := b.GetGluonDataDir()
require.NoError(t, err)
// Login the user.
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
defer done()
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
// The user is now connected.
require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
require.Equal(t, []string{userID}, b.GetUserIDs())
require.Equal(t, []string{userID}, getConnectedUserIDs(t, b))
// Change directory
err = bridge.SetGluonDir(ctx, newCacheDir)
err = b.SetGluonDir(ctx, newCacheDir)
require.NoError(t, err)
_, err = os.ReadDir(currentCacheDir)
// Old store should no more exists.
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(currentCacheDir))
require.True(t, os.IsNotExist(err))
// Database should not have changed.
_, err = os.ReadDir(bridge.ApplyGluonConfigPathSuffix(configDir))
require.False(t, os.IsNotExist(err))
require.Equal(t, newCacheDir, bridge.GetGluonDir())
// New path should have Gluon sub-folder.
require.Equal(t, filepath.Join(newCacheDir, "gluon"), b.GetGluonCacheDir())
// And store should be inside it.
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
require.False(t, os.IsNotExist(err))
// We should be able to fetch.
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
status, err := client.Select(`Folders/folder`, false)
require.NoError(t, err)
require.Equal(t, uint32(10), status.Messages)
})
})
}
func TestBridge_ChangeAddressOrder(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("imap", password)
require.NoError(t, err)
// Create a second address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
// Log the user in with its first address.
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
defer done()
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
// We should see 10 messages in the inbox.
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
status, err := client.Select(`Inbox`, false)
require.NoError(t, err)
require.Equal(t, uint32(10), status.Messages)
})
// Make the second address the primary one.
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.OrderAddresses(ctx, proton.OrderAddressesReq{AddressIDs: []string{aliasID, addrID}}))
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
// We should still see 10 messages in the inbox.
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.State == bridge.Connected)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
require.Eventually(t, func() bool {
status, err := client.Select(`Inbox`, false)
require.NoError(t, err)
return status.Messages == 10
}, 5*time.Second, 100*time.Millisecond)
})
})
}
@ -522,29 +670,37 @@ func withEnv(t *testing.T, tests func(context.Context, *server.Server, *proton.N
tests(ctx, server, netCtl, locations, vaultKey)
}
// withMocks creates the mock objects used in the tests.
func withMocks(t *testing.T, tests func(*bridge.Mocks)) {
mocks := bridge.NewMocks(t, v2_3_0, v2_3_0)
defer mocks.Close()
tests(mocks)
}
// Needs to be global to survive bridge shutdown/startup in unit tests as they happen to fast.
var testUIDValidityGenerator = imap.DefaultEpochUIDValidityGenerator()
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
func withBridge(
func withBridgeNoMocks(
ctx context.Context,
t *testing.T,
mocks *bridge.Mocks,
apiURL string,
netCtl *proton.NetCtl,
locator bridge.Locator,
vaultKey []byte,
tests func(*bridge.Bridge, *bridge.Mocks),
tests func(*bridge.Bridge),
) {
// Create the mock objects used in the tests.
mocks := bridge.NewMocks(t, v2_3_0, v2_3_0)
defer mocks.Close()
// Bridge will enable the proxy by default at startup.
mocks.ProxyCtl.EXPECT().AllowProxy()
// Bridge will disable the proxy by default at startup.
mocks.ProxyCtl.EXPECT().DisallowProxy()
// Get the path to the vault.
vaultDir, err := locator.ProvideSettingsPath()
require.NoError(t, err)
// Create the vault.
vault, _, err := vault.New(vaultDir, t.TempDir(), vaultKey)
vault, _, err := vault.New(vaultDir, t.TempDir(), vaultKey, async.NoopPanicHandler{})
require.NoError(t, err)
// Create a new cookie jar.
@ -570,6 +726,7 @@ func withBridge(
mocks.ProxyCtl,
mocks.CrashHandler,
mocks.Reporter,
testUIDValidityGenerator,
// The logging stuff.
os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1",
@ -581,6 +738,10 @@ func withBridge(
// Wait for bridge to finish loading users.
waitForEvent(t, eventCh, events.AllUsersLoaded{})
// Wait for bridge to start the IMAP server.
waitForEvent(t, eventCh, events.IMAPServerReady{})
// Wait for bridge to start the SMTP server.
waitForEvent(t, eventCh, events.SMTPServerReady{})
// Set random IMAP and SMTP ports for the tests.
require.NoError(t, bridge.SetIMAPPort(0))
@ -590,10 +751,27 @@ func withBridge(
defer bridge.Close(ctx)
// Use the bridge.
tests(bridge, mocks)
tests(bridge)
}
func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, wantEvent T) {
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
func withBridge(
ctx context.Context,
t *testing.T,
apiURL string,
netCtl *proton.NetCtl,
locator bridge.Locator,
vaultKey []byte,
tests func(*bridge.Bridge, *bridge.Mocks),
) {
withMocks(t, func(mocks *bridge.Mocks) {
withBridgeNoMocks(ctx, t, mocks, apiURL, netCtl, locator, vaultKey, func(bridge *bridge.Bridge) {
tests(bridge, mocks)
})
})
}
func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, _ T) {
t.Helper()
for event := range eventCh {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -37,7 +37,7 @@ const (
MaxCompressedFilesCount = 6
)
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error { //nolint:funlen
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error {
var account string
if info, err := bridge.QueryUserInfo(username); err == nil {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -22,10 +22,7 @@ import "errors"
var (
ErrVaultInsecure = errors.New("the vault is insecure")
ErrVaultCorrupt = errors.New("the vault is corrupt")
ErrServeIMAP = errors.New("failed to serve IMAP")
ErrServeSMTP = errors.New("failed to serve SMTP")
ErrWatchUpdates = errors.New("failed to watch for updates")
ErrWatchUpdates = errors.New("failed to watch for updates")
ErrNoSuchUser = errors.New("no such user")
ErrUserAlreadyExists = errors.New("user already exists")

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -18,6 +18,8 @@
package bridge
import (
"fmt"
"io"
"os"
"path/filepath"
)
@ -62,3 +64,75 @@ func moveFile(from, to string) error {
return nil
}
func copyDir(from, to string) error {
entries, err := os.ReadDir(from)
if err != nil {
return err
}
if err := createIfNotExists(to, 0o700); err != nil {
return err
}
for _, entry := range entries {
sourcePath := filepath.Join(from, entry.Name())
destPath := filepath.Join(to, entry.Name())
if entry.IsDir() {
if err := copyDir(sourcePath, destPath); err != nil {
return err
}
} else {
if err := copyFile(sourcePath, destPath); err != nil {
return err
}
}
}
return nil
}
func copyFile(srcFile, dstFile string) error {
out, err := os.Create(filepath.Clean(dstFile))
defer func(out *os.File) {
_ = out.Close()
}(out)
if err != nil {
return err
}
in, err := os.Open(filepath.Clean(srcFile))
defer func(in *os.File) {
_ = in.Close()
}(in)
if err != nil {
return err
}
_, err = io.Copy(out, in)
if err != nil {
return err
}
return nil
}
func exists(filePath string) bool {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return false
}
return true
}
func createIfNotExists(dir string, perm os.FileMode) error {
if exists(dir) {
return nil
}
if err := os.MkdirAll(dir, perm); err != nil {
return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
}
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -20,21 +20,21 @@ package bridge
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon"
"github.com/ProtonMail/gluon/async"
imapEvents "github.com/ProtonMail/gluon/events"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/store"
"github.com/ProtonMail/proton-bridge/v3/internal/async"
"github.com/ProtonMail/gluon/store/fallback_v0"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
@ -47,26 +47,42 @@ const (
)
func (bridge *Bridge) serveIMAP() error {
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
port, err := func() (int, error) {
if bridge.imapServer == nil {
return 0, fmt.Errorf("no IMAP server instance running")
}
logrus.Info("Starting IMAP server")
logrus.Info("Starting IMAP server")
imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig)
if err != nil {
return 0, fmt.Errorf("failed to create IMAP listener: %w", err)
}
bridge.imapListener = imapListener
if err := bridge.imapServer.Serve(context.Background(), bridge.imapListener); err != nil {
return 0, fmt.Errorf("failed to serve IMAP: %w", err)
}
if err := bridge.vault.SetIMAPPort(getPort(imapListener.Addr())); err != nil {
return 0, fmt.Errorf("failed to store IMAP port in vault: %w", err)
}
return getPort(imapListener.Addr()), nil
}()
imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig)
if err != nil {
return fmt.Errorf("failed to create IMAP listener: %w", err)
bridge.publish(events.IMAPServerError{
Error: err,
})
return err
}
bridge.imapListener = imapListener
if err := bridge.imapServer.Serve(context.Background(), bridge.imapListener); err != nil {
return fmt.Errorf("failed to serve IMAP: %w", err)
}
if err := bridge.vault.SetIMAPPort(getPort(imapListener.Addr())); err != nil {
return fmt.Errorf("failed to set IMAP port: %w", err)
}
bridge.publish(events.IMAPServerReady{
Port: port,
})
return nil
}
@ -78,6 +94,8 @@ func (bridge *Bridge) restartIMAP() error {
if err := bridge.imapListener.Close(); err != nil {
return fmt.Errorf("failed to close IMAP listener: %w", err)
}
bridge.publish(events.IMAPServerStopped{})
}
return bridge.serveIMAP()
@ -90,6 +108,7 @@ func (bridge *Bridge) closeIMAP(ctx context.Context) error {
if err := bridge.imapServer.Close(ctx); err != nil {
return fmt.Errorf("failed to close IMAP server: %w", err)
}
bridge.imapServer = nil
}
@ -99,6 +118,8 @@ func (bridge *Bridge) closeIMAP(ctx context.Context) error {
}
}
bridge.publish(events.IMAPServerStopped{})
return nil
}
@ -122,9 +143,53 @@ func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error {
if gluonID, ok := user.GetGluonID(addrID); ok {
log.WithField("gluonID", gluonID).Info("Loading existing IMAP user")
if err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
// Load the user, checking whether the DB was newly created.
isNew, err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey())
if err != nil {
return fmt.Errorf("failed to load IMAP user: %w", err)
}
if isNew {
// If the DB was newly created, clear the sync status; gluon's DB was not found.
logrus.Warn("IMAP user DB was newly created, clearing sync status")
// Remove the user from IMAP so we can clear the sync status.
if err := bridge.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
return fmt.Errorf("failed to remove IMAP user: %w", err)
}
// Clear the sync status -- we need to resync all messages.
if err := user.ClearSyncStatus(); err != nil {
return fmt.Errorf("failed to clear sync status: %w", err)
}
// Add the user back to the IMAP server.
if isNew, err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
return fmt.Errorf("failed to add IMAP user: %w", err)
} else if isNew {
panic("IMAP user should already have a database")
}
} else if status := user.GetSyncStatus(); !status.HasLabels {
// Otherwise, the DB already exists -- if the labels are not yet synced, we need to re-create the DB.
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
return fmt.Errorf("failed to remove old IMAP user: %w", err)
}
if err := user.RemoveGluonID(addrID, gluonID); err != nil {
return fmt.Errorf("failed to remove old IMAP user ID: %w", err)
}
gluonID, err := bridge.imapServer.AddUser(ctx, imapConn, user.GluonKey())
if err != nil {
return fmt.Errorf("failed to add IMAP user: %w", err)
}
if err := user.SetGluonID(addrID, gluonID); err != nil {
return fmt.Errorf("failed to set IMAP user ID: %w", err)
}
log.WithField("gluonID", gluonID).Info("Re-created IMAP user")
}
} else {
log.Info("Creating new IMAP user")
@ -141,6 +206,9 @@ func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error {
}
}
// Trigger a sync for the user, if needed.
user.TriggerSync()
return nil
}
@ -149,6 +217,7 @@ func (bridge *Bridge) removeIMAPUser(ctx context.Context, user *user.User, withD
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
logrus.WithFields(logrus.Fields{
"userID": user.ID(),
"withData": withData,
@ -195,47 +264,52 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
if event.IMAPID.Name != "" && event.IMAPID.Version != "" {
bridge.identifier.SetClient(event.IMAPID.Name, event.IMAPID.Version)
}
case imapEvents.LoginFailed:
logrus.WithFields(logrus.Fields{
"sessionID": event.SessionID,
"username": event.Username,
}).Info("Received IMAP login failure notification")
bridge.publish(events.IMAPLoginFailed{Username: event.Username})
}
}
func getGluonDir(encVault *vault.Vault) (string, error) {
empty, exists, err := isEmpty(encVault.GetGluonDir())
if err != nil {
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
if err := os.MkdirAll(encVault.GetGluonCacheDir(), 0o700); err != nil {
return "", fmt.Errorf("failed to create gluon dir: %w", err)
}
if !exists {
if err := os.MkdirAll(encVault.GetGluonDir(), 0o700); err != nil {
return "", fmt.Errorf("failed to create gluon dir: %w", err)
}
}
if empty {
if err := encVault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
return user.ClearSyncStatus()
}); err != nil {
return "", fmt.Errorf("failed to reset user sync status: %w", err)
}
}
return encVault.GetGluonDir(), nil
return encVault.GetGluonCacheDir(), nil
}
func ApplyGluonCachePathSuffix(basePath string) string {
return filepath.Join(basePath, "backend", "store")
}
func ApplyGluonConfigPathSuffix(basePath string) string {
return filepath.Join(basePath, "backend", "db")
}
// nolint:funlen
func newIMAPServer(
gluonDir string,
gluonCacheDir, gluonConfigDir string,
version *semver.Version,
tlsConfig *tls.Config,
reporter reporter.Reporter,
logClient, logServer bool,
eventCh chan<- imapEvents.Event,
tasks *async.Group,
uidValidityGenerator imap.UIDValidityGenerator,
panicHandler async.PanicHandler,
) (*gluon.Server, error) {
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
logrus.WithFields(logrus.Fields{
"gluonDir": gluonDir,
"version": version,
"logClient": logClient,
"logServer": logServer,
"gluonStore": gluonCacheDir,
"gluonDB": gluonConfigDir,
"version": version,
"logClient": logClient,
"logServer": logServer,
}).Info("Creating IMAP server")
if logClient || logServer {
@ -263,11 +337,14 @@ func newIMAPServer(
imapServer, err := gluon.New(
gluon.WithTLS(tlsConfig),
gluon.WithDataDir(gluonDir),
gluon.WithDataDir(gluonCacheDir),
gluon.WithDatabaseDir(gluonConfigDir),
gluon.WithStoreBuilder(new(storeBuilder)),
gluon.WithLogger(imapClientLog, imapServerLog),
getGluonVersionInfo(version),
gluon.WithReporter(reporter),
gluon.WithUIDValidityGenerator(uidValidityGenerator),
gluon.WithPanicHandler(panicHandler),
)
if err != nil {
return nil, err
@ -297,32 +374,13 @@ func getGluonVersionInfo(version *semver.Version) gluon.Option {
)
}
// isEmpty returns whether the given directory is empty.
// If the directory does not exist, the second return value is false.
func isEmpty(dir string) (bool, bool, error) {
if _, err := os.Stat(dir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return false, false, fmt.Errorf("failed to stat %s: %w", dir, err)
}
return true, false, nil
}
entries, err := os.ReadDir(dir)
if err != nil {
return false, false, fmt.Errorf("failed to read dir %s: %w", dir, err)
}
return len(entries) == 0, true, nil
}
type storeBuilder struct{}
func (*storeBuilder) New(path, userID string, passphrase []byte) (store.Store, error) {
return store.NewOnDiskStore(
filepath.Join(path, userID),
passphrase,
store.WithCompressor(new(store.GZipCompressor)),
store.WithFallback(fallback_v0.NewOnDiskStoreV0WithCompressor(&fallback_v0.GZipCompressor{})),
)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -57,6 +57,7 @@ func TestBridge_Refresh(t *testing.T) {
require.Equal(t, userID, (<-syncCh).UserID)
})
var uidValidities = make(map[string]uint32, len(names))
// If we then connect an IMAP client, it should see all the labels with UID validity of 1.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
@ -73,7 +74,7 @@ func TestBridge_Refresh(t *testing.T) {
for _, name := range names {
status, err := client.Select("Folders/"+name, false)
require.NoError(t, err)
require.Equal(t, uint32(1000), status.UidValidity)
uidValidities[name] = status.UidValidity
}
})
@ -106,7 +107,7 @@ func TestBridge_Refresh(t *testing.T) {
for _, name := range names {
status, err := client.Select("Folders/"+name, false)
require.NoError(t, err)
require.Equal(t, uint32(1001), status.UidValidity)
require.Greater(t, status.UidValidity, uidValidities[name])
}
})
})

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -18,10 +18,12 @@
package bridge_test
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"os"
"strings"
"testing"
"time"
@ -30,6 +32,7 @@ import (
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/emersion/go-sasl"
@ -42,7 +45,7 @@ func TestBridge_Send(t *testing.T) {
_, _, err := s.CreateUser("recipient", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
@ -100,7 +103,7 @@ func TestBridge_Send(t *testing.T) {
defer recipientIMAPClient.Logout() //nolint:errcheck
// Sender should have 10 messages in the sent folder.
// Recipient should have 0 messages in inbox.
// Recipient should have 10 messages in inbox.
require.Eventually(t, func() bool {
sent, err := senderIMAPClient.Status(`Sent`, []imap.StatusItem{imap.StatusMessages})
require.NoError(t, err)
@ -113,3 +116,396 @@ func TestBridge_Send(t *testing.T) {
})
})
}
func TestBridge_SendDraftFlags(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a recipient user.
_, _, err := s.CreateUser("recipient", password)
require.NoError(t, err)
// The sender should be fully synced.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
defer done()
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
})
// Start the bridge.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
// Get the sender user info.
userInfo, err := bridge.QueryUserInfo(username)
require.NoError(t, err)
// Connect the sender IMAP client.
imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
require.NoError(t, err)
require.NoError(t, imapClient.Login(userInfo.Addresses[0], string(userInfo.BridgePass)))
defer imapClient.Logout() //nolint:errcheck
// The message to send.
const message = `Subject: Test\r\n\r\nHello world!`
// Save a draft.
require.NoError(t, imapClient.Append("Drafts", []string{imap.DraftFlag}, time.Now(), strings.NewReader(message)))
// Assert that the draft exists and is marked as a draft.
{
messages, err := clientFetch(imapClient, "Drafts")
require.NoError(t, err)
require.Len(t, messages, 1)
require.Contains(t, messages[0].Flags, imap.DraftFlag)
}
// Connect the SMTP client.
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
require.NoError(t, err)
defer smtpClient.Close() //nolint:errcheck
// Upgrade to TLS.
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
// Authorize with SASL PLAIN.
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
userInfo.Addresses[0],
userInfo.Addresses[0],
string(userInfo.BridgePass)),
))
// Send the message.
require.NoError(t, smtpClient.SendMail(
userInfo.Addresses[0],
[]string{"recipient@" + s.GetDomain()},
strings.NewReader(message),
))
// Delete the draft: add the \Deleted flag and expunge.
{
status, err := imapClient.Select("Drafts", false)
require.NoError(t, err)
require.Equal(t, uint32(1), status.Messages)
// Add the \Deleted flag.
require.NoError(t, clientStore(imapClient, 1, 1, true, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag))
// Expunge.
require.NoError(t, imapClient.Expunge(nil))
}
// Assert that the draft is eventually gone.
require.Eventually(t, func() bool {
status, err := imapClient.Select("Drafts", false)
require.NoError(t, err)
return status.Messages == 0
}, 10*time.Second, 100*time.Millisecond)
// Assert that the message is eventually in the sent folder.
require.Eventually(t, func() bool {
messages, err := clientFetch(imapClient, "Sent")
require.NoError(t, err)
return len(messages) == 1
}, 10*time.Second, 100*time.Millisecond)
// Assert that the message is not marked as a draft.
{
messages, err := clientFetch(imapClient, "Sent")
require.NoError(t, err)
require.Len(t, messages, 1)
require.NotContains(t, messages[0].Flags, imap.DraftFlag)
}
})
})
}
func TestBridge_SendInvite(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a recipient user.
_, _, err := s.CreateUser("recipient", password)
require.NoError(t, err)
// Set "attach public keys" to true for the user.
withClient(ctx, t, s, username, password, func(ctx context.Context, client *proton.Client) {
settings, err := client.SetAttachPublicKey(ctx, proton.SetAttachPublicKeyReq{AttachPublicKey: true})
require.NoError(t, err)
require.Equal(t, proton.Bool(true), settings.AttachPublicKey)
})
// The sender should be fully synced.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
defer done()
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
})
// Start the bridge.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
// Get the sender user info.
userInfo, err := bridge.QueryUserInfo(username)
require.NoError(t, err)
// Connect the sender IMAP client.
imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
require.NoError(t, err)
require.NoError(t, imapClient.Login(userInfo.Addresses[0], string(userInfo.BridgePass)))
defer imapClient.Logout() //nolint:errcheck
// The message to send.
b, err := os.ReadFile("testdata/invite.eml")
require.NoError(t, err)
// Save a draft.
require.NoError(t, imapClient.Append("Drafts", []string{imap.DraftFlag}, time.Now(), bytes.NewReader(b)))
// Assert that the draft exists and is marked as a draft.
{
messages, err := clientFetch(imapClient, "Drafts")
require.NoError(t, err)
require.Len(t, messages, 1)
require.Contains(t, messages[0].Flags, imap.DraftFlag)
}
// Connect the SMTP client.
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
require.NoError(t, err)
defer smtpClient.Close() //nolint:errcheck
// Upgrade to TLS.
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
// Authorize with SASL PLAIN.
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
userInfo.Addresses[0],
userInfo.Addresses[0],
string(userInfo.BridgePass)),
))
// Send the message.
require.NoError(t, smtpClient.SendMail(
userInfo.Addresses[0],
[]string{"recipient@" + s.GetDomain()},
bytes.NewReader(b),
))
// Delete the draft: add the \Deleted flag and expunge.
{
status, err := imapClient.Select("Drafts", false)
require.NoError(t, err)
require.Equal(t, uint32(1), status.Messages)
// Add the \Deleted flag.
require.NoError(t, clientStore(imapClient, 1, 1, true, imap.FormatFlagsOp(imap.AddFlags, true), imap.DeletedFlag))
// Expunge.
require.NoError(t, imapClient.Expunge(nil))
}
// Assert that the draft is eventually gone.
require.Eventually(t, func() bool {
status, err := imapClient.Select("Drafts", false)
require.NoError(t, err)
return status.Messages == 0
}, 10*time.Second, 100*time.Millisecond)
// Assert that the message is eventually in the sent folder.
require.Eventually(t, func() bool {
messages, err := clientFetch(imapClient, "Sent")
require.NoError(t, err)
return len(messages) == 1
}, 10*time.Second, 100*time.Millisecond)
// Assert that the message is not marked as a draft.
{
messages, err := clientFetch(imapClient, "Sent")
require.NoError(t, err)
require.Len(t, messages, 1)
require.NotContains(t, messages[0].Flags, imap.DraftFlag)
}
})
})
}
func TestBridge_SendAddTextBodyPartIfNotExists(t *testing.T) {
const messageMultipartWithoutText = `Content-Type: multipart/mixed;
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
Subject: A new message
Date: Mon, 13 Mar 2023 16:06:16 +0100
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
Content-Disposition: inline;
filename=Cat_August_2010-4.jpeg
Content-Type: image/jpeg;
name="Cat_August_2010-4.jpeg"
Content-Transfer-Encoding: base64
SGVsbG8gd29ybGQ=
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
`
const messageMultipartWithText = `Content-Type: multipart/mixed;
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
Subject: A new message Part2
Date: Mon, 13 Mar 2023 16:06:16 +0100
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
Content-Disposition: inline;
filename=Cat_August_2010-4.jpeg
Content-Type: image/jpeg;
name="Cat_August_2010-4.jpeg"
Content-Transfer-Encoding: base64
SGVsbG8gd29ybGQ=
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
Content-Type: text/html;charset=utf8
Content-Transfer-Encoding: quoted-printable
Hello world
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
`
const messageWithTextOnly = `Content-Type: text/plain;charset=utf8
Content-Transfer-Encoding: quoted-printable
Subject: A new message Part3
Date: Mon, 13 Mar 2023 16:06:16 +0100
Hello world
`
const messageMultipartWithoutTextWithTextAttachment = `Content-Type: multipart/mixed;
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
Subject: A new message Part4
Date: Mon, 13 Mar 2023 16:06:16 +0100
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
Content-Type: text/plain; charset=UTF-8; name="text.txt"
Content-Disposition: attachment; filename="text.txt"
Content-Transfer-Encoding: base64
SGVsbG8gd29ybGQK
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
`
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
_, _, err := s.CreateUser("recipient", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
require.NoError(t, err)
senderInfo, err := bridge.GetUserInfo(senderUserID)
require.NoError(t, err)
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
require.NoError(t, err)
messages := []string{
messageMultipartWithoutText,
messageMultipartWithText,
messageWithTextOnly,
messageMultipartWithoutTextWithTextAttachment,
}
for _, m := range messages {
// Dial the server.
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
require.NoError(t, err)
defer client.Close() //nolint:errcheck
// Upgrade to TLS.
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
// Authorize with SASL LOGIN.
require.NoError(t, client.Auth(sasl.NewLoginClient(
senderInfo.Addresses[0],
string(senderInfo.BridgePass)),
))
// Send the message.
require.NoError(t, client.SendMail(
senderInfo.Addresses[0],
[]string{recipientInfo.Addresses[0]},
strings.NewReader(m),
))
}
// Connect the sender IMAP client.
senderIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
require.NoError(t, err)
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
defer senderIMAPClient.Logout() //nolint:errcheck
// Connect the recipient IMAP client.
recipientIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
require.NoError(t, err)
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
defer recipientIMAPClient.Logout() //nolint:errcheck
require.Eventually(t, func() bool {
messages, err := clientFetch(senderIMAPClient, `Sent`, imap.FetchBodyStructure)
require.NoError(t, err)
require.Equal(t, 4, len(messages))
// messages may not be in order
for _, message := range messages {
switch {
case message.Envelope.Subject == "A new message":
// The message that was sent should now include an empty text/plain body part since there was none
// in the original message.
require.Equal(t, 2, len(message.BodyStructure.Parts))
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
require.Equal(t, "plain", message.BodyStructure.Parts[0].MIMESubType)
require.Equal(t, uint32(0), message.BodyStructure.Parts[0].Size)
require.Equal(t, "image", message.BodyStructure.Parts[1].MIMEType)
require.Equal(t, "jpeg", message.BodyStructure.Parts[1].MIMESubType)
case message.Envelope.Subject == "A new message Part2":
// This message already has a text body, should be unchanged
require.Equal(t, 2, len(message.BodyStructure.Parts))
require.Equal(t, "image", message.BodyStructure.Parts[1].MIMEType)
require.Equal(t, "jpeg", message.BodyStructure.Parts[1].MIMESubType)
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
require.Equal(t, "html", message.BodyStructure.Parts[0].MIMESubType)
case message.Envelope.Subject == "A new message Part3":
// This message already has a text body, should be unchanged
require.Equal(t, 0, len(message.BodyStructure.Parts))
require.Equal(t, "text", message.BodyStructure.MIMEType)
require.Equal(t, "plain", message.BodyStructure.MIMESubType)
case message.Envelope.Subject == "A new message Part4":
// The message that was sent should now include an empty text/plain body part since even though
// there was only a text/plain attachment in the original message.
require.Equal(t, 2, len(message.BodyStructure.Parts))
require.Equal(t, "text", message.BodyStructure.Parts[0].MIMEType)
require.Equal(t, "plain", message.BodyStructure.Parts[0].MIMESubType)
require.Equal(t, uint32(0), message.BodyStructure.Parts[0].Size)
require.Equal(t, "text", message.BodyStructure.Parts[1].MIMEType)
require.Equal(t, "plain", message.BodyStructure.Parts[1].MIMESubType)
require.Equal(t, "attachment", message.BodyStructure.Parts[1].Disposition)
}
}
return true
}, 10*time.Second, 100*time.Millisecond)
})
})
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -21,14 +21,13 @@ import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
"github.com/sirupsen/logrus"
)
@ -114,38 +113,42 @@ func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
return bridge.restartSMTP()
}
func (bridge *Bridge) GetGluonDir() string {
return bridge.vault.GetGluonDir()
func (bridge *Bridge) GetGluonCacheDir() string {
return bridge.vault.GetGluonCacheDir()
}
func (bridge *Bridge) GetGluonDataDir() (string, error) {
return bridge.locator.ProvideGluonDataPath()
}
func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error {
return safe.RLockRet(func() error {
currentGluonDir := bridge.GetGluonDir()
currentGluonDir := bridge.GetGluonCacheDir()
newGluonDir = filepath.Join(newGluonDir, "gluon")
if newGluonDir == currentGluonDir {
return fmt.Errorf("new gluon dir is the same as the old one")
}
currentVolumeName := filepath.VolumeName(currentGluonDir)
newVolumeName := filepath.VolumeName(newGluonDir)
if currentVolumeName != newVolumeName {
return fmt.Errorf("it's currently not possible to move the cache between different volumes")
}
if err := bridge.closeIMAP(context.Background()); err != nil {
return fmt.Errorf("failed to close IMAP: %w", err)
}
if err := moveDir(bridge.GetGluonDir(), newGluonDir); err != nil {
return fmt.Errorf("failed to move gluon dir: %w", err)
if err := bridge.moveGluonCacheDir(currentGluonDir, newGluonDir); err != nil {
logrus.WithError(err).Error("failed to move GluonCacheDir")
if err := bridge.vault.SetGluonDir(currentGluonDir); err != nil {
return fmt.Errorf("failed to revert GluonCacheDir: %w", err)
}
}
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
return fmt.Errorf("failed to set new gluon dir: %w", err)
gluonDataDir, err := bridge.GetGluonDataDir()
if err != nil {
return fmt.Errorf("failed to get Gluon Database directory: %w", err)
}
imapServer, err := newIMAPServer(
bridge.vault.GetGluonDir(),
bridge.vault.GetGluonCacheDir(),
gluonDataDir,
bridge.curVersion,
bridge.tlsConfig,
bridge.reporter,
@ -153,6 +156,8 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
bridge.logIMAPServer,
bridge.imapEventCh,
bridge.tasks,
bridge.uidValidityGenerator,
bridge.panicHandler,
)
if err != nil {
return fmt.Errorf("failed to create new IMAP server: %w", err)
@ -174,6 +179,23 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
}, bridge.usersLock)
}
func (bridge *Bridge) moveGluonCacheDir(oldGluonDir, newGluonDir string) error {
logrus.Infof("gluon cache moving from %s to %s", oldGluonDir, newGluonDir)
oldCacheDir := ApplyGluonCachePathSuffix(oldGluonDir)
if err := copyDir(oldCacheDir, ApplyGluonCachePathSuffix(newGluonDir)); err != nil {
return fmt.Errorf("failed to copy gluon dir: %w", err)
}
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
return fmt.Errorf("failed to set new gluon cache dir: %w", err)
}
if err := os.RemoveAll(oldCacheDir); err != nil {
logrus.WithError(err).Error("failed to remove old gluon cache dir")
}
return nil
}
func (bridge *Bridge) GetProxyAllowed() bool {
return bridge.vault.GetProxyAllowed()
}
@ -272,23 +294,11 @@ func (bridge *Bridge) GetCurrentVersion() *semver.Version {
}
func (bridge *Bridge) GetLastVersion() *semver.Version {
return bridge.vault.GetLastVersion()
return bridge.lastVersion
}
func (bridge *Bridge) GetFirstStart() bool {
return bridge.vault.GetFirstStart()
}
func (bridge *Bridge) SetFirstStart(firstStart bool) error {
return bridge.vault.SetFirstStart(firstStart)
}
func (bridge *Bridge) GetFirstStartGUI() bool {
return bridge.vault.GetFirstStartGUI()
}
func (bridge *Bridge) SetFirstStartGUI(firstStart bool) error {
return bridge.vault.SetFirstStartGUI(firstStart)
return bridge.firstStart
}
func (bridge *Bridge) GetColorScheme() string {
@ -299,6 +309,9 @@ func (bridge *Bridge) SetColorScheme(colorScheme string) error {
return bridge.vault.SetColorScheme(colorScheme)
}
// FactoryReset deletes all users, wipes the vault, and deletes all files.
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
// which we need at next startup to decrypt the vault.
func (bridge *Bridge) FactoryReset(ctx context.Context) {
// Delete all the users.
safe.Lock(func() {
@ -308,29 +321,17 @@ func (bridge *Bridge) FactoryReset(ctx context.Context) {
}, bridge.usersLock)
// Wipe the vault.
gluonDir, err := bridge.locator.ProvideGluonPath()
gluonCacheDir, err := bridge.locator.ProvideGluonCachePath()
if err != nil {
logrus.WithError(err).Error("Failed to provide gluon dir")
} else if err := bridge.vault.Reset(gluonDir); err != nil {
} else if err := bridge.vault.Reset(gluonCacheDir); err != nil {
logrus.WithError(err).Error("Failed to reset vault")
}
// Then delete all files.
if err := bridge.locator.Clear(); err != nil {
// Lastly, delete all files except the vault.
if err := bridge.locator.Clear(bridge.vault.Path()); err != nil {
logrus.WithError(err).Error("Failed to clear data paths")
}
// Lastly clear the keychain.
vaultDir, err := bridge.locator.ProvideSettingsPath()
if err != nil {
logrus.WithError(err).Error("Failed to get vault dir")
} else if helper, err := vault.GetHelper(vaultDir); err != nil {
logrus.WithError(err).Error("Failed to get keychain helper")
} else if keychain, err := keychain.NewKeychain(helper, constants.KeyChainName); err != nil {
logrus.WithError(err).Error("Failed to get keychain")
} else if err := keychain.Clear(); err != nil {
logrus.WithError(err).Error("Failed to clear keychain")
}
}
func getPort(addr net.Addr) int {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -119,14 +119,14 @@ func TestBridge_Settings_Proxy(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, proxy is allowed.
require.True(t, bridge.GetProxyAllowed())
require.False(t, bridge.GetProxyAllowed())
// Disallow proxy.
mocks.ProxyCtl.EXPECT().DisallowProxy()
require.NoError(t, bridge.SetProxyAllowed(false))
mocks.ProxyCtl.EXPECT().AllowProxy()
require.NoError(t, bridge.SetProxyAllowed(true))
// Get the new setting.
require.False(t, bridge.GetProxyAllowed())
require.True(t, bridge.GetProxyAllowed())
})
})
}
@ -162,26 +162,7 @@ func TestBridge_Settings_FirstStart(t *testing.T) {
// By default, first start is true.
require.True(t, bridge.GetFirstStart())
// Set first start to false.
require.NoError(t, bridge.SetFirstStart(false))
// Get the new setting.
require.False(t, bridge.GetFirstStart())
})
})
}
func TestBridge_Settings_FirstStartGUI(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, first start is true.
require.True(t, bridge.GetFirstStartGUI())
// Set first start to false.
require.NoError(t, bridge.SetFirstStartGUI(false))
// Get the new setting.
require.False(t, bridge.GetFirstStartGUI())
// the setting of the first start value is managed by bridge itself, so the setter is not exported.
})
})
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -22,6 +22,7 @@ import (
"crypto/tls"
"fmt"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
@ -31,25 +32,41 @@ import (
)
func (bridge *Bridge) serveSMTP() error {
logrus.Info("Starting SMTP server")
port, err := func() (int, error) {
logrus.Info("Starting SMTP server")
smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig)
if err != nil {
return fmt.Errorf("failed to create SMTP listener: %w", err)
}
bridge.smtpListener = smtpListener
bridge.tasks.Once(func(context.Context) {
if err := bridge.smtpServer.Serve(smtpListener); err != nil {
logrus.WithError(err).Info("SMTP server stopped")
smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig)
if err != nil {
return 0, fmt.Errorf("failed to create SMTP listener: %w", err)
}
})
if err := bridge.vault.SetSMTPPort(getPort(smtpListener.Addr())); err != nil {
return fmt.Errorf("failed to set IMAP port: %w", err)
bridge.smtpListener = smtpListener
bridge.tasks.Once(func(context.Context) {
if err := bridge.smtpServer.Serve(smtpListener); err != nil {
logrus.WithError(err).Info("SMTP server stopped")
}
})
if err := bridge.vault.SetSMTPPort(getPort(smtpListener.Addr())); err != nil {
return 0, fmt.Errorf("failed to store SMTP port in vault: %w", err)
}
return getPort(smtpListener.Addr()), nil
}()
if err != nil {
bridge.publish(events.SMTPServerError{
Error: err,
})
return err
}
bridge.publish(events.SMTPServerReady{
Port: port,
})
return nil
}
@ -60,6 +77,8 @@ func (bridge *Bridge) restartSMTP() error {
return fmt.Errorf("failed to close SMTP: %w", err)
}
bridge.publish(events.SMTPServerStopped{})
bridge.smtpServer = newSMTPServer(bridge, bridge.tlsConfig, bridge.logSMTP)
return bridge.serveSMTP()
@ -82,6 +101,8 @@ func (bridge *Bridge) closeSMTP() error {
logrus.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)")
}
bridge.publish(events.SMTPServerStopped{})
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -28,6 +28,7 @@ import (
"testing"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
@ -351,7 +352,7 @@ func withClient(ctx context.Context, t *testing.T, s *server.Server, username st
fn(ctx, c)
}
func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error) { //nolint:unused
func clientFetch(client *client.Client, mailbox string, extraItems ...imap.FetchItem) ([]*imap.Message, error) {
status, err := client.Select(mailbox, false)
if err != nil {
return nil, err
@ -363,10 +364,13 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error)
resCh := make(chan *imap.Message)
fetchItems := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure, "BODY.PEEK[]"}
fetchItems = append(fetchItems, extraItems...)
go func() {
if err := client.Fetch(
&imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}},
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, "BODY.PEEK[]"},
fetchItems,
resCh,
); err != nil {
panic(err)
@ -376,6 +380,35 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error)
return iterator.Collect(iterator.Chan(resCh)), nil
}
func clientStore(client *client.Client, from, to int, isUID bool, item imap.StoreItem, flags ...string) error {
var storeFunc func(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error
if isUID {
storeFunc = client.UidStore
} else {
storeFunc = client.Store
}
return storeFunc(
&imap.SeqSet{Set: []imap.Seq{{Start: uint32(from), Stop: uint32(to)}}},
item,
xslices.Map(flags, func(flag string) interface{} { return flag }),
nil,
)
}
func clientList(client *client.Client) []*imap.MailboxInfo {
resCh := make(chan *imap.MailboxInfo)
go func() {
if err := client.List("", "*", resCh); err != nil {
panic(err)
}
}()
return iterator.Collect(iterator.Chan(resCh))
}
func createNumMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, count int) []string {
literal, err := os.ReadFile(filepath.Join("testdata", "text-plain.eml"))
require.NoError(t, err)
@ -396,10 +429,13 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID,
keyPass, err := salt.SaltForKey(password, user.Keys.Primary().ID)
require.NoError(t, err)
_, addrKRs, err := proton.Unlock(user, addr, keyPass)
_, addrKRs, err := proton.Unlock(user, addr, keyPass, async.NoopPanicHandler{})
require.NoError(t, err)
res, err := stream.Collect(ctx, c.ImportMessages(
_, ok := addrKRs[addrID]
require.True(t, ok)
str, err := c.ImportMessages(
ctx,
addrKRs[addrID],
runtime.NumCPU(),
@ -414,7 +450,10 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID,
Message: message,
}
})...,
))
)
require.NoError(t, err)
res, err := stream.Collect(ctx, str)
require.NoError(t, err)
return xslices.Map(res, func(res proton.ImportRes) string {

85
internal/bridge/testdata/invite.eml vendored Normal file
View File

@ -0,0 +1,85 @@
From: <username@proton.local>
To: <recipient@proton.local>
Subject: Testing calendar invite
Date: Fri, 3 Feb 2023 01:04:32 +0100
Message-ID: <000001d93763$183b74e0$48b25ea0$@proton.local>
MIME-Version: 1.0
Content-Type: text/calendar; method=REQUEST;
charset="utf-8"
Content-Transfer-Encoding: 7bit
X-Mailer: Microsoft Outlook 16.0
Thread-Index: Adk3Yw5pLdgwsT46RviXb/nfvQlesQAAAmGA
Content-Language: en-gb
BEGIN:VCALENDAR
PRODID:-//Microsoft Corporation//Outlook 16.0 MIMEDIR//EN
VERSION:2.0
METHOD:REQUEST
X-MS-OLK-FORCEINSPECTOROPEN:TRUE
BEGIN:VTIMEZONE
TZID:Central European Standard Time
BEGIN:STANDARD
DTSTART:16011028T030000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010325T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ATTENDEE;CN=recipient@proton.local;RSVP=TRUE:mailto:recipient@proton.local
CLASS:PUBLIC
CREATED:20230203T000432Z
DESCRIPTION:qweqweqweqweqweqwe/gn\\n
DTEND;TZID="Central European Standard Time":20230203T020000
DTSTAMP:20230203T000432Z
DTSTART;TZID="Central European Standard Time":20230203T013000
LAST-MODIFIED:20230203T000432Z
LOCATION:qweqwe
ORGANIZER;CN=username@proton.local:mailto:username@proton.local
PRIORITY:5
SEQUENCE:0
SUMMARY;LANGUAGE=en-gb:Testing calendar invite
TRANSP:OPAQUE
UID:040000008200E00074C5B7101A82E008000000003080B2796B37D901000000000000000
0100000001236CD1CD93CA9449C6FF1AC4DEAC44E
X-ALT-DESC;FMTTYPE=text/html:<html xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-mic
rosoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/
12/omml" xmlns="http://www.w3.org/TR/REC-html40"><head><meta http-equiv=Co
ntent-Type content="text/html/g; charset=us-ascii"><meta name=Generator con
tent="Microsoft Word 15 (filtered medium)"><style><!--/gn/* Font Definition
s *//gn@font-face\\n {font-family:"Cambria Math"\\;\\n panose-1:2 4 5 3 5 4 6
3 2 4/g;}\\n@font-face\\n {font-family:Calibri\\;\\n panose-1:2 15 5 2 2 2 4 3
2 4/g;}\\n/* Style Definitions */\\np.MsoNormal\\, li.MsoNormal\\, div.MsoNorma
l/gn {margin:0cm\\;\\n font-size:11.0pt\\;\\n font-family:"Calibri"\\,sans-serif
/g;\\n mso-fareast-language:EN-US\\;}\\nspan.EmailStyle18\\n {mso-style-type:pe
rsonal-compose/g;\\n font-family:"Calibri"\\,sans-serif\\;\\n color:windowtext\\
;}/gn.MsoChpDefault\\n {mso-style-type:export-only\\;\\n font-size:10.0pt\\;}\\n
@page WordSection1/gn {size:612.0pt 792.0pt\\;\\n margin:72.0pt 72.0pt 72.0pt
72.0pt/g;}\\ndiv.WordSection1\\n {page:WordSection1\\;}\\n--></style><!--[if g
te mso 9]><xml>/gn<o:shapedefaults v:ext="edit" spidmax="1026" />\\n</xml><!
[endif]--><!--[if gte mso 9]><xml>/gn<o:shapelayout v:ext="edit">\\n<o:idmap
v:ext="edit" data="1" />/gn</o:shapelayout></xml><![endif]--></head><body
lang=EN-GB link="#0563C1" vlink="#954F72" style='word-wrap:break-word'><di
v class=WordSection1><p class=MsoNormal><span lang=EN-US>qweqweqweqweqweqw
e<o:p></o:p></span></p></div></body></html>
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-DISALLOW-COUNTER:FALSE
X-MS-OLK-AUTOSTARTCHECK:FALSE
X-MS-OLK-CONFTYPE:0
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -18,5 +18,9 @@
package bridge
func (bridge *Bridge) GetBridgeTLSCert() ([]byte, []byte) {
return bridge.vault.GetBridgeTLSCert(), bridge.vault.GetBridgeTLSKey()
return bridge.vault.GetBridgeTLSCert()
}
func (bridge *Bridge) SetBridgeTLSCertPath(certPath, keyPath string) error {
return bridge.vault.SetBridgeTLSCertPath(certPath, keyPath)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -26,10 +26,11 @@ import (
type Locator interface {
ProvideSettingsPath() (string, error)
ProvideLogsPath() (string, error)
ProvideGluonPath() (string, error)
ProvideGluonCachePath() (string, error)
ProvideGluonDataPath() (string, error)
GetLicenseFilePath() string
GetDependencyLicensesLink() string
Clear() error
Clear(...string) error
}
type Identifier interface {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -32,19 +32,7 @@ func (bridge *Bridge) CheckForUpdates() {
}
func (bridge *Bridge) InstallUpdate(version updater.VersionInfo) {
log := logrus.WithFields(logrus.Fields{
"version": version.Version,
"current": bridge.curVersion,
"channel": bridge.vault.GetUpdateChannel(),
})
select {
case bridge.installCh <- installJob{version: version, silent: false}:
log.Info("The update will be installed manually")
default:
log.Info("An update is already being installed")
}
bridge.installCh <- installJob{version: version, silent: false}
}
func (bridge *Bridge) handleUpdate(version updater.VersionInfo) {
@ -89,17 +77,7 @@ func (bridge *Bridge) handleUpdate(version updater.VersionInfo) {
default:
safe.RLock(func() {
if version.Version.GreaterThan(bridge.newVersion) {
log.Info("An update is available")
select {
case bridge.installCh <- installJob{version: version, silent: true}:
log.Info("The update will be installed silently")
default:
log.Info("An update is already being installed")
}
}
bridge.installCh <- installJob{version: version, silent: true}
}, bridge.newVersionLock)
}
}
@ -117,6 +95,12 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
"channel": bridge.vault.GetUpdateChannel(),
})
if !job.version.Version.GreaterThan(bridge.newVersion) {
return
}
log.WithField("silent", job.silent).Info("An update is available")
bridge.publish(events.UpdateAvailable{
Version: job.version,
Compatible: true,
@ -142,6 +126,7 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
Silent: job.silent,
Error: err,
})
default:
log.Info("The update was installed successfully")

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -23,9 +23,10 @@ import (
"fmt"
"runtime"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/async"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
@ -94,7 +95,7 @@ func (bridge *Bridge) GetUserInfo(userID string) (UserInfo, error) {
if len(user.AuthUID()) == 0 {
state = SignedOut
}
info = getUserInfo(user.UserID(), user.Username(), state, user.AddressMode())
info = getUserInfo(user.UserID(), user.Username(), user.PrimaryEmail(), state, user.AddressMode())
}); err != nil {
return UserInfo{}, fmt.Errorf("failed to get user info: %w", err)
}
@ -298,6 +299,59 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
}, bridge.usersLock)
}
// SendBadEventUserFeedback passes the feedback to the given user.
func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string, doResync bool) error {
logrus.WithField("userID", userID).WithField("doResync", doResync).Info("Passing bad event feedback to user")
return safe.LockRet(func() error {
ctx := context.Background()
user, ok := bridge.users[userID]
if !ok {
if rerr := bridge.reporter.ReportMessageWithContext(
"Failed to handle event: feedback failed: no such user",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
}
return ErrNoSuchUser
}
if doResync {
if rerr := bridge.reporter.ReportMessageWithContext(
"Failed to handle event: feedback resync",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
}
if err := bridge.addIMAPUser(ctx, user); err != nil {
return fmt.Errorf("failed to add IMAP user: %w", err)
}
user.BadEventFeedbackResync(ctx)
return nil
}
if rerr := bridge.reporter.ReportMessageWithContext(
"Failed to handle event: feedback logout",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
}
bridge.logoutUser(ctx, user, true, false)
bridge.publish(events.UserLoggedOut{
UserID: userID,
})
return nil
}, bridge.usersLock)
}
func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, authUID, authRef string, keyPass []byte) (string, error) {
apiUser, err := client.GetUser(ctx)
if err != nil {
@ -329,30 +383,37 @@ func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, auth
// loadUsers tries to load each user in the vault that isn't already loaded.
func (bridge *Bridge) loadUsers(ctx context.Context) error {
logrus.WithField("count", len(bridge.vault.GetUserIDs())).Info("Loading users")
defer logrus.Info("Finished loading users")
return bridge.vault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
log := logrus.WithField("userID", user.UserID())
if user.AuthUID() == "" {
log.Info("User is not connected (skipping)")
return nil
}
if safe.RLockRet(func() bool { return mapHas(bridge.users, user.UserID()) }, bridge.usersLock) {
log.Info("User is already loaded (skipping)")
return nil
}
logrus.WithField("userID", user.UserID()).Info("Loading connected user")
log.Info("Loading connected user")
bridge.publish(events.UserLoading{
UserID: user.UserID(),
})
if err := bridge.loadUser(ctx, user); err != nil {
logrus.WithError(err).Error("Failed to load connected user")
log.WithError(err).Error("Failed to load connected user")
bridge.publish(events.UserLoadFail{
UserID: user.UserID(),
Error: err,
})
} else {
logrus.WithField("userID", user.UserID()).Info("Successfully loaded user")
log.Info("Successfully loaded connected user")
bridge.publish(events.UserLoadSuccess{
UserID: user.UserID(),
@ -367,12 +428,13 @@ func (bridge *Bridge) loadUsers(ctx context.Context) error {
func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error {
client, auth, err := bridge.api.NewClientWithRefresh(ctx, user.AuthUID(), user.AuthRef())
if err != nil {
if apiErr := new(proton.Error); errors.As(err, &apiErr) && (apiErr.Code == proton.AuthRefreshTokenInvalid) {
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && (apiErr.Code == proton.AuthRefreshTokenInvalid) {
// The session cannot be refreshed, we sign out the user by clearing his auth secrets.
if err := user.Clear(); err != nil {
logrus.WithError(err).Warn("Failed to clear user secrets")
}
}
return fmt.Errorf("failed to create API client: %w", err)
}
@ -389,6 +451,12 @@ func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error {
return fmt.Errorf("failed to add user: %w", err)
}
if user.PrimaryEmail() != apiUser.Email {
if err := user.SetPrimaryEmail(apiUser.Email); err != nil {
return fmt.Errorf("failed to modify user primary email: %w", err)
}
}
return nil
}
@ -448,9 +516,9 @@ func (bridge *Bridge) addUserWithVault(
client,
bridge.reporter,
apiUser,
bridge.crashHandler,
bridge.vault.SyncWorkers(),
bridge.panicHandler,
bridge.vault.GetShowAllMail(),
bridge.vault.GetMaxSyncMemory(),
)
if err != nil {
return fmt.Errorf("failed to create user: %w", err)
@ -504,7 +572,7 @@ func (bridge *Bridge) newVaultUser(
saltedKeyPass []byte,
) (*vault.User, bool, error) {
if !bridge.vault.HasUser(apiUser.ID) {
user, err := bridge.vault.AddUser(apiUser.ID, apiUser.Name, authUID, authRef, saltedKeyPass)
user, err := bridge.vault.AddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
if err != nil {
return nil, false, fmt.Errorf("failed to add user to vault: %w", err)
}
@ -550,11 +618,17 @@ func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI,
}
// getUserInfo returns information about a disconnected user.
func getUserInfo(userID, username string, state UserState, addressMode vault.AddressMode) UserInfo {
func getUserInfo(userID, username, primaryEmail string, state UserState, addressMode vault.AddressMode) UserInfo {
var addresses []string
if len(primaryEmail) > 0 {
addresses = []string{primaryEmail}
}
return UserInfo{
State: state,
UserID: userID,
Username: username,
Addresses: addresses,
AddressMode: addressMode,
}
}

View File

@ -0,0 +1,852 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge_test
import (
"context"
"fmt"
"net"
"net/http"
"net/mail"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/bradenaw/juniper/stream"
"github.com/bradenaw/juniper/xslices"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestBridge_User_RefreshEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
require.NoError(t, err)
var messageIDs []string
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
})
// Remove a message
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.DeleteMessage(ctx, messageIDs[0]))
})
require.NoError(t, s.RefreshUser(userID, proton.RefreshMail))
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).MinTimes(1)
require.Equal(t, userID, (<-syncCh).UserID)
closeCh()
userContinueEventProcess(ctx, t, s, bridge)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, labelID, 10)
})
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_BadMessage_BadEvent(t *testing.T) {
t.Run("Resync", test_badMessage_badEvent(func(t *testing.T, ctx context.Context, bridge *bridge.Bridge, badUserID string) {
// User feedback is resync
require.NoError(t, bridge.SendBadEventUserFeedback(ctx, badUserID, true))
// Wait for sync to finish
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
require.Equal(t, badUserID, (<-syncCh).UserID)
closeCh()
}))
t.Run("LogoutAndLogin", test_badMessage_badEvent(func(t *testing.T, ctx context.Context, bridge *bridge.Bridge, badUserID string) {
logoutCh, closeCh := chToType[events.Event, events.UserLoggedOut](bridge.GetEvents(events.UserLoggedOut{}))
// User feedback is logout
require.NoError(t, bridge.SendBadEventUserFeedback(ctx, badUserID, false))
require.Equal(t, badUserID, (<-logoutCh).UserID)
closeCh()
// The user will eventually be logged out due to the bad request errors.
require.Eventually(t, func() bool {
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 0
}, 100*user.EventPeriod, user.EventPeriod)
// Login again
_, err := bridge.LoginFull(ctx, "user", password, nil, nil)
require.NoError(t, err)
}))
}
func test_badMessage_badEvent(userFeedback func(t *testing.T, ctx context.Context, bridge *bridge.Bridge, badUserID string)) func(t *testing.T) {
return func(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, labelID, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
var messageIDs []string
// Create 10 more messages for the user, generating events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
})
// If bridge attempts to sync the new messages, it should get a BadRequest error.
doBadRequest := true
s.AddStatusHook(func(req *http.Request) (int, bool) {
if !doBadRequest {
return 0, false
}
if xslices.Index(xslices.Map(messageIDs[0:5], func(messageID string) string {
return "/mail/v4/messages/" + messageID
}), req.URL.Path) < 0 {
return 0, false
}
return http.StatusBadRequest, true
})
badUserID := userReceivesBadError(t, bridge, mocks)
// Remove messages, make response OK again
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.DeleteMessage(ctx, messageIDs[0:5]...))
})
doBadRequest = false
userFeedback(t, ctx, bridge, badUserID)
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
}
func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
_, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
var messageIDs []string
// Create 10 more messages for the user, generating events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
messageIDs = createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
// If bridge attempts to sync the new messages, it should get a BadRequest error.
s.AddStatusHook(func(req *http.Request) (int, bool) {
if strings.Contains(req.URL.Path, "/mail/v4/messages/"+messageIDs[2]) {
return http.StatusUnprocessableEntity, true
}
return 0, false
})
// Remove messages
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.DeleteMessage(ctx, messageIDs...))
})
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_SameMessageLabelCreated_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
var messageIDs []string
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
messageIDs = createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
require.NoError(t, err)
// Add NOOP events
require.NoError(t, s.AddLabelCreatedEvent(userID, labelID))
require.NoError(t, s.AddMessageCreatedEvent(userID, messageIDs[9]))
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_MessageLabelDeleted_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, labelID, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
// Create and delete 10 more messages for the user, generating delete events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
messageIDs := createNumMessages(ctx, t, c, addrID, labelID, 10)
require.NoError(t, c.DeleteMessage(ctx, messageIDs...))
})
// Create and delete 10 labels for the user, generating delete events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
for i := 0; i < 10; i++ {
label, err := c.CreateLabel(ctx, proton.CreateLabelReq{
Name: uuid.NewString(),
Color: "#f66",
Type: proton.LabelTypeLabel,
})
require.NoError(t, err)
require.NoError(t, c.DeleteLabel(ctx, label.ID))
}
})
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
addrID, err = s.CreateAddress(userID, "other@pm.me", password)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.AddAddressCreatedEvent(userID, addrID))
userContinueEventProcess(ctx, t, s, bridge)
})
otherID, err := s.CreateAddress(userID, "another@pm.me", password)
require.NoError(t, err)
require.NoError(t, s.RemoveAddress(userID, otherID))
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.CreateAddressKey(userID, addrID, password))
userContinueEventProcess(ctx, t, s, bridge)
require.NoError(t, s.RemoveAddress(userID, addrID))
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_AddressEventUpdatedForAddressThatDoesNotExist_NoBadEvent(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
_, err := s.CreateAddressAsUpdate(userID, "another@pm.me", password)
require.NoError(t, err)
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_Network_NoBadEvents(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
retVal := int32(0)
setResponseAndWait := func(status int32) {
atomic.StoreInt32(&retVal, status)
time.Sleep(user.EventPeriod)
}
s.AddStatusHook(func(req *http.Request) (int, bool) {
status := atomic.LoadInt32(&retVal)
if strings.Contains(req.URL.Path, "/core/v4/events/") {
return int(status), status != 0
}
return 0, false
})
// Create a user.
_, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
// Create 10 more messages for the user, generating events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
setResponseAndWait(http.StatusInternalServerError)
setResponseAndWait(http.StatusServiceUnavailable)
setResponseAndWait(http.StatusPaymentRequired)
setResponseAndWait(http.StatusForbidden)
setResponseAndWait(http.StatusBadRequest)
setResponseAndWait(http.StatusUnprocessableEntity)
setResponseAndWait(http.StatusTooManyRequests)
time.Sleep(10 * time.Second) // needs minimum of 10 seconds to retry
})
setResponseAndWait(0)
time.Sleep(10 * time.Second) // needs up to 20 seconds to retry
userContinueEventProcess(ctx, t, s, bridge)
})
})
}
func TestBridge_User_DropConn_NoBadEvent(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
dropListener := proton.NewListener(l, proton.NewDropConn)
defer func() { _ = dropListener.Close() }()
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
_, addrID, err := s.CreateUser("user", password)
require.NoError(t, err)
// Create 10 messages for the user.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes()
// Create 10 more messages for the user, generating events.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
})
var count int
// The first 10 times bridge attempts to sync any of the messages, drop the connection.
s.AddStatusHook(func(req *http.Request) (int, bool) {
if strings.Contains(req.URL.Path, "/mail/v4/messages") {
if count++; count < 10 {
dropListener.DropAll()
}
}
return 0, false
})
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
// The IMAP client will eventually see 20 messages.
require.Eventually(t, func() bool {
status, err := client.Status("INBOX", []imap.StatusItem{imap.StatusMessages})
return err == nil && status.Messages == 20
}, 10*time.Second, 100*time.Millisecond)
})
}, server.WithListener(dropListener))
}
func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a bridge user.
_, _, err := s.CreateUser("user", password)
require.NoError(t, err)
// Initially sync the user.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
})
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
user, err := c.GetUser(ctx)
require.NoError(t, err)
addrs, err := c.GetAddresses(ctx)
require.NoError(t, err)
salts, err := c.GetSalts(ctx)
require.NoError(t, err)
keyPass, err := salts.SaltForKey(password, user.Keys.Primary().ID)
require.NoError(t, err)
_, addrKRs, err := proton.Unlock(user, addrs, keyPass, async.NoopPanicHandler{})
require.NoError(t, err)
// Create a draft (generating a "create draft message" event).
draft, err := c.CreateDraft(ctx, addrKRs[addrs[0].ID], proton.CreateDraftReq{
Message: proton.DraftTemplate{
Subject: "subject",
Sender: &mail.Address{Name: "sender", Address: addrs[0].Email},
Body: "body",
MIMEType: rfc822.TextPlain,
},
})
require.NoError(t, err)
// Process those events
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
})
// Update the draft (generating an "update draft message" event).
require.NoError(t, getErr(c.UpdateDraft(ctx, draft.ID, addrKRs[addrs[0].ID], proton.UpdateDraftReq{
Message: proton.DraftTemplate{
Subject: "subject 2",
Sender: &mail.Address{Name: "sender", Address: addrs[0].Email},
Body: "body 2",
MIMEType: rfc822.TextPlain,
},
})))
// Import a message (generating a "create message" event).
str, err := c.ImportMessages(ctx, addrKRs[addrs[0].ID], 1, 1, proton.ImportReq{
Metadata: proton.ImportMetadata{
AddressID: addrs[0].ID,
Flags: proton.MessageFlagReceived,
},
Message: []byte("From: someone@example.com\r\nTo: blabla@example.com\r\n\r\nhello"),
})
require.NoError(t, err)
res, err := stream.Collect(ctx, str)
require.NoError(t, err)
// Process those events.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
})
// Update the imported message (generating an "update message" event).
require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID))
// Process those events.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
})
})
})
}
func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a bridge user.
_, _, err := s.CreateUser("user", password)
require.NoError(t, err)
// Initially sync the user.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userLoginAndSync(ctx, t, bridge, "user", password)
})
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
user, err := c.GetUser(ctx)
require.NoError(t, err)
addrs, err := c.GetAddresses(ctx)
require.NoError(t, err)
salts, err := c.GetSalts(ctx)
require.NoError(t, err)
keyPass, err := salts.SaltForKey(password, user.Keys.Primary().ID)
require.NoError(t, err)
_, addrKRs, err := proton.Unlock(user, addrs, keyPass, async.NoopPanicHandler{})
require.NoError(t, err)
// Create a draft (generating a "create draft message" event).
draft, err := c.CreateDraft(ctx, addrKRs[addrs[0].ID], proton.CreateDraftReq{
Message: proton.DraftTemplate{
Subject: "subject",
ToList: []*mail.Address{{Address: addrs[0].Email}},
Sender: &mail.Address{Name: "sender", Address: addrs[0].Email},
Body: "body",
MIMEType: rfc822.TextPlain,
},
})
require.NoError(t, err)
// Process those events
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
messages, err := clientFetch(client, "Drafts")
require.NoError(t, err)
require.Len(t, messages, 1)
require.Contains(t, messages[0].Flags, imap.DraftFlag)
})
// Send the draft (generating an "update message" event).
{
pubKeys, recType, err := c.GetPublicKeys(ctx, addrs[0].Email)
require.NoError(t, err)
require.Equal(t, recType, proton.RecipientTypeInternal)
var req proton.SendDraftReq
require.NoError(t, req.AddTextPackage(addrKRs[addrs[0].ID], "body", rfc822.TextPlain, map[string]proton.SendPreferences{
addrs[0].Email: {
Encrypt: true,
PubKey: must(crypto.NewKeyRing(must(crypto.NewKeyFromArmored(pubKeys[0].PublicKey)))),
SignatureType: proton.DetachedSignature,
EncryptionScheme: proton.InternalScheme,
MIMEType: rfc822.TextPlain,
},
}, nil))
require.NoError(t, getErr(c.SendDraft(ctx, draft.ID, req)))
}
// Process those events; the draft will move to the sent folder and lose the draft flag.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userContinueEventProcess(ctx, t, s, bridge)
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
messages, err := clientFetch(client, "Sent")
require.NoError(t, err)
require.Len(t, messages, 1)
require.NotContains(t, messages[0].Flags, imap.DraftFlag)
})
})
})
}
func TestBridge_User_DisableEnableAddress(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
// Create an additional address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
require.NoError(t, err)
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
// Initially we should list the address.
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
require.Contains(t, info.Addresses, "alias@"+s.GetDomain())
})
// Disable the address.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.DisableAddress(ctx, aliasID))
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Eventually we shouldn't list the address.
require.Eventually(t, func() bool {
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
return xslices.Index(info.Addresses, "alias@"+s.GetDomain()) < 0
}, 5*time.Second, 100*time.Millisecond)
})
// Enable the address.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.EnableAddress(ctx, aliasID))
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Eventually we should list the address.
require.Eventually(t, func() bool {
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
return xslices.Index(info.Addresses, "alias@"+s.GetDomain()) >= 0
}, 5*time.Second, 100*time.Millisecond)
})
})
}
func TestBridge_User_CreateDisabledAddress(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
// Create a user.
userID, _, err := s.CreateUser("user", password)
require.NoError(t, err)
// Create an additional address for the user.
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
require.NoError(t, err)
// Immediately disable the address.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, c.DisableAddress(ctx, aliasID))
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
// Initially we shouldn't list the address.
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
require.NotContains(t, info.Addresses, "alias@"+s.GetDomain())
})
})
}
func TestBridge_User_HandleParentLabelRename(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
info, err := bridge.QueryUserInfo(username)
require.NoError(t, err)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
withClient(ctx, t, s, username, password, func(ctx context.Context, c *proton.Client) {
parentName := uuid.NewString()
childName := uuid.NewString()
// Create a folder.
parentLabel, err := c.CreateLabel(ctx, proton.CreateLabelReq{
Name: parentName,
Type: proton.LabelTypeFolder,
Color: "#f66",
})
require.NoError(t, err)
// Wait for the parent folder to be created.
require.Eventually(t, func() bool {
return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool {
return mailbox.Name == fmt.Sprintf("Folders/%v", parentName)
}) >= 0
}, 100*user.EventPeriod, user.EventPeriod)
// Create a subfolder.
childLabel, err := c.CreateLabel(ctx, proton.CreateLabelReq{
Name: childName,
Type: proton.LabelTypeFolder,
Color: "#f66",
ParentID: parentLabel.ID,
})
require.NoError(t, err)
require.Equal(t, parentLabel.ID, childLabel.ParentID)
// Wait for the parent folder to be created.
require.Eventually(t, func() bool {
return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool {
return mailbox.Name == fmt.Sprintf("Folders/%v/%v", parentName, childName)
}) >= 0
}, 100*user.EventPeriod, user.EventPeriod)
newParentName := uuid.NewString()
// Rename the parent folder.
require.NoError(t, getErr(c.UpdateLabel(ctx, parentLabel.ID, proton.UpdateLabelReq{
Color: "#f66",
Name: newParentName,
})))
// Wait for the parent folder to be renamed.
require.Eventually(t, func() bool {
return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool {
return mailbox.Name == fmt.Sprintf("Folders/%v", newParentName)
}) >= 0
}, 100*user.EventPeriod, user.EventPeriod)
// Wait for the child folder to be renamed.
require.Eventually(t, func() bool {
return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool {
return mailbox.Name == fmt.Sprintf("Folders/%v/%v", newParentName, childName)
}) >= 0
}, 100*user.EventPeriod, user.EventPeriod)
})
})
})
}
// userLoginAndSync logs in user and waits until user is fully synced.
func userLoginAndSync(
ctx context.Context,
t *testing.T,
bridge *bridge.Bridge,
username string, password []byte, //nolint:unparam
) {
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
defer done()
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
}
func userReceivesBadError(
t *testing.T,
bridge *bridge.Bridge,
mocks *bridge.Mocks,
) (userID string) {
badEventCh, closeCh := bridge.GetEvents(events.UserBadEvent{})
// The user will continue to process events and will receive bad request errors.
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).MinTimes(1)
badEvent, ok := (<-badEventCh).(events.UserBadEvent)
require.True(t, ok)
closeCh()
return badEvent.UserID
}
func userContinueEventProcess(
ctx context.Context,
t *testing.T,
s *server.Server,
bridge *bridge.Bridge,
) {
info, err := bridge.QueryUserInfo("user")
require.NoError(t, err)
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
randomLabel := uuid.NewString()
// Create a new label.
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
require.NoError(t, getErr(c.CreateLabel(ctx, proton.CreateLabelReq{
Name: randomLabel,
Color: "#f66",
Type: proton.LabelTypeLabel,
})))
})
// Wait for the label to be created.
require.Eventually(t, func() bool {
return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool {
return mailbox.Name == "Labels/"+randomLabel
}) >= 0
}, 100*user.EventPeriod, user.EventPeriod)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -21,10 +21,13 @@ import (
"context"
"fmt"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/proton-bridge/v3/internal"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/sirupsen/logrus"
)
func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, event events.Event) error {
@ -34,9 +37,14 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
return fmt.Errorf("failed to handle user address created event: %w", err)
}
case events.UserAddressUpdated:
if err := bridge.handleUserAddressUpdated(ctx, user, event); err != nil {
return fmt.Errorf("failed to handle user address updated event: %w", err)
case events.UserAddressEnabled:
if err := bridge.handleUserAddressEnabled(ctx, user, event); err != nil {
return fmt.Errorf("failed to handle user address enabled event: %w", err)
}
case events.UserAddressDisabled:
if err := bridge.handleUserAddressDisabled(ctx, user, event); err != nil {
return fmt.Errorf("failed to handle user address disabled event: %w", err)
}
case events.UserAddressDeleted:
@ -45,74 +53,113 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
}
case events.UserRefreshed:
if err := bridge.handleUserRefreshed(ctx, user); err != nil {
if err := bridge.handleUserRefreshed(ctx, user, event); err != nil {
return fmt.Errorf("failed to handle user refreshed event: %w", err)
}
case events.UserDeauth:
bridge.handleUserDeauth(ctx, user)
case events.UserBadEvent:
bridge.handleUserBadEvent(ctx, user, event)
case events.UncategorizedEventError:
bridge.handleUncategorizedErrorEvent(event)
}
return nil
}
func (bridge *Bridge) handleUserAddressCreated(ctx context.Context, user *user.User, event events.UserAddressCreated) error {
if user.GetAddressMode() == vault.SplitMode {
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
if user.GetAddressMode() == vault.CombinedMode {
return nil
}
gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey())
if err != nil {
return fmt.Errorf("failed to add user to IMAP server: %w", err)
}
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
if err := user.SetGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to set gluon ID: %w", err)
}
gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey())
if err != nil {
return fmt.Errorf("failed to add user to IMAP server: %w", err)
}
if err := user.SetGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to set gluon ID: %w", err)
}
return nil
}
// GODT-1948: Handle addresses that have been disabled!
func (bridge *Bridge) handleUserAddressUpdated(_ context.Context, user *user.User, _ events.UserAddressUpdated) error {
switch user.GetAddressMode() {
case vault.CombinedMode:
return fmt.Errorf("not implemented")
func (bridge *Bridge) handleUserAddressEnabled(ctx context.Context, user *user.User, event events.UserAddressEnabled) error {
if user.GetAddressMode() == vault.CombinedMode {
return nil
}
case vault.SplitMode:
return fmt.Errorf("not implemented")
gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey())
if err != nil {
return fmt.Errorf("failed to add user to IMAP server: %w", err)
}
if err := user.SetGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to set gluon ID: %w", err)
}
return nil
}
func (bridge *Bridge) handleUserAddressDisabled(ctx context.Context, user *user.User, event events.UserAddressDisabled) error {
if user.GetAddressMode() == vault.CombinedMode {
return nil
}
gluonID, ok := user.GetGluonID(event.AddressID)
if !ok {
return fmt.Errorf("gluon ID not found for address %s", event.AddressID)
}
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
}
if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to remove gluon ID for address: %w", err)
}
return nil
}
func (bridge *Bridge) handleUserAddressDeleted(ctx context.Context, user *user.User, event events.UserAddressDeleted) error {
if user.GetAddressMode() == vault.SplitMode {
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
if user.GetAddressMode() == vault.CombinedMode {
return nil
}
gluonID, ok := user.GetGluonID(event.AddressID)
if !ok {
return fmt.Errorf("gluon ID not found for address %s", event.AddressID)
}
if bridge.imapServer == nil {
return fmt.Errorf("no imap server instance running")
}
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
}
gluonID, ok := user.GetGluonID(event.AddressID)
if !ok {
return fmt.Errorf("gluon ID not found for address %s", event.AddressID)
}
if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to remove gluon ID for address: %w", err)
}
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
}
if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil {
return fmt.Errorf("failed to remove gluon ID for address: %w", err)
}
return nil
}
func (bridge *Bridge) handleUserRefreshed(ctx context.Context, user *user.User) error {
func (bridge *Bridge) handleUserRefreshed(ctx context.Context, user *user.User, event events.UserRefreshed) error {
return safe.RLockRet(func() error {
if event.CancelEventPool {
user.CancelSyncAndEventPoll()
}
if err := bridge.removeIMAPUser(ctx, user, true); err != nil {
return fmt.Errorf("failed to remove IMAP user: %w", err)
}
@ -130,3 +177,34 @@ func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
bridge.logoutUser(ctx, user, false, false)
}, bridge.usersLock)
}
func (bridge *Bridge) handleUserBadEvent(_ context.Context, user *user.User, event events.UserBadEvent) {
safe.Lock(func() {
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle event", reporter.Context{
"user_id": user.ID(),
"old_event_id": event.OldEventID,
"new_event_id": event.NewEventID,
"event_info": event.EventInfo,
"error": event.Error,
"error_type": internal.ErrCauseType(event.Error),
}); rerr != nil {
logrus.WithError(rerr).Error("Failed to report failed event handling")
}
user.CancelSyncAndEventPoll()
// Disable IMAP user
if err := bridge.removeIMAPUser(context.Background(), user, false); err != nil {
logrus.WithError(err).Error("Failed to remove IMAP user")
}
}, bridge.usersLock)
}
func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEventError) {
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle due to uncategorized error", reporter.Context{
"error_type": internal.ErrCauseType(event.Error),
"error": event.Error,
}); rerr != nil {
logrus.WithError(rerr).Error("Failed to report failed event handling")
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -20,6 +20,8 @@ package bridge_test
import (
"context"
"fmt"
"net"
"net/http"
"testing"
"time"
@ -61,6 +63,50 @@ func TestBridge_Login(t *testing.T) {
})
}
func TestBridge_Login_DropConn(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
dropListener := proton.NewListener(l, proton.NewDropConn)
defer func() { _ = dropListener.Close() }()
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user.
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
require.NoError(t, err)
// The user is now connected.
require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
})
// Whether to allow the user to be created.
var allowUser bool
s.AddStatusHook(func(req *http.Request) (int, bool) {
// Drop any request to the users endpoint.
if !allowUser && req.URL.Path == "/core/v4/users" {
dropListener.DropAll()
}
// After the ping request, allow the user to be created.
if req.URL.Path == "/tests/ping" {
allowUser = true
}
return 0, false
})
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// The user is eventually connected.
require.Eventually(t, func() bool {
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1
}, 5*time.Second, 100*time.Millisecond)
})
}, server.WithListener(dropListener))
}
func TestBridge_LoginTwice(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -98,6 +98,8 @@ func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
// Make sure the temporary file is deleted.
go func() {
defer recover() //nolint:errcheck
<-time.After(10 * time.Minute)
_ = os.RemoveAll(dir)
}()

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -44,6 +44,9 @@ var (
// DSNSentry client keys to be able to report crashes to Sentry.
DSNSentry = ""
// BuildEnv tags used at build time.
BuildEnv = ""
)
const (

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -90,7 +90,8 @@ func TestTLSSignedCertWrongPublicKey(t *testing.T) {
r.Error(t, err, "expected dial to fail because of wrong public key")
}
func TestTLSSignedCertTrustedPublicKey(t *testing.T) {
// GODT-2293 bump badssl cert and re enable this.
func _TestTLSSignedCertTrustedPublicKey(t *testing.T) { //nolint:unused,deadcode
skipIfProxyIsSet(t)
_, dialer, _, checker, _ := createClientWithPinningDialer("")

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
@ -24,6 +24,7 @@ import (
"sync"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -40,17 +41,20 @@ type ProxyTLSDialer struct {
allowProxy bool
proxyProvider *proxyProvider
proxyUseDuration time.Duration
panicHandler async.PanicHandler
}
// NewProxyTLSDialer constructs a dialer which provides a proxy-managing layer on top of an underlying dialer.
func NewProxyTLSDialer(dialer TLSDialer, hostURL string) *ProxyTLSDialer {
func NewProxyTLSDialer(dialer TLSDialer, hostURL string, panicHandler async.PanicHandler) *ProxyTLSDialer {
return &ProxyTLSDialer{
dialer: dialer,
locker: sync.RWMutex{},
directAddress: formatAsAddress(hostURL),
proxyAddress: formatAsAddress(hostURL),
proxyProvider: newProxyProvider(dialer, hostURL, DoHProviders),
proxyProvider: newProxyProvider(dialer, hostURL, DoHProviders, panicHandler),
proxyUseDuration: proxyUseDuration,
panicHandler: panicHandler,
}
}
@ -129,6 +133,8 @@ func (d *ProxyTLSDialer) switchToReachableServer() error {
// This means we want to disable it again in 24 hours.
if d.proxyAddress == d.directAddress {
go func() {
defer async.HandlePanic(d.panicHandler)
<-time.After(d.proxyUseDuration)
d.locker.Lock()

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -24,6 +24,7 @@ import (
"sync"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/go-resty/resty/v2"
"github.com/miekg/dns"
"github.com/pkg/errors"
@ -67,11 +68,13 @@ type proxyProvider struct {
canReachTimeout time.Duration
lastLookup time.Time // The time at which we last attempted to find a proxy.
panicHandler async.PanicHandler
}
// newProxyProvider creates a new proxyProvider that queries the given DoH providers
// to retrieve DNS records for the given query string.
func newProxyProvider(dialer TLSDialer, hostURL string, providers []string) (p *proxyProvider) {
func newProxyProvider(dialer TLSDialer, hostURL string, providers []string, panicHandler async.PanicHandler) (p *proxyProvider) {
p = &proxyProvider{
dialer: dialer,
hostURL: hostURL,
@ -80,6 +83,7 @@ func newProxyProvider(dialer TLSDialer, hostURL string, providers []string) (p *
cacheRefreshTimeout: proxyCacheRefreshTimeout,
dohTimeout: proxyDoHTimeout,
canReachTimeout: proxyCanReachTimeout,
panicHandler: panicHandler,
}
// Use the default DNS lookup method; this can be overridden if necessary.
@ -109,11 +113,13 @@ func (p *proxyProvider) findReachableServer() (proxy string, err error) {
wg.Add(2)
go func() {
defer async.HandlePanic(p.panicHandler)
defer wg.Done()
apiReachable = p.canReach(p.hostURL)
}()
go func() {
defer async.HandlePanic(p.panicHandler)
defer wg.Done()
err = p.refreshProxyCache()
}()
@ -150,6 +156,8 @@ func (p *proxyProvider) refreshProxyCache() error {
resultChan := make(chan []string)
go func() {
defer async.HandlePanic(p.panicHandler)
for _, provider := range p.providers {
if proxies, err := p.dohLookup(ctx, p.query, provider); err == nil {
resultChan <- proxies
@ -203,6 +211,7 @@ func (p *proxyProvider) defaultDoHLookup(ctx context.Context, query, dohProvider
dataChan, errChan := make(chan []string), make(chan error)
go func() {
defer async.HandlePanic(p.panicHandler)
// Build new DNS request in RFC1035 format.
dnsRequest := new(dns.Msg).SetQuestion(dns.Fqdn(query), dns.TypeTXT)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -23,6 +23,7 @@ import (
"testing"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
r "github.com/stretchr/testify/require"
)
@ -31,7 +32,7 @@ func TestProxyProvider_FindProxy(t *testing.T) {
proxy := getTrustedServer()
defer closeServer(proxy)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy.URL}, nil }
url, err := p.findReachableServer()
@ -47,7 +48,7 @@ func TestProxyProvider_FindProxy_ChooseReachableProxy(t *testing.T) {
unreachableProxy := getTrustedServer()
closeServer(unreachableProxy)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
return []string{reachableProxy.URL, unreachableProxy.URL}, nil
}
@ -68,7 +69,7 @@ func TestProxyProvider_FindProxy_ChooseTrustedProxy(t *testing.T) {
checker := NewTLSPinChecker(TrustedAPIPins)
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
p := newProxyProvider(dialer, "", []string{"not used"})
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
return []string{untrustedProxy.URL, trustedProxy.URL}, nil
}
@ -85,7 +86,7 @@ func TestProxyProvider_FindProxy_FailIfNoneReachable(t *testing.T) {
unreachableProxy2 := getTrustedServer()
closeServer(unreachableProxy2)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
return []string{unreachableProxy1.URL, unreachableProxy2.URL}, nil
}
@ -105,7 +106,7 @@ func TestProxyProvider_FindProxy_FailIfNoneTrusted(t *testing.T) {
checker := NewTLSPinChecker(TrustedAPIPins)
dialer := NewPinningTLSDialer(NewBasicTLSDialer(""), reporter, checker)
p := newProxyProvider(dialer, "", []string{"not used"})
p := newProxyProvider(dialer, "", []string{"not used"}, async.NoopPanicHandler{})
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) {
return []string{untrustedProxy1.URL, untrustedProxy2.URL}, nil
}
@ -115,7 +116,7 @@ func TestProxyProvider_FindProxy_FailIfNoneTrusted(t *testing.T) {
}
func TestProxyProvider_FindProxy_RefreshCacheTimeout(t *testing.T) {
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
p.cacheRefreshTimeout = 1 * time.Second
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { time.Sleep(2 * time.Second); return nil, nil }
@ -132,7 +133,7 @@ func TestProxyProvider_FindProxy_CanReachTimeout(t *testing.T) {
}))
defer closeServer(slowProxy)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"not used"}, async.NoopPanicHandler{})
p.canReachTimeout = 1 * time.Second
p.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{slowProxy.URL}, nil }
@ -144,7 +145,7 @@ func TestProxyProvider_FindProxy_CanReachTimeout(t *testing.T) {
}
func TestProxyProvider_DoHLookup_Quad9(t *testing.T) {
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
records, err := p.dohLookup(context.Background(), proxyQuery, Quad9Provider)
r.NoError(t, err)
@ -155,7 +156,7 @@ func TestProxyProvider_DoHLookup_Quad9(t *testing.T) {
// port filter. Basic functionality should be covered by other tests. Keeping
// code here to be able to run it locally if needed.
func DISABLEDTestProxyProviderDoHLookupQuad9Port(t *testing.T) {
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
records, err := p.dohLookup(context.Background(), proxyQuery, Quad9PortProvider)
r.NoError(t, err)
@ -163,7 +164,7 @@ func DISABLEDTestProxyProviderDoHLookupQuad9Port(t *testing.T) {
}
func TestProxyProvider_DoHLookup_Google(t *testing.T) {
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
records, err := p.dohLookup(context.Background(), proxyQuery, GoogleProvider)
r.NoError(t, err)
@ -173,7 +174,7 @@ func TestProxyProvider_DoHLookup_Google(t *testing.T) {
func TestProxyProvider_DoHLookup_FindProxy(t *testing.T) {
skipIfProxyIsSet(t)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
url, err := p.findReachableServer()
r.NoError(t, err)
@ -183,7 +184,7 @@ func TestProxyProvider_DoHLookup_FindProxy(t *testing.T) {
func TestProxyProvider_DoHLookup_FindProxyFirstProviderUnreachable(t *testing.T) {
skipIfProxyIsSet(t)
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"https://unreachable", Quad9Provider, GoogleProvider})
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{"https://unreachable", Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
url, err := p.findReachableServer()
r.NoError(t, err)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -25,6 +25,7 @@ import (
"testing"
"time"
"github.com/ProtonMail/gluon/async"
"github.com/stretchr/testify/require"
)
@ -141,8 +142,8 @@ func TestProxyDialer_UseProxy(t *testing.T) {
trustedProxy := getTrustedServer()
defer closeServer(trustedProxy)
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders)
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "")
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
d.proxyProvider = provider
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{trustedProxy.URL}, nil }
@ -159,8 +160,8 @@ func TestProxyDialer_UseProxy_MultipleTimes(t *testing.T) {
proxy3 := getTrustedServer()
defer closeServer(proxy3)
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders)
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "")
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
d.proxyProvider = provider
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy1.URL}, nil }
@ -189,8 +190,8 @@ func TestProxyDialer_UseProxy_RevertAfterTime(t *testing.T) {
trustedProxy := getTrustedServer()
defer closeServer(trustedProxy)
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders)
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "")
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
d.proxyProvider = provider
d.proxyUseDuration = time.Second
@ -212,8 +213,8 @@ func TestProxyDialer_UseProxy_RevertAfterTime(t *testing.T) {
func TestProxyDialer_UseProxy_RevertIfProxyStopsWorkingAndOriginalAPIIsReachable(t *testing.T) {
trustedProxy := getTrustedServer()
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders)
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "")
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
d.proxyProvider = provider
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{trustedProxy.URL}, nil }
@ -242,8 +243,8 @@ func TestProxyDialer_UseProxy_FindSecondAlternativeIfFirstFailsAndAPIIsStillBloc
proxy2 := getTrustedServer()
defer closeServer(proxy2)
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders)
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "")
provider := newProxyProvider(NewBasicTLSDialer(""), "", DoHProviders, async.NoopPanicHandler{})
d := NewProxyTLSDialer(NewBasicTLSDialer(""), "", async.NoopPanicHandler{})
d.proxyProvider = provider
provider.dohLookup = func(ctx context.Context, q, p string) ([]string, error) { return []string{proxy1.URL, proxy2.URL}, nil }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

38
internal/errors.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package internal
import (
"errors"
"fmt"
)
// ErrCause returns the cause of the error, the inner-most error in the wrapped chain.
func ErrCause(err error) error {
cause := err
for errors.Unwrap(cause) != nil {
cause = errors.Unwrap(cause)
}
return cause
}
func ErrCauseType(err error) string {
return fmt.Sprintf("%T", ErrCause(err))
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -35,6 +35,30 @@ func (event UserAddressCreated) String() string {
return fmt.Sprintf("UserAddressCreated: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email))
}
type UserAddressEnabled struct {
eventBase
UserID string
AddressID string
Email string
}
func (event UserAddressEnabled) String() string {
return fmt.Sprintf("UserAddressEnabled: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email))
}
type UserAddressDisabled struct {
eventBase
UserID string
AddressID string
Email string
}
func (event UserAddressDisabled) String() string {
return fmt.Sprintf("UserAddressDisabled: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email))
}
type UserAddressUpdated struct {
eventBase

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -52,9 +52,8 @@ type UserLabelDeleted struct {
UserID string
LabelID string
Name string
}
func (event UserLabelDeleted) String() string {
return fmt.Sprintf("UserLabelDeleted: UserID: %s, LabelID: %s, Name: %s", event.UserID, event.LabelID, logging.Sensitive(event.Name))
return fmt.Sprintf("UserLabelDeleted: UserID: %s, LabelID: %s", event.UserID, event.LabelID)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

76
internal/events/serve.go Normal file
View File

@ -0,0 +1,76 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package events
import "fmt"
type IMAPServerReady struct {
eventBase
Port int
}
func (event IMAPServerReady) String() string {
return fmt.Sprintf("IMAPServerReady: Port %d", event.Port)
}
type IMAPServerStopped struct {
eventBase
}
func (event IMAPServerStopped) String() string {
return "IMAPServerStopped"
}
type IMAPServerError struct {
eventBase
Error error
}
func (event IMAPServerError) String() string {
return fmt.Sprintf("IMAPServerError: %v", event.Error)
}
type SMTPServerReady struct {
eventBase
Port int
}
func (event SMTPServerReady) String() string {
return fmt.Sprintf("SMTPServerReady: Port %d", event.Port)
}
type SMTPServerStopped struct {
eventBase
}
func (event SMTPServerStopped) String() string {
return "SMTPServerStopped"
}
type SMTPServerError struct {
eventBase
Error error
}
func (event SMTPServerError) String() string {
return fmt.Sprintf("SMTPServerError: %v", event.Error)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

View File

@ -1,4 +1,4 @@
// Copyright (c) 2022 Proton AG
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//

Some files were not shown because too many files have changed in this diff Show More