Compare commits

..

890 Commits

Author SHA1 Message Date
ba712516ff Other: Bridge Millau 2.2.0 2022-05-18 13:05:54 +02:00
865ac44037 GODT-1508: Splash screen for rebranding.
* Use old keychain until manual update.
* Improve desktop files migration for windows and linux.
* Revert, need admin rights to change desktop and start menu files.
2022-05-18 11:25:48 +02:00
7d41062ae9 GODT-1542: Update login screen for rebranding. 2022-05-18 11:23:43 +02:00
f3c69faf8b GODT-1260: Renaming
* Renaming GUI, CLI, no-impact config.
* License header and documentation rebranding.
* Rename app title and vendor. Impact: manual install
* Migrating mac keychain and launch on startup.
* Fix linter and linter renaming
2022-05-18 11:23:38 +02:00
e353dc554d GODT-1502: Rebranding: color and radius. 2022-05-18 11:19:33 +02:00
415d08b411 GODT-1549: Add notification when address list changes. 2022-05-18 11:17:03 +02:00
62499a5630 Other: update dependencies 2022-05-18 11:06:24 +02:00
d6d7ea592e GODT-1560: Dependecy licenses update and link. 2022-05-18 10:54:16 +02:00
5033e9718c GODT-1543: Using one buffered event for off and on connection 2022-05-17 15:25:19 +02:00
16f9dc43cb GODT-1534: Reset address when leaving split mode. 2022-05-17 08:35:49 +00:00
b8f27cc7d2 GODT-1550: Update dependencies 2022-05-16 09:23:16 +00:00
6b10da524c GODT-1545 GODT-1521: Change wording and enable release notes link. 2022-04-13 14:22:57 +02:00
51eb2c42cd Other: Bridge London 2.1.3 2022-04-08 07:34:19 +02:00
c94d839fbb Other: debug mac CI 2022-04-07 16:04:23 +00:00
de586e5f12 GODT-1527: Change bug report description. 2022-04-07 16:04:23 +00:00
c32a106898 GODT-1525: Add keybase/go-keychain/secretservice as new keychain helper. 2022-04-06 11:49:27 +00:00
5b20b6a3d0 GODT-1537: Manual in-app update mechanism. 2022-04-04 11:54:34 +02:00
3b07121f08 Other: temporary disable mac build 2022-03-30 14:45:51 +02:00
a53bc4b027 Other: Bridge London 2.1.2 2022-03-24 09:44:05 +01:00
478345e277 GODT-1522: Rebuild macOS keychain notification 2022-03-24 09:08:34 +01:00
0ed78f1ccb GODT-1524: Fix tests. 2022-03-24 08:33:58 +01:00
6671dd38ea GODT-1524: Logout issues with macOS. 2022-03-23 09:50:17 +01:00
2d5ea669a5 GODT-1437 Add new proxy provider (Quad9 with port). 2022-03-21 11:13:29 +00:00
c7eb7234a2 GODT-1507: Enable autostart after Qt setup. 2022-03-07 17:01:18 +01:00
73d1fe2f65 GODT-1516 GODT-1451: KeepassXC is crashing on start. We need to block it until it's fixed. 2022-03-01 14:17:11 +00:00
cf75ea739f GODT-1516: Return notification on missing keychain 2022-03-01 14:17:11 +00:00
c920c53243 GODT-1451: Do not check for gnome keyring to allow other implementations of secret-service API. Thanks to @remgodow. 2022-03-01 14:17:11 +00:00
63379001e3 GODT-1515: Do not crash when bridge users got disconnected. 2022-02-22 16:44:45 +01:00
aa8cc3fc4b GODT-1503 GODT-1492: Improve email validation and username in bug report. 2022-02-21 13:34:57 +01:00
61e4ca5814 Other: Bridge London 2.1.1 2022-02-02 17:16:48 +01:00
8e0693ab03 GODT-1499: Remove message from DB once is not on server any more 2022-01-31 09:40:23 +00:00
a3d2df9d38 GODT-1376: Add first userID to sentry scope. 2022-01-20 12:45:53 +01:00
f9f4ce996d GODT-1375: Add host architecture to sentry reports 2022-01-18 09:52:57 +00:00
fc69b9aabb GODT-1364: Add windows CI machine for tests, and build 2022-01-17 14:49:51 +00:00
f7ed3abcfe Other: Bridge London 2.1.0 2022-01-14 11:20:50 +01:00
c2ffae3799 GODT-1494: Update GopenPGP to 2.4.1 2022-01-14 10:20:51 +01:00
437b7a4cfe GODT-1478: Add GUI settings for keychain selection. 2022-01-11 10:18:14 +01:00
df601ecbbd GODT-1482: Comment or mitigate panics, unlock cache when needed. 2022-01-11 08:14:45 +00:00
e9c05c5a6b GODT-1490: Update go sentry and openpgp 2022-01-10 15:48:46 +01:00
22f427d522 GODT-1474: Optimising live integration tests
- pkg/pmapi: Reduce max number of retries
- test/features: tweak create mailbox scenarios.
- test/context: change order of clean up steps
- test/context: logger field
- test/context: message preparation per username
- test/context: check that eventID has changed after adding messages
- test/features: make sure we wait 15sec before detecting import duplicates
2022-01-07 09:56:06 +01:00
d356f306d9 Other: Change copyright year 2022-01-04 11:04:30 +01:00
e717b69139 GODT-1483: correct scope in sentry report 2021-12-21 10:10:21 +00:00
1dbd37d05b GODT-1481: Always turn off non-encrypted recipient report 2021-12-21 09:55:18 +00:00
7dad6cc9a4 GODT-1477: Change CoD wording 2021-12-21 06:55:00 +00:00
0332a3f873 GODT-1329: Dark mode, with macOS autodetect. 2021-12-16 14:33:52 +00:00
20a0404efb Other: release notes: rename: early -> beta. 2021-12-14 16:39:31 +01:00
29b7530ddf Other: release notes 2021-12-14 16:39:16 +01:00
27e7d7967d Other: Bridge Kwai 2.0.1 2021-12-14 09:24:07 +01:00
d40fbda2ab GODT-1468: Fix main windows status and add background context without retry. 2021-12-13 15:48:02 +01:00
3bb9146d9f Other: Bridge Kwai 2.0.0 2021-12-10 11:58:23 +01:00
f0d05aeb79 GODT-1458: Splash screen and wording. 2021-12-10 10:27:53 +00:00
ad6b84d4e0 GODT-1460 GODT-1462: Adding delete account dialog and fixing status view brief and icon. 2021-12-10 10:13:06 +00:00
38031b2fb9 GODT-1459: Wording. 2021-12-10 09:39:06 +00:00
b74dba884f GODT-1456: Make text selectable and clickable 2021-12-10 09:22:53 +00:00
7276c23b2b GODT-1438: Turn off SW OpenGL on windows and add debug info about graphic renderer. 2021-12-10 09:08:09 +00:00
f0b1ab55a2 GODT-1455 Adding links to setup guide. 2021-12-08 14:35:35 +01:00
f851f4d5c2 GODT-1428: Fix welcome illustration by using PNG. 2021-12-06 08:11:09 +00:00
f2d568d92f GODT-1425: Factory reset enables launch on startup 2021-12-06 07:56:25 +00:00
a0dc764bb9 GODT-1433 Message.Type is deprecated, use Flags instead. 2021-12-01 10:25:12 +01:00
55beb9227f GODT-1433 Adding first integration test for drafts. 2021-11-30 17:02:14 +01:00
6ed97a0347 GODT-1433: Do not save message to cache if it's a draft.
- remove: deprecated SetContentTypeAndHeader
- fix: write metada unit test
- change: do not cache body for Drafts
- change: do not cache body structure (header) for Drafts
- change: do not cache body size for Drafts
2021-11-30 11:36:27 +01:00
7ce3529f5d GODT-1442: Fix "Sign In" button 2021-11-30 11:32:47 +01:00
ed9edb3620 GODT-1431 Do not cache message during new message event when CoD is off. 2021-11-30 11:29:39 +01:00
f30269865d GODT-1381 Treat readonly folder as failure for cache on disk. 2021-11-30 11:29:39 +01:00
d7c5ace8e4 GODT-1431 Prevent watcher when not using disk on cache
- change: Rename Cacher -> MsgCachePool
- change: Do not run watcher when using memory cache
- add: Allow to cancel cacher jobs (added context)
- change: Fix behavior on context cancel (was causing no internet)
2021-11-30 11:29:39 +01:00
b82e2ca176 GODT-1381: Use in-memory cache in case local cache is unavailable
- change: refactor GUI notification object
- add: global bridge errors
- add: when cache on disk cannot be initialized fallback to memory cache
- add: show notification for CoD failure
- change: do not allow login to IMAP and SMTP when CoD init failed
2021-11-30 11:29:39 +01:00
5af3e930ec GODT-1391: Fix color for avatar text 2021-11-30 10:48:41 +01:00
72cd641c72 GODT-1391: Fix link colors across GUI 2021-11-30 10:48:41 +01:00
4a2ac813d3 GODT-1325: Add "already logged in" notification 2021-11-30 10:15:36 +01:00
961742fa53 GODT-1366: Simple lookup of index and select current user 2021-11-30 10:15:36 +01:00
9984165798 GODT-1226: Fix status window position 2021-11-30 10:15:36 +01:00
551f5c3c18 GODT-1412: Refactor paths and links 2021-11-30 10:15:36 +01:00
41f2ffa4ec GODT-1327: Reset cache path to default when disabling 2021-11-30 10:15:36 +01:00
778b17c44e Other: Fix typo accounts(s) -> account(s) 2021-11-30 10:15:36 +01:00
5d51cc1739 Other: Fix typo SMPT -> SMTP 2021-11-30 10:15:36 +01:00
a7270102af Other: bump facelift version 2021-11-30 10:15:35 +01:00
ca5c45b1c5 Other: make setup guide not transparent 2021-11-30 10:14:30 +01:00
31920a4468 GODT-1411: refactor SettingView content to fill height 2021-11-30 10:14:30 +01:00
2e52b8db87 GODT-1351: Fix used size update from mail operations 2021-11-30 10:14:30 +01:00
2899e7bb15 GODT-1351: Cache and update of space bytes in user object. 2021-11-30 10:14:28 +01:00
41e15db442 GODT-1244: Refactor switching stable-early and factory reset 2021-11-30 10:12:36 +01:00
b5b477a3ce GODT-1316: Set default TextArea and TextField behavior 2021-11-30 10:12:36 +01:00
07b7fa7364 GODT-1389: Fix buttons and banner layout 2021-11-30 10:12:36 +01:00
5637ca2529 GODT-1251: Fix change SMTP settings 2021-11-30 10:12:36 +01:00
a93a8e7be9 GODT-1356 GODT-1302: Cache on disk concurency and API retries
- GODT-1302: Change maximum resty retries from 0 to 30
- GODT-1302: Make sure we are closing GetAttachmen io.ReadCloser on error
- GODT-1356: Do not use attachmentPool - it was useless anyway
- GODT-1356: Increase cache watcher limit to 10min
- GODT-1356: Start cache watcher right after start (do not wait first 10 min)
- GODT-1356: Limit number of buildJobs (memory allocation) in BuildAndCacheMessage
- Other: Pass context from job options (message builder) to fetcher (both message and attachments)
- Other: BuildJob contains same function as returned buildDone (proper map locking)
2021-11-30 10:12:36 +01:00
db7ead3901 GODT-1390: Fix autostart toggle 2021-11-30 10:12:36 +01:00
42ced6694e GODT-1388: Refactor Alternative routing 2021-11-30 10:12:36 +01:00
af0c5e6bae GODT-1378: varia GUI fixes 2021-11-30 10:12:36 +01:00
cf6ed81a00 Other: fix popup behaviour 2021-11-30 10:12:36 +01:00
8c9d5c54fc Other: Fix activeFocus color for components 2021-11-30 10:12:36 +01:00
36ec9b07e0 Other: Override roleNames
This enables to use userModel inside Repeater and other model-based
components together with modelData attached property.
2021-11-30 10:12:36 +01:00
d9847ddd6a GODT-1385: Fix port setting 2021-11-30 10:12:36 +01:00
e1747357bc GODT-1384: Fix SettingsView scroll 2021-11-30 10:12:36 +01:00
77e352a101 GODT-1332 Added tests for cache move functions. 2021-11-30 10:12:36 +01:00
b41c4d2fa6 GODT-1332: moved cache related functions to separate file.
When migrating cache, closing of stored is delayed until the last moment.
2021-11-30 10:12:36 +01:00
b6ad1fe490 GODT-1332 moving cache does not work on Windows. 2021-11-30 10:12:36 +01:00
bc21bb1d8d GODT-1367: use waitgroup instead of channel in pool/pchan 2021-11-30 10:12:36 +01:00
8ea610c625 GODT-1367: Use sync.Once to only close pool jobs once 2021-11-30 10:12:36 +01:00
10da4f284c GODT-1272: Ultimate fix for MacOS transparency 2021-11-30 10:12:36 +01:00
e49d2e1be7 GODT-175: Add option to attach logs for bug reports 2021-11-30 10:12:36 +01:00
b259de238e GODT-1336: Fix showing window on startup 2021-11-30 10:12:36 +01:00
ea821b1bd8 GODT-1272: Fix status view layout 2021-11-30 10:12:36 +01:00
94347d95df GODT-1358: Fix wording 2021-11-30 10:12:36 +01:00
ef051d5ed6 GODT-1369: Fix link render and wording in Help view 2021-11-30 10:12:36 +01:00
e40e8e3e75 GODT-1250: Fix Port settings wording 2021-11-30 10:12:36 +01:00
9d0368de97 GODT-1314: Limit description field length within 150/800 bounds 2021-11-30 10:12:36 +01:00
c5699700b3 GODT-1210: "free user" banner 2021-11-30 10:12:36 +01:00
1141ea27e2 GODT-1320: Add loading property to each action within a notification 2021-11-30 10:12:36 +01:00
ecc1c34b16 GODT-1271: fix Status margings 2021-11-30 10:12:36 +01:00
3601adcae6 Other: small fixes on devel-facelift 2021-11-30 10:12:36 +01:00
d11cf57879 GODT-1346: GODT-1340 GODT-1315 QML changes
GODT-1365: Create ComboBox component
GODT-1338: GODT-1343 Help view buttons
GODT-1340: Not crashing, user list updating in main thread.
GODT-1345: adding panic handlers
2021-11-30 10:12:36 +01:00
2c8feff97a GODT-1317 Use large png for systray and mark it as mask. 2021-11-30 10:12:36 +01:00
b7adccf651 GODT-1319: Set sourceSize everywhere for images 2021-11-30 10:12:36 +01:00
726c8918ab Other: reactive show on startup 2021-11-30 10:12:36 +01:00
29af8e7178 GODT-1349: Change cache-related settings when enabling/disabling/moving cache 2021-11-30 10:12:36 +01:00
18257f0302 GODT-1350: stop cacher/worker properly when logging out user 2021-11-30 10:12:36 +01:00
aeceb7d593 GODT-1298: signal GUI is ready and rise window 2021-11-30 10:12:36 +01:00
85c06809d2 GODT-1158: adding cache on disk signals 2021-11-30 10:12:36 +01:00
f65e050588 GODT-1051: Factory reset button 2021-11-30 10:12:36 +01:00
e0d07d67a0 GODT-22: Frontend-backend
- GODT-1246 Implement settings view.
- GODT-1257 GODT-1246: Account and Help view
- GODT-1298: Minimal working build (panics)
- GODT-1298: loading QML (needs Popup window)
- GODT-1298: WARN: Adding PopupWindow not possible!
    In therecipe qt the `quickwidgets` classes are within `quick` module, but
    forgot to add library and include paths into cgo flags. Therefore
    compilation fails and it would be hard to patch therecipe in order to
    fix it.

    I am not sure if rewrite PopupWindow into go would make any difference,
    therefore I decided to use normal QML Window without borders.
- GODT-1298: Rework status window, add backend props, slots and signals.
- GODT-1298: Users
- GODT-1298: Login
- GODT-1298: WIP Help and bug report
- GODT-1178: MacOS dock icon control
- GODT-1298: Help, bug report, update and events
- GODT-1298: Apple Mail config and Settings (without cache on disk)
2021-11-30 10:12:36 +01:00
0a9748a15d GODT-22: Facelift
- GODT-1199: Add menu to status window
- GODT-22: use ColorImage instead of IconLabel
- GODT-22: remove banners from MainWindow
- GODT-1199: Fix separator width
- GODT-1199: Fix StatusWindow button position
- GODT-1198: Open main window on startup if no users
- GODT-1199: Fix avatar text color
- GODT-1198: refactor main window layout
- GODT-22: add missing components to qmldir
- GODT-22: refactor components having Layout as root item
- GODT-22: add more user controls
- GODT-1199: Add status window resize and maximum height
- GODT-22: WIP: notification arch
- GODT-22: Notifications WIP
- GODT-22: Fix notification filter, topmost notification
- GODT-1199: Add notifications to status window
- GODT-22: Add strict typization to colorScheme variable
- GODT-1198: WIP Notifications, dialogs and banners
- GODT-22: Add backend notifications (Banners & Dialogs)

D
2021-11-30 10:12:36 +01:00
6bd0739013 GODT-1158: Store full messages bodies on disk
- GODT-1158: simple on-disk cache in store
- GODT-1158: better member naming in event loop
- GODT-1158: create on-disk cache during bridge setup
- GODT-1158: better job options
- GODT-1158: rename GetLiteral to GetRFC822
- GODT-1158: rename events -> currentEvents
- GODT-1158: unlock cache per-user
- GODT-1158: clean up cache after logout
- GODT-1158: randomized encrypted cache passphrase
- GODT-1158: Opt out of on-disk cache in settings
- GODT-1158: free space in cache
- GODT-1158: make tests compile
- GODT-1158: optional compression
- GODT-1158: cache custom location
- GODT-1158: basic capacity checker
- GODT-1158: cache free space config
- GODT-1158: only unlock cache if pmapi client is unlocked as well
- GODT-1158: simple background sync worker
- GODT-1158: set size/bodystructure when caching message
- GODT-1158: limit store db update blocking with semaphore
- GODT-1158: dumb 10-semaphore
- GODT-1158: properly handle delete; remove bad bodystructure handling
- GODT-1158: hacky fix for caching after logout... baaaaad
- GODT-1158: cache worker
- GODT-1158: compute body structure lazily
- GODT-1158: cache size in store
- GODT-1158: notify cacher when adding to store
- GODT-1158: 15 second store cache watcher
- GODT-1158: enable cacher
- GODT-1158: better cache worker starting/stopping
- GODT-1158: limit cacher to less concurrency than disk cache
- GODT-1158: message builder prio + pchan pkg
- GODT-1158: fix pchan, use in message builder
- GODT-1158: no sem in cacher (rely on message builder prio)
- GODT-1158: raise priority of existing jobs when requested
- GODT-1158: pending messages in on-disk cache
- GODT-1158: WIP just a note about deleting messages from disk cache
- GODT-1158: pending wait when trying to write
- GODT-1158: pending.add to return bool
- GODT-1225: Headers in bodystructure are stored as bytes.
- GODT-1158: fixing header caching
- GODT-1158: don't cache in background
- GODT-1158: all concurrency set in settings
- GODT-1158: worker pools inside message builder
- GODT-1158: fix linter issues
- GODT-1158: remove completed builds from builder
- GODT-1158: remove builder pool
- GODT-1158: cacher defer job done properly
- GODT-1158: fix linter
- GODT-1299: Continue with bodystructure build if deserialization failed
- GODT-1324: Delete messages from the cache when they are deleted on the server
- GODT-1158: refactor cache tests
- GODT-1158: move builder to app/bridge
- GODT-1306: Migrate cache on disk when location is changed (and delete when disabled)
2021-11-30 10:12:36 +01:00
5cb893fc1b GODT-1179 GODT-658: Components and login flows 2021-11-30 10:12:36 +01:00
f5624c9932 GODT-1051: Add factory reset to bridge object
- remove deleted test no_internet.feature
- clean up old remnants of import export
- FactoryReset docstring
2021-11-30 10:12:36 +01:00
2b1daa60bb GODT-1167 GODT-1179 make run-qml-preview 2021-11-30 10:12:36 +01:00
ffb18adfd0 GODT-1177: remove Import-Export from repo 2021-11-30 10:12:33 +01:00
649195cc2b GODT-1168 GODT-1169 Facelift: it has begun, qml artifacts for preview 2021-11-30 10:11:33 +01:00
b0ce46ca8a Other: Bridge James v1.8.12 2021-11-29 06:37:57 +01:00
6435f7b09a GODT-1432: Check if keys are active before unlocking 2021-11-23 18:48:49 +01:00
59075f2e26 GODT-1409 Wait in GetEvents during message preparation for live API. 2021-11-19 10:21:14 +01:00
1d9855a190 Other: Bridge James 1.8.11 2021-11-12 14:06:04 +01:00
cea33bebe2 GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
This is regression of GODT-963
2021-11-09 10:32:58 +00:00
9d405a1549 GODT-1397: Update bbolt to v1.3.6 2021-11-09 10:01:08 +00:00
47f468e4b7 GODT-1410: Remove event ID from sentry report description 2021-11-08 16:05:34 +00:00
b9c6c00709 GODT-1395: CI should fail on go.sum changed. 2021-11-05 06:12:38 +01:00
5ce9cb8eec GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append. 2021-11-01 16:32:28 +01:00
bc7133e401 Merge branch 'master' into devel 2021-10-21 13:26:29 +02:00
a219ecf3cb Other: fix go.sum 2021-10-21 13:25:44 +02:00
8061b1e6fa GODT-1392: added unit test for get message 2021-10-15 14:01:04 +02:00
6509df523f GODT-1360: added extra check.
This should help diagnostic test failure next time it happens.
2021-10-08 09:01:02 +00:00
0d1abaec0d Other: fixed new copy test feature. 2021-10-07 11:31:56 +00:00
4d1ace5de7 Other: Copy from All Mail allowed again.
Creates duplicate.
Added test scenario.
2021-10-07 11:31:56 +00:00
1250621a4d GODT-963 STORE removing junk or adding nojunk should move message to inbox 2021-10-07 11:31:56 +00:00
8d6e55ba54 GODT-965 MOVE command should end with error for All Mail 2021-10-07 11:31:56 +00:00
a4a29cbf82 Other Improved tests in move_without_support 2021-10-07 11:31:56 +00:00
39bccc2747 GODT-968 Messages in All Mail should not be able to mark as deleted
Feature already implemented.
A test already existed, but lacked the final expunge check.
2021-10-07 11:31:56 +00:00
0cf1b38c2b GODT-967 Append external message to All Mail should be APPEND to Archive instead 2021-10-07 11:31:56 +00:00
6b7e706100 GODT-966 Do nothing if message APPEND to All Mail is already in Archive. 2021-10-07 11:31:56 +00:00
bb90ed5446 GODT-966: Fixed comment. 2021-10-07 11:31:56 +00:00
107843d58f GODT-966: return correct UID in response to APPEND to All Mail. 2021-10-07 11:31:56 +00:00
63f089540e GODT-966 Append internal message to AllMail should be no action
GODT-966 Fixed CI issues (WIP)

WIP GODT-966 modified behavior of APPEND.

WIP GODT-966 implemented test for possible order of operations.

WIP GODT-966: code cleanup and refactoring.

WIP GODT-966 fix for linter.

Other: Minor refactoring.
2021-10-07 11:31:56 +00:00
c35ff4f913 Other: James v1.8.10 2021-09-30 22:19:14 +02:00
cd6b5cdcc3 GODT-1348: max 100 conn per host 2021-09-30 21:19:11 +02:00
8f1ca00c9d GODT-1318 go-srp version identifier in go.sum underlying commit is identical 2021-09-30 20:19:07 +02:00
f21f583d05 GODT-1202: Do not update package if it's version older than launcher 2021-09-23 16:24:11 +00:00
e940d9f6fe GODT-1204: Handle importing too big messages 2021-09-23 11:09:56 +00:00
1ed8e939b8 Other: typo 2021-09-20 14:28:57 +02:00
b5d63783f7 GODT-1318: Bump gopenpgp to 2.2.2, go-srp to a843a0b9, go-crypto to 52430bf6 2021-09-17 11:54:55 +02:00
e0113ec267 GODT-219: Update to godog v0.12.1 2021-09-10 13:38:05 +02:00
22d2bcc21d GODT-1205: "RCPT TO" does not contain all addressed from "CC" 2021-09-06 21:13:37 +00:00
91dcb2f773 Other: Bridge James 1.8.9 2021-09-01 12:20:59 +02:00
c676c732ab Merge branch 'release-notes' into devel 2021-09-01 12:13:20 +02:00
444f2d8a12 Other 2021-09-01 07:45:48 +00:00
f10da3c7f0 GODT-1263: Fix crash on invalid or empty header 2021-08-27 13:39:34 +00:00
b8dd9f82bd GODT-1235: Fix 401 response error handling 2021-08-27 12:29:42 +00:00
1157e60972 GODT-1261: Fix building messages with long key 2021-08-16 15:47:14 +02:00
e9e4d8c725 Other: use windows-compatible filename when dumping message in QA builds 2021-08-04 13:05:57 +02:00
186fa24106 Other: Bridge James v 1.8.8 2021-07-21 06:45:47 +02:00
63780b7b8d GODT-1234 Set attachment name 'message.eml' for message/rfc822 attachments. 2021-07-19 14:40:55 +02:00
e3e4769d78 Other: remove dead code 2021-07-19 07:45:56 +02:00
b2e9c4e4e9 Other: Revert "GODT-1224: don't strip trailing newlines from message bodies"
This reverts commit 54161e263fa2f95795fc8623e9dfd2134afb0ae5.
2021-07-19 07:45:29 +02:00
db4cb36538 GODT-1224: don't strip trailing newlines from message bodies 2021-07-19 07:45:12 +02:00
984864553e Other: better keychain logging 2021-07-19 07:44:58 +02:00
2707a5627c Other: prefer empty string check vs nil check 2021-06-25 15:34:48 +02:00
8e0d6d41a6 Other 2021-06-23 08:40:15 +00:00
2b76a45e17 Other 2021-06-21 20:59:37 +00:00
7ab54da8c4 Other: Bridge James 1.8.7 2021-06-17 13:54:43 +02:00
2cce29e858 GODT-1201: bump gopenpgp to 2.1.10 and update crypto time 2021-06-17 05:32:17 +00:00
ef1223391b GODT-1193: Don't doubly encode parts 2021-06-17 07:04:43 +02:00
fb98a797ba Merge branch 'release/james' into devel 2021-06-15 15:56:31 +02:00
6be31bdeb2 Other go.mod 2021-06-15 15:55:02 +02:00
fce5990d19 Other: release notes 1.8.5 2021-06-15 08:57:59 +02:00
1d4ee0c33e Other: Bridge 1.8.6 2021-06-14 09:07:50 +02:00
a3e102e456 GODT-1166: Do not expose current auth token in interface 2021-06-11 17:13:26 +02:00
21dcac9fac GODT-1187: Remove IMAP/SMTP blocking when no internet. 2021-06-11 09:16:47 +00:00
f0ee82fdd2 GODT-1166: Preliminary disable IMAP/SMTP blocking feature. 2021-06-11 09:16:47 +00:00
0c6a098af9 GODT-1166: Reduce the number of auth for live test
- Changed: Do not reauth controller clients.
- Changed: Verbosisty is set only once before run
- Changed: AddUser takes TestAccount as argument
- Added: Setup/clean up before/after test run
- Added: Access to the current refresh token from pmapi.Client interface.
- Added: Context function to add test a user to bridge without login, just call users.FinishLogin.
- Added: PMAPIController.GetAuthClient returns authenticated client for username.
- Added: Persistent clients does not loggout after every scenario.
- Changed: Disabled no-internet tests.
2021-06-11 09:16:47 +00:00
5bf359d34f GODT-1193: don't use message.Read; permit non-UTF-8 charsets 2021-06-11 06:43:21 +00:00
6d784f2444 Other: release notes 1.8.5 2021-06-11 08:33:54 +02:00
835bf1e77f Other: make changelog linter happy 2021-06-09 09:51:31 +02:00
df5fbda72f Other: Bridge James 1.8.5 2021-06-08 23:54:31 +02:00
c482f768d9 GODT-1189 GODT-1190 GODT-1191 Fix missing sender while creating draft. 2021-06-08 09:09:32 +02:00
21cf7459c9 Other: Bridge 1.8.4 Changelog 2021-06-02 11:14:19 +02:00
cf1ba6588a GODT-949: Fix section parsing issue 2021-06-02 05:56:17 +02:00
858f2c7f29 Other: add (failing) bodystructure test 2021-06-01 11:01:14 +00:00
f63238faed Other: stuff mostly passes but bodystructure parse is broken? 2021-06-01 11:01:14 +00:00
f6ff85f69d GODT-1184: Preserve signatures in externally signed messages 2021-06-01 11:01:14 +00:00
ec5b5939b9 GODT-949: Add comment about ignoring InvalidMediaParameter 2021-06-01 09:04:05 +00:00
dec00ff9cc GODT-949: Ignore some InvalidMediaParameter errors in lite parser 2021-06-01 10:54:01 +02:00
9fddd77f0d GODT-1183: Add test for getting contact emails by email 2021-05-31 12:16:26 +00:00
aae65c9d38 Other: fix license year in QML 2021-05-31 13:48:01 +02:00
f3b197fa56 Merge branch 'release/james' into devel 2021-05-28 10:18:46 +02:00
0a9ce5f526 GODT-1155: zero out mailbox password during logout 2021-05-27 16:48:45 +02:00
a2029002c4 GODT-1155 Update gopenpgp and use go-srp 2021-05-27 16:43:44 +02:00
7c41c8e23a Other: Bridge James 1.8.3 2021-05-27 15:13:04 +02:00
36fdb88d96 GODT-1182: use correct contacts route 2021-05-27 14:15:00 +02:00
c69239ca16 Other: bump go-rfc5322 dependency to v0.8.0 2021-05-27 11:03:49 +02:00
e10aa89313 Other: Bridge James 1.8.3 2021-05-26 17:21:33 +02:00
d0a97a3f4a GODT-1044: fix header lines parsing 2021-05-26 14:48:46 +00:00
e01dc77a61 GODT-1044: lite parser 2021-05-26 14:48:46 +00:00
509ba52ba2 GODT-1162: Fix wrong section 1 error when email has no MIME parts 2021-05-26 13:10:05 +00:00
c37a0338c5 Other: Release notes 1.8.2 2021-05-26 13:33:10 +02:00
9f23d5a6f4 Merge branch 'release/james' into devel 2021-05-26 09:42:43 +02:00
885fb95454 Other: Bridge James v1.8.2 2021-05-21 07:16:17 +02:00
629d6c5e4d GODT-1175: report bug 2021-05-20 15:24:43 +02:00
3f50bf66f4 Merge branch 'release/james' into devel 2021-05-20 08:45:23 +02:00
4072205709 Other: version bump and changelog Bridge James 1.8.2 2021-05-20 08:38:33 +02:00
233c55ab19 Other: release notes Bridge James v1.8.1 2021-05-20 08:21:56 +02:00
5d82c218ca GODT-1165: Handle UID FETCH with sequence range of empty mailbox 2021-05-19 16:17:19 +02:00
cb30dd91e3 Other: fix no internet integration test 2021-05-19 08:45:59 +00:00
41d82e10f9 Other: Bridge James v1.8.0 release notes 2021-05-19 10:00:18 +02:00
8496c9e181 Other: bump SMTP test timeout time from 1 to 2 seconds, fingers crossed 2021-05-18 16:55:03 +02:00
3dadad5131 GODT-1161: Guarantee order of responses when creating new message 2021-05-17 17:54:28 +02:00
00146e7474 Other: Bridge James 1.8.0 release notes 2021-05-12 09:04:37 +02:00
12ac47e949 Other: fix typos regarding listener 2021-05-10 15:51:47 +02:00
6ff4c8a738 Other: Bridge James 1.8.0 2021-05-07 15:56:11 +02:00
dd66b7f8d0 GODT-1159 SMTP server not restarting after restored internet
- [x] write tests to check that IMAP and SMTP servers are closed when there
  is no internet
- [x] always create new go-smtp instance during listenAndServe(int)
2021-05-07 10:34:14 +00:00
0b95ed4dea GODT-1146: Refactor header filtering 2021-05-03 15:53:46 +02:00
ce64aeb05f Other: avoid API jail 2021-05-03 07:05:15 +02:00
27cfda680d GODT-1152: Correctly resolve wildcard sequence/UID set 2021-04-30 10:35:34 +00:00
323303a98b GODT-1089 Explicitly open system preferences window on BigSur. 2021-04-30 09:10:14 +00:00
8109831c07 GODT-35: Finish all details and make tests pass 2021-04-30 05:41:39 +02:00
2284e9ede1 GODT-35: New pmapi client and manager using resty 2021-04-30 05:34:36 +02:00
1d538e8540 GODT-876 Set default from if empty for importing draft 2021-04-29 14:07:20 +00:00
8ccaac8090 GODT-1056 Check encrypted size of the message before upload 2021-04-29 12:40:29 +00:00
22bf8f62ce GODT-1143 Turn off SMTP server while no connection 2021-04-28 11:23:41 +02:00
fed031ebaa Other: early release notes 1.7.1 2021-04-28 10:43:42 +02:00
7a15ebbd54 Other: update go.mod with qt docs 2021-04-27 17:11:23 +02:00
94b5799ba7 Other refactor: clean old builder 2021-04-27 08:12:50 +00:00
286f51a4e7 Other: Bridge Iron 1.7.1 2021-04-23 11:29:03 +02:00
ee961ae4a8 GODT-1141 Use attachment name from content type if not specified in content disposition 2021-04-23 07:18:41 +00:00
4038752a9a Other: preserve message header in PGP/MIME passthrough message 2021-04-22 16:30:29 +02:00
ebf724412b Other: fix custom message on decryption error for externally encrypted message 2021-04-22 12:28:54 +00:00
14d42b5e76 GODT-1081 Return newline after headers with every fetch 2021-04-21 13:02:23 +00:00
2b8d92e82d Other: fix release notes 2021-04-21 12:11:52 +02:00
11b1e3acf5 Other: Release notes Iron early 1.7.0 2021-04-21 10:47:46 +02:00
c5eb660315 Other: fix live test: API sanitize timestamp 2021-04-16 08:32:51 +02:00
5ad23715ec Other: Release Bridge Iron v1.7.0 2021-04-15 13:27:05 +02:00
8ab05a000c GODT-1136 DB Cache header from builder and test 2021-04-15 09:51:08 +00:00
454d248819 GODT-213: Preserve contenttype for undecryptable message body 2021-04-15 09:51:08 +00:00
6c8e5f7cd3 GODT-213: Use application/octet-stream for encrypted parts 2021-04-15 09:51:08 +00:00
f5aba717b2 GODT-213: Force no transfer encoding for embedded message/rfc822 parts 2021-04-15 09:51:08 +00:00
1359c39bc0 GODT-213: Remove dead code GetRelatedHeader/GetRelatedBoundary 2021-04-15 09:51:08 +00:00
4850681f1d GODT-213: correctly expect text/plain in custom message text parts 2021-04-15 09:51:08 +00:00
aa55c69307 Other: fix linter 2021-04-15 09:51:08 +00:00
1f19d4df75 GODT-213: Force text/plain for custom message text part 2021-04-15 09:51:08 +00:00
c0f6af9eb5 GODT-213: Complex external encrypted tests (multipart/alternative, message/rfc822 attachment) 2021-04-15 09:51:08 +00:00
ef6a3d4999 GODT-213: Add comments for newly added code 2021-04-15 09:51:08 +00:00
50550d42b4 GODT-213: Message Builder 2021-04-15 09:51:08 +00:00
8db89a1a6c GODT-1113: Fix tray icon size on macOS Big Sur.
Add patched libqcocoa based on Qt 5.13.0
2021-04-15 09:08:19 +00:00
ba1dfb1bf4 GODT-947 Force colors in logs 2021-04-15 07:20:53 +00:00
d243880753 Other: stop rejecting old TLS versions 2021-04-14 09:28:31 +02:00
cccaaa3d82 Other: turn off bad login in live test 2021-04-12 06:16:34 +02:00
2d95f21567 Other: add straightforward linters 2021-04-08 16:09:40 +02:00
7d0af7624c Other: Bump linter 2021-04-07 10:54:09 +02:00
2f35c453a1 Other: Release notes stable 2021-04-01 08:05:04 +02:00
05dd137bc8 Other: Release notes 2021-03-31 06:52:00 +02:00
767628946f Other: Bridge HZM 1.6.9 2021-03-29 12:08:46 +02:00
d4efa7131f GODT-1121 Initial value of silent updates toggle button 2021-03-29 06:15:33 +02:00
144cf6e40c Other: Bridge HZM 1.6.8 & Import-Export Farg 1.3.3 2021-03-26 11:17:01 +01:00
a205d8c046 GODT-1120 hotfix: use Info level in internal/app logs 2021-03-25 11:33:32 +01:00
cccadaee42 Other: Bridge HZM 1.6.7 & Import-Export Farg 1.3.2 2021-03-24 15:11:46 +01:00
bbb365f8a5 Merge branch 'release/farg' into devel 2021-03-24 14:55:55 +01:00
1f18d9d917 GODT-1117 Do not change updates location for Bridge now 2021-03-24 10:45:55 +01:00
59e0d63485 GODT-1105 Fix: Dylib hijack vulnerability found by https://objective-see.com/products/dhs.html 2021-03-24 08:37:30 +00:00
72fe5a636e GODT-1063: Add metainfo to launcher
Refactor metainfo file a bit
2021-03-24 07:04:28 +00:00
45a83133ba Other: increase SMTP line limit to 2^16 2021-03-17 11:45:54 +01:00
215eb4d6eb GODT-1085 Ignore live test of importing to sent and custom label 2021-03-17 08:10:50 +01:00
479b951c50 GODT-1076 Fix UIDPLUS response for importing existing message 2021-03-16 11:55:36 +00:00
a94c8a943f GODT-1077 IMAP sync counting 2021-03-16 12:35:36 +01:00
ea306f405e Other: print address mode in info level 2021-03-12 09:02:54 +01:00
1b405506b8 Merge remote-tracking branch 'remotes/origin/release-notes' into farg 2021-03-11 00:01:21 +01:00
38c6132f81 Other: Import-Export Farg 1.3.1 2021-03-11 00:00:40 +01:00
b7351dfaf8 Other 2021-03-10 21:52:52 +00:00
7e8f6943f2 Other 2021-03-10 21:35:31 +00:00
a0132e8440 GODT-1047 No silent updates for Import-Export app 2021-03-10 18:56:55 +00:00
27541784aa Merge master into devel 2021-03-10 14:52:45 +01:00
9e567f08b2 Other: release notes for 1.6.6 stable 2021-03-04 11:56:11 +00:00
bf274f984e Other: include latest go.mod/go.sum changes 2021-03-04 11:25:33 +00:00
3b60bbe13b Other 2021-03-04 09:50:29 +00:00
a73a1b623a GODT-803 Fix import to wrong target address 2021-03-02 16:02:23 +01:00
c0a8877018 Other: include latest go.mod/go.sum changes 2021-03-01 17:48:22 +01:00
904166c01c GODT-247 Cache and update files moved from user's cache to config 2021-03-01 14:06:58 +00:00
4761bc935a GODT-948 Embedded messages 2021-03-01 09:22:08 +00:00
71301d891f Other 2021-02-28 20:55:23 +01:00
d47be3c4c0 GODT-1043 Fix showing long login error in GUI dialog 2021-02-26 12:21:12 +00:00
199a4d1e3a Other: Release Bridge HZM 1.6.6 2021-02-25 16:28:17 +01:00
18668aafc9 GODT-1029: Fix tray icon not updating under certain conditions 2021-02-25 14:53:43 +00:00
fd73ec6861 GODT-1062: Fix lost notification bar when window is closed 2021-02-25 14:53:43 +00:00
feeb7179f5 GODT-1058 Install after chaning channel right away only in case of downgrade 2021-02-25 14:47:12 +00:00
0e5a45671f GODT-1073 Added: Re-write autostart link on every start if turned on in preferences. 2021-02-24 19:32:59 +00:00
2beb0d298e Other: QA build checks for update every 5 minutes 2021-02-24 20:34:13 +01:00
22a6fcd87f Other: add debug message dump when sending 2021-02-23 10:38:15 +00:00
f499252444 GODT-1055 Fix flaky empty trash test 2021-02-23 08:37:07 +00:00
b27e3fdb28 Merge release/hzm in devel 2021-02-22 17:36:31 +01:00
415e56d928 Other Update bridge_early.md 2021-02-22 15:42:30 +01:00
845074f421 Other: Bridge HZM 1.6.5 2021-02-19 13:00:01 +01:00
28f46deef9 Other: only choose pass if usable 2021-02-18 13:23:38 +01:00
2a078b76e6 GODT-1045 build without Qt by default 2021-02-18 09:45:18 +00:00
3428557b15 Other: Bridge HZM 1.6.4 2021-02-17 14:17:11 +01:00
1f25aeab31 GODT-980: placeholder for user agent 2021-02-17 13:49:51 +01:00
4e531d4524 GODT-1036 Event loop Sentry reporting of failures and refresh 2021-02-17 09:17:19 +00:00
7fc7083c76 GODT-957 Increase space to hide difference 2021-02-17 08:37:12 +00:00
0fe69d9de1 GODT-937: Add keychain switcher to frontend
GODT-1008: Fix transparent dialog under certain conditions
2021-02-17 07:35:59 +00:00
8b436186a4 GODT-1034 More tolerant connection speed detection 2021-02-17 06:13:15 +00:00
4d000c2376 GODT-1018 Pre-push git hook to check lints 2021-02-17 05:10:42 +00:00
56bce8e06f Other: Make all command line flags as const strings 2021-02-16 22:01:50 +00:00
6fd614595d Other: 1.6.3 release notes update 2021-02-16 19:39:43 +01:00
7bb7e1a518 GODT-1041 Log IMAP requests to debug Apple Mail re-sync issue 2021-02-16 14:15:37 +00:00
fb89fb7b31 Other: pretty print prefs.json 2021-02-15 11:31:42 +01:00
e6ae344f1f GODT-797 APPEND waits for EXPUNGE to prevent data loss when Outlook moves from Spam or Trash 2021-02-12 15:33:31 +01:00
bad8cad97d Other: fix nogui build 2021-02-12 09:34:10 +01:00
77cd2955f1 chore: remove credits 2021-02-11 15:10:53 +00:00
567b65df8d feat: autoupdates CLI commands 2021-02-11 08:40:51 +00:00
06b3ed9b85 GODT-317 Fix wrong total mailbox size in Apple Mail 2021-02-11 07:29:28 +00:00
565c0b6ddf Fixing changelog punctuation. 2021-02-10 16:09:47 +01:00
df318382b7 Bridge HZM 1.6.3 2021-02-10 14:45:38 +01:00
341487d839 GODT-1033 Retry starting IMAP server after connection was down 2021-02-10 13:33:18 +00:00
7468ed7dc0 GODT-976 Exclude updates from clearing cache and clear cache, including updates, while switching early access off 2021-02-10 12:34:37 +00:00
c6107dbd4b GODT-919 GODT-1022 GODT-947 Logs and signals
+ Added startup logs
+ Added wait group for update notifications
+ Changed hooks in debug and trace level
2021-02-10 08:51:14 +00:00
bcef1c36ba Notify about update right after the start 2021-02-10 08:51:14 +00:00
6d9d5f35ca Clear unreleased after rebase 2021-02-09 17:25:47 +01:00
6299a6d390 GODT-1021 Do not allow copy Inbox->Sent or Sent->Inbox 2021-02-08 07:53:00 +00:00
4ab5635293 feat: default update channel 2021-02-05 12:17:14 +01:00
5c9e9caa2f feat: include desktop files in repo 2021-02-05 10:38:25 +01:00
e055acb8eb Use lenient version parser to properly parse version provided by Mac 2021-02-05 09:33:26 +00:00
72c01046e3 Better user message about wrong mailbox password 2021-02-05 07:35:02 +00:00
46bc8b08dc Stable integration test deleting many messages using UID EXPUNGE 2021-02-04 09:56:59 +00:00
00b5046653 Update ie_early.md 2021-02-04 08:45:10 +01:00
21d8ef649f Integration tests 2021-02-04 06:37:52 +00:00
6c96643d12 Do not explicitly unlabel folders during move 2021-02-04 06:37:52 +00:00
9193205834 Update release notes. 2021-02-03 15:37:45 +01:00
15df130d76 IE Farg 1.3.0 2021-02-03 10:46:41 +01:00
4554369292 Update release notes. 2021-02-03 10:34:16 +01:00
50d167a983 Sending: do not send empty objects to API 2021-02-01 14:44:37 +01:00
7065211064 Merge release/hzm into devel 2021-02-01 13:47:20 +01:00
0069eb9a0c feat: remove dependency on go-apple-mobileconfig 2021-02-01 11:06:10 +01:00
35b5b925cf Bridge HZM 1.6.2 2021-02-01 10:12:36 +01:00
d4df2ea348 Apply 1 suggestion(s) to 1 file(s) 2021-02-01 08:57:33 +00:00
0159f24f17 fix(GODT-1010): strip angle brackets from ExternalID 2021-02-01 08:57:33 +00:00
619d5eaec9 Bridge HZM v1.6.1 2021-02-01 06:18:05 +01:00
52804c7039 GODT-1008: Fix transparent Welcome message 2021-02-01 02:55:04 +00:00
837e0d3758 GDOT-1007 Notify user when version is the latest. 2021-01-29 15:55:41 +01:00
fa3829cbfe feat: compute size before upload 2021-01-29 14:48:09 +00:00
0679b99a65 feat: reject messages which are too large 2021-01-29 14:48:09 +00:00
4ffa62f6ca fix: set contentID if present and not explicitly attachment 2021-01-29 15:07:48 +01:00
0c458f709f fix: use correct (historical) macOS keychain name 2021-01-29 15:07:48 +01:00
d1daa02b35 ci: add ie qa builds 2021-01-28 11:27:13 +01:00
26cdfdeba9 Apply 1 suggestion(s) to 1 file(s) 2021-01-28 09:13:43 +00:00
f4405b5186 Fix tests after rebase 2021-01-28 09:11:02 +00:00
76dda10572 Importing to sent and inbox 2021-01-28 09:11:02 +00:00
0cde1ab801 hasher with logs and deterministic delimiter 2021-01-27 12:49:27 +00:00
d9c9edf4d7 sanitize changelog 2021-01-27 11:32:11 +01:00
29f034abdc Bridge 1.6.0 HMZ 2021-01-27 11:09:57 +01:00
62a64cde61 Merge release/golden-gate into devel 2021-01-27 11:06:10 +01:00
9747145a3c feat: add logging for catalina detection when configuring applemail 2021-01-27 10:08:33 +01:00
e2a30d1ac6 [GODT-958] Release notes per each channel 2021-01-27 08:12:28 +00:00
3168cbb77d Fix panic when modifying addresses during changing address mode 2021-01-26 08:36:02 +00:00
0a0cc0a62c Fix panic when stopping import progress during loading mailboxes info 2021-01-25 16:22:57 +00:00
adcf0827ee feat: report corrupt update files 2021-01-25 15:45:12 +01:00
b9ee4a152a refactor: remove go-appdir dependency 2021-01-25 13:40:24 +00:00
cb839ff149 fix: check deprecated API statuscode first to better determine API error 2021-01-25 10:32:07 +00:00
45efdad27e docs: info about supported architecture in BUILDS.md 2021-01-25 10:46:39 +01:00
6ef2bb254d Tests and final touches 2021-01-22 11:33:49 +01:00
645a8257d9 Bridge Golden-Gate 1.5.7
Fixed
• Fixed sending error caused by inconsistent use of upper and lower case in sender’s email address
2021-01-22 11:05:24 +01:00
8ab852277c Cache body structure in order to reduce network traffic 2021-01-22 10:54:36 +01:00
516ca018d3 Mitigate Apple Mail re-sync (both bodies and meta info) 2021-01-22 10:54:35 +01:00
5117672388 Turning off IMAP server while no connection 2021-01-22 10:54:35 +01:00
a468ce635c Pause event loop only for non-UID FETCHes 2021-01-22 10:54:35 +01:00
5c58089fb7 Do not unpause event loop if other mailbox is still fetching 2021-01-22 10:54:35 +01:00
b3a64892fe Fix sending error due to mixed case in sender address 2021-01-22 10:30:31 +01:00
3e9c4ba614 Fix move to local folder and back - remove deleted flag 2021-01-22 08:45:28 +00:00
8cd17addbe Apply 1 suggestion(s) to 1 file(s) 2021-01-22 08:10:40 +00:00
2feaba8888 Fix invalid input report 2021-01-22 08:10:40 +00:00
1909ceed67 Support of UID EXPUNGE 2021-01-22 07:49:25 +00:00
07c100bd66 docs: correct readme locations for prefs.json 2021-01-21 14:12:24 +01:00
ab4776c332 refactor: tidy up tls cert stuff 2021-01-21 11:12:16 +00:00
f17e0d761e clean unreleased 2021-01-21 12:04:37 +01:00
a5b9f4c3f1 wait for release notes check and then open externally 2021-01-21 10:26:20 +00:00
a72f52a5ed [GODT-961] Update release notes link
* on release notes, check updates if link missing
* on update set release note link
* update version and links with every check
* clean old release notes
* change paths to IEapp notes
2021-01-21 10:26:20 +00:00
6523b906af Apply 1 suggestion(s) to 1 file(s) 2021-01-21 08:10:46 +00:00
5d246d449c Fix flaky tests about notifying changes 2021-01-21 08:10:46 +00:00
0b39b2adf6 Merge release/golden-gate into devel 2021-01-20 16:48:03 +01:00
25a8c1962b lint changelog 2021-01-20 09:48:43 +01:00
9bb7c828cd feat: don't always report update errors to frontend 2021-01-19 09:36:00 +01:00
f18dcf9db3 Bridge Golden-Gate v1.5.6
Changed
• Improvements to message parsing
• Better error handling

Fixed
• AppleMail crashes (related to timestamps)
• Sending messages from aliases in combined inbox mode
• Fedora font issues
2021-01-18 12:25:08 +01:00
44f8a49b47 [GODT-797] EXPUNGE waits for APPEND to prevent data loss when Outlook moves from Spam to Inbox 2021-01-18 11:55:17 +01:00
10301b8600 Apply 1 suggestion(s) to 1 file(s) 2021-01-18 07:26:27 +00:00
32db6b8d44 Solve missing TODOs 2021-01-18 08:20:53 +01:00
debf015dd0 fix: don't warn users when checking for updates fails 2021-01-15 13:22:56 +00:00
4013892a47 fix: launcher should be executed silently, not in a console, on windows 2021-01-15 13:22:56 +00:00
d5277454c6 fix: use pass by default on linux 2021-01-15 13:22:56 +00:00
a9f44731dc feat: send heartbeat ASAP on each new calendar day 2021-01-15 13:22:56 +00:00
48808992ec fix: better startup ordering 2021-01-15 13:22:56 +00:00
036bc88789 feat: log user agent when updating 2021-01-15 13:22:56 +00:00
67a7d556ec test: add test using fake helper 2021-01-15 13:22:56 +00:00
5ad338e835 Apply 1 suggestion(s) to 1 file(s) 2021-01-15 13:22:56 +00:00
e442c47eed feat: default keychain helper 2021-01-15 13:22:56 +00:00
5380edeeb9 feat: only delete if the secret is present in the keychain 2021-01-15 13:22:56 +00:00
e50d1d01da fix: address review comments 2021-01-15 13:22:56 +00:00
082a803e47 feat: switchable keychain 2021-01-15 13:22:56 +00:00
07d9bc0831 Happy New Year (silent updates) 2021-01-15 13:22:56 +00:00
4e5a1d4b30 Do not rise window on restart needed for silent updates 2021-01-15 13:22:56 +00:00
4a54f878c4 fix: bridge always restarst even when an error updating occurred 2021-01-15 13:22:55 +00:00
dc3b4d53e1 feat: remove deprecated use of BuildNameToCertificate 2021-01-15 13:22:55 +00:00
be583c431e Fix IE SettingsView: change Rectangle to ScrollView 2021-01-15 13:22:55 +00:00
805544ffb0 MR comments 2021-01-15 13:22:55 +00:00
7b84038bf4 rename channels and set pubkey bridge key 2021-01-15 13:22:55 +00:00
e8cbbaa832 Make vertical scroll bar at settings tab always active 2021-01-15 13:22:55 +00:00
56e32e67de remove qa key 2021-01-15 13:22:55 +00:00
b36ac532c9 tweak helpers 2021-01-15 13:22:55 +00:00
d3b0871cf1 feat: only persist cookies on app teardown 2021-01-15 13:22:55 +00:00
7b4204591c fix: add missing OS to x-pm-appversion 2021-01-15 13:22:55 +00:00
4514d72d70 feat: add release notes to version file 2021-01-15 13:22:55 +00:00
dfbf25a9f4 feat: add teardown to app base 2021-01-15 13:22:55 +00:00
122eac50a6 feat: bump version of gopenpgp to v2.1.3 2021-01-15 13:22:55 +00:00
839708dcfe feat: enable autostart to use launcher 2021-01-15 13:22:55 +00:00
d2066173f0 feat: early access 2021-01-15 13:22:55 +00:00
eccad4bbfd feat: verify by checksum and remove if invalid 2021-01-15 13:22:55 +00:00
98ab794f13 [GODT-274] GUI changes for autoupdates
[GODT-275] Add enable/disable auto updates GUI option

Refactor Updater module
GODT-805 Changed manual update information bar layout
GODT-806, GODT-875 Change update dialogs
Refactor InformationBar
2021-01-15 13:22:55 +00:00
b7b2297635 Release notes 2021-01-15 13:22:55 +00:00
dc3f61acee Launcher, app/base, sentry, update service 2021-01-15 13:22:55 +00:00
6fffb460b8 fix: bump license header year 2021-01-15 12:11:44 +01:00
c677b78f16 fix: don't log errors caused by empty SELECT 2021-01-14 14:00:41 +01:00
014c8af560 fix: panic when no multipart/alternative children 2021-01-14 11:52:31 +01:00
2f149eb545 Bridge Golden-Gate v1.5.5
Changed
• Improvements to message parsing
• Better error handling

Fixed
• AppleMail crashes (related to timestamps)
• Sending messages from aliases in combined inbox mode
• Fedora font issues
2021-01-13 15:24:25 +01:00
175f0977f9 chore: bump go-imap dependency and remove go-imap-specialuse dependency 2021-01-11 12:25:06 +00:00
8fe042218c Fix crash when sending while account is logging in 2021-01-07 14:35:26 +00:00
9fe3718d3f Fix empty label name 2021-01-07 09:26:54 +01:00
1839f072b4 Replace old INTERNALDATE 2021-01-06 15:28:50 +00:00
a2cf1b6022 Fix parsing messages with long lines in header and long header split to multiple lines 2021-01-06 13:52:38 +01:00
0a4fb4594a ci: limit GOMAXPROCS to half the number of vCPUs 2021-01-06 09:13:50 +00:00
eb1176eba6 Fix typo in accounts CLI 2021-01-05 16:30:50 +01:00
a89dfc4524 Happy New Year 2021-01-04 11:55:15 +01:00
1ba378dace Remove usage of fontawesome where not needed 2020-12-30 10:10:33 +00:00
25c1014ab0 fix: only set ContentID for inline attachments 2020-12-30 06:36:54 +00:00
363f553d3c chore: bump go-rfc5322 dependency to v0.5.0 2020-12-29 08:05:56 +01:00
36a6f1ed4b test: fix panic on concurrent map write 2020-12-29 07:39:00 +01:00
7b112fc448 Prefer From header instead of MAIL FROM address 2020-12-22 12:46:01 +00:00
2a0052dda6 Fix listener locking 2020-12-18 15:10:32 +00:00
a7b32e1330 chore: bump go-rfc5322 dependency to v0.4.0 2020-12-18 14:15:39 +00:00
5adfdc19aa Apply 1 suggestion(s) to 1 file(s) 2020-12-18 12:56:11 +00:00
351c86ee7a Apply 1 suggestion(s) to 1 file(s) 2020-12-18 12:56:11 +00:00
dce7888408 Added info about nogui to BUILDS.md 2020-12-18 12:56:11 +00:00
a2d99fa6b7 Apply 1 suggestion(s) to 1 file(s) 2020-12-17 07:38:36 +00:00
79465571d7 Fix panic during restarting the bridge 2020-12-17 07:38:36 +00:00
9d576beeb8 Bridge 1.5.4 Golden Gate 2020-12-14 21:15:25 +01:00
e3332d1cb6 Windows needs txt suffix 2020-12-14 16:04:24 +00:00
f59f68f894 Fix Windows license path 2020-12-14 16:04:24 +00:00
9c881a02d6 Fix license path for arch 2020-12-14 16:04:24 +00:00
7b21c2d734 Log warning about permanently deleting messages 2020-12-14 09:21:04 +01:00
9fdc5960bf ci: use large runners for integration tests 2020-12-10 12:08:23 +00:00
fe853efe32 clear unreleased 2020-12-10 13:08:13 +01:00
9b82c03959 Import-Export app Elbe 1.2.3
• Allow an import of already encrypted messages (as cypher text)
    • Cosmetic GUI changes
    • Better error handling
    • Installation issues on linux
2020-12-09 17:57:44 +01:00
914d1b27b5 Bridge Golden-Gate 1.5.3
• Support read confirmations
• Adding GPLv3 licence button to the GUI
• Improved testing

• AppleMail crashes (timestamp related)
• Encoding errors
• Installation issues on linux
2020-12-09 17:56:21 +01:00
f295d03641 License button to open LICENSE file 2020-12-09 15:58:41 +00:00
8515f6e6ac Switch to bridge-internal:latest runner 2020-12-09 15:33:29 +00:00
4d330e24c1 Add qt docs target to system-qt builds 2020-12-09 15:33:29 +00:00
a7a52bc57e testing native qt builds with CI 2020-12-09 15:33:29 +00:00
3cef7985d3 rename COPYING.md file 2020-12-09 10:14:41 +00:00
40db822450 Replace old date to not crash Apple Mail 2020-12-09 07:22:04 +00:00
2de202ca02 fix: set flags in status response 2020-12-08 09:06:22 +00:00
38eb9fdac7 feat(GODT-906): support rfc2047-encoded content transfer encodings 2020-12-07 13:03:49 +01:00
f469d34781 Send unilateral responses before sending OK 2020-12-04 12:37:02 +00:00
33dfc5ce09 Use function to determine which functions to skip 2020-12-02 12:31:18 +00:00
2100e2ff7c Enhanced sentry reporting 2020-12-02 12:31:18 +00:00
e9b7cce138 chore: bump go-rfc5322 dependency to v0.2.1 2020-12-02 10:58:56 +01:00
6877a5a15d Add changelog linter 2020-12-01 08:48:21 +00:00
64206e69bd Fix of all known flaky tests 2020-11-30 16:15:53 +01:00
7643c76cb1 Merge branch 'release/elbe' into devel 2020-11-30 11:59:41 +01:00
b0f59273d3 ci: beefier runners for heavier jobs 2020-11-30 08:15:09 +01:00
af8eb9d37d Adding documentation about therecipe/qt 2020-11-27 08:58:05 +00:00
635e51f32f Upgrade to latest go-smtp 2020-11-27 09:23:18 +01:00
ca962ce5ad Import encrypted messages as is 2020-11-27 09:09:11 +01:00
a50266cdc0 Merge branch 'master' into release/elbe 2020-11-27 07:46:40 +01:00
6230200218 Import-Export app Elbe 1.2.2
Changed
* Improvements to the import from large mbox files with multiple labels
* Not allow to run multiple instances of the app or transfers at the same time
* Better handling and displaying of skipped messages
* Various enhancements of the import process related to parsing
* Cosmetic GUI changes
* Better error handling

Fixed
* Linux font issues - Fedora specific
* App response to the user pausing and canceling import or export
* Upgrade errors
2020-11-27 07:37:08 +01:00
f96cd167ef Merge branch 'release/golden-gate' into devel 2020-11-26 09:32:06 +01:00
072ce54fe1 Bridge 1.5.2 Golden Gate
Release Notes
* Improved package creation logic
* Refactor of sending functions to simplify code maintenance
* Added tests for package creation

Fixed
* Bridge crashes related to labels handling
* GUI popup related to TLS connection error
* An issue where a random session key is included in the data payload
* Error handling (including improved detection)
2020-11-24 10:38:36 +01:00
d043cb9086 test: disable flaky expunge tests (followup GODT-881) 2020-11-23 14:41:47 +00:00
1f31df3a94 Bridge 1.5.1 Golden Gate
Release Notes
* Improved package creation logic
* Refactor of sending functions to simplify code maintenance
* Added tests for package creation

Fixed
* Bridge crashes related to labels handling
* GUI popup related to TLS connection error
* An issue where a random session key is included in the data payload
* Error handling (including improved detection)
2020-11-23 07:43:43 +01:00
9ee30e4923 Add sentry fingerprint 2020-11-20 14:44:42 +00:00
7b44f12ab1 Update sentry client 2020-11-20 14:44:42 +00:00
874882b554 Logic change to follow old code. 2020-11-20 13:39:13 +00:00
945bdf4c60 Custom types for flags and encrypted outside test 2020-11-20 13:39:13 +00:00
6e1e5a2afe re-organise test definitions 2020-11-20 13:39:13 +00:00
b709b51790 Simplify test cases 2020-11-20 13:39:13 +00:00
d380485bb6 Fixing lint and integration tests, changelog, GODT-880, and typos 2020-11-20 13:39:13 +00:00
87c8228cd0 rename 2020-11-20 13:39:13 +00:00
152046bf97 refactor smtp sending
* [x] move package creation logic to `pmapi.SendMessageReq`
* [ ] write test of package creation logic
    * [x] internal
    * [x] plain
    * [x] external encrypted
    * [ ] signature ???
    * [x] attachments
2020-11-20 13:39:13 +00:00
a0fbed5859 use unreleased for changes 2020-11-20 14:35:24 +01:00
89e9e17d26 Fix typos in InlineLabelSelect.qml 2020-11-20 07:43:43 +00:00
b595247392 chore: add version info to github issue template 2020-11-19 16:57:21 +01:00
9d50a8cef2 Add OS to app version 2020-11-18 09:46:01 +00:00
f888176485 Build creates proper binary names 2020-11-18 08:56:38 +00:00
2f9876ad74 Remove unnecessary semicolon 2020-11-13 13:18:16 +00:00
53404122cc Integration test of sending and manual appending to Sent mailbox 2020-11-13 13:18:16 +00:00
ba65494fce Try load messages one-by-one 2020-11-13 09:43:04 +00:00
70645c1732 Import-Export Elbe 1.2.1
• Further improvements to address and date parsing
• Better handling and displaying of skipped messages
• Improved error reporting
2020-11-11 14:03:00 +01:00
1055e60d27 Fixing time order in changelog. 2020-11-11 12:02:56 +01:00
e04196f8a0 feat: switch to public go-rfc5322 parser 2020-11-10 09:27:07 +00:00
11a0dec047 Using atomic bool 2020-11-10 07:50:29 +00:00
b9740e1b7d Close connection before deleting labels to prevent panics accessing deleted bucket 2020-11-10 07:50:29 +00:00
f0695eb870 add test gui 2020-11-09 11:58:32 +00:00
a40018cdf9 Percentage available on progress count struct 2020-11-09 11:58:32 +00:00
5b7eabe21a Skipped messages do not change total counts but shows as separate number 2020-11-09 11:58:32 +00:00
d5d60aa11b feat: remove tls upgrade error notification 2020-11-09 10:59:42 +00:00
a62fa132e6 rename build tag 2020-11-06 16:02:30 +01:00
052395f917 test: add benchmarks for rfc5322 address/date parser 2020-11-04 15:00:18 +01:00
9a77650004 Bridge GoldenGate 1.5.0
- Ensured better message flow by refactoring both address and date parsing
- Improved secure connectivity checks
- Better deb packaging
- More robust error handling

- Ensured that conversations are properly threaded
- Fixed Linux font issues (Fedora)
- Better handling of Mime encrypted messages
2020-11-04 12:26:07 +01:00
f1d70361c9 Do not include conversation ID in references 2020-11-04 09:12:16 +00:00
3496599723 feat: custom address/date parser based on rfc5322 abnf 2020-11-03 16:21:06 +01:00
9e0635a6a4 fix: don't check tls fingerprints when checking connectivity 2020-11-02 13:38:39 +00:00
10509621ce Updated go-mbox dependency back to upstream 2020-11-02 10:32:21 +01:00
3727ecdfe5 Show in error counts also lost messages at the end report 2020-10-30 13:58:32 +00:00
ac71d22e86 Waiting for unilateral update during deleting the message 2020-10-30 13:42:04 +00:00
bc81356d53 test: update feature file to use new "seq" command 2020-10-29 13:10:54 +01:00
881cb64beb Release Danube: notes, version bump, change log 2020-10-29 12:57:59 +01:00
1286e57b63 Support Apple Mail MBOX export format 2020-10-29 09:07:37 +01:00
fe5f73d96e Fix crash when IMAP client connects while account is logging in 2020-10-29 07:21:45 +00:00
8f7a8b31a3 Apply 1 suggestion(s) to 1 file(s) 2020-10-28 16:42:57 +00:00
68db35d5d4 Not able to update I-E on mac GODT-794
Added missing signal, corrected the update name, log tweaks.
2020-10-28 16:42:57 +00:00
df17017ced Apply 1 suggestion(s) to 1 file(s) 2020-10-28 10:20:32 +00:00
5c48332b0e change rectangle to column in global settings GODT-677 2020-10-27 10:13:08 +01:00
8985738af5 Merge master into devel 2020-10-23 10:31:08 +02:00
2d8a676dd5 Merge branch 'release/forth' into release/danube 2020-10-22 18:00:33 +02:00
7e0a9f398c I/E Fix printing zero time in error report 2020-10-22 09:12:56 +00:00
9af5769510 Apply 1 suggestion(s) to 1 file(s) 2020-10-22 08:26:35 +00:00
bb46d9a009 README and BUILD info about Import-Export and tags 2020-10-22 10:22:00 +02:00
606b42a6e7 Fix flaky TestFailUnpauseAndStops 2020-10-22 10:04:22 +02:00
d547f5ea22 Changelog 2020-10-21 13:56:55 +02:00
563b4889e3 Update go-imap dependency to get fix for UTF-7 incompatibility 2020-10-21 09:15:42 +00:00
b449beb68c Do not spam sentry with bad ID by integration test 2020-10-21 08:38:54 +00:00
f9d58f4f9c Merge branch 'release/forth' into release/danube 2020-10-21 09:07:27 +02:00
1dfec9902e gofmt fix 2020-10-21 09:04:06 +02:00
79cafee2eb Support quoted printable and filter out some auto-generated Gmail labels 2020-10-21 09:04:06 +02:00
64fbcdc1ca Fix mbox scanning 2020-10-21 09:04:06 +02:00
e4a341af3a Better log message 2020-10-21 09:04:05 +02:00
e0292fe957 Use map instead of list as set 2020-10-21 09:04:05 +02:00
ef85c8df24 Detect Gmail labels from All Mail mbox export 2020-10-21 09:04:05 +02:00
719d369c2a Fix transfer stopping 2020-10-21 06:42:54 +00:00
51b6f95342 Show fatal errors after export is terminated 2020-10-21 06:14:39 +00:00
26fb1fc34d Sanizize mailbox name for exporting 2020-10-21 06:02:02 +00:00
ae1578a5e2 GODT-829 fix apple mail subfolders 2020-10-20 19:09:59 +02:00
cfd8e56277 Do not resume paused transfer progress after dismissing cancel popup 2020-10-19 10:25:52 +02:00
4893931a8d Fix deadlock in integration tests for Import-Export 2020-10-16 10:53:44 +02:00
932928ddc8 Allow to send calendar update multiple times 2020-10-15 13:11:40 +00:00
a33e414f01 Do not mix font awesome icon with regular text to avoid issues on Fedora 2020-10-15 12:48:09 +00:00
43d54c8f4f Clear separation of different message IDs in integration tests 2020-10-14 14:41:39 +02:00
6cbc11a75d Fix update on windows 2020-10-14 11:25:19 +02:00
a21bb130e1 Append duplicate of emails with References 2020-10-14 10:11:49 +02:00
12403785af fix: replace, don't add, transfer encoding when making body 7-bit clean 2020-10-09 13:55:37 +02:00
b4892855d4 Set flags by FLAGS (not using +/-FLAGS) do not change spam state 2020-10-06 08:42:33 +00:00
7ff67f2217 Reverted sending IMAP updates to be not blocking again 2020-10-05 11:33:16 +02:00
4912c27be8 Changelog 2020-10-05 10:51:11 +02:00
288ba11452 test: add test for sending pgp/mime as plaintext 2020-10-01 16:56:38 +02:00
7874183052 fix(GODT-770): handle extraneous end-of-mail 2020-10-01 16:16:15 +02:00
b12873f1df Fix of speed of checking whether message is deleted 2020-10-01 13:42:16 +00:00
dc9851f8ea fix(GODT-749): don't force pgp/inline when sending plaintext 2020-10-01 10:47:39 +02:00
ec73170e9b Use label.Path instead of Name 2020-09-30 09:38:35 +02:00
68616e470c chore: bump crypto version 2020-09-25 15:45:29 +02:00
53cd2ff524 CI artifacts only for a day 2020-09-25 11:29:45 +02:00
51c8bceed8 Changelog and use pmmime 2020-09-24 18:19:35 +02:00
e02c7c7f06 Parsing message with empty address as '<>' 2020-09-24 15:37:21 +02:00
15c1d7bc24 fix: duplicate charset param 2020-09-24 14:26:45 +02:00
a89a3f6612 Convert to UTF-8 any message part which specifies charset 2020-09-24 13:58:15 +02:00
d956b04062 Parsing non-utf8 multipart/alternative message 2020-09-24 13:17:38 +02:00
ef1671d4ab Parsing message with empty CC 2020-09-24 12:23:31 +02:00
fe926cbd57 IE release notes and GODT-738 2020-09-23 13:50:08 +02:00
e01747e3b9 Merge branch 'release/forth' into devel 2020-09-23 13:10:11 +02:00
85220848d0 Update total even if its zero 2020-09-23 09:24:58 +02:00
70f91ae55b notes and build v1.4.0 2020-09-21 13:29:33 +02:00
a73b30ed9e Better naming 2020-09-18 10:25:14 +02:00
7337f78d4a PMAPI target - parallel upload 2020-09-18 10:25:14 +02:00
9b5da91f7c Fix: Yahoo not supporting TLS1.3 GODT-730 2020-09-18 07:53:53 +00:00
c7669b950f fix: gitignore should also ignore ie build files 2020-09-17 14:33:46 +02:00
b3ed8d51a7 fix: version check for catalina 2020-09-17 11:35:05 +00:00
60b7d980f4 Fix integration test - deleting from All Mail 2020-09-17 10:19:55 +00:00
abf2238e6f Wrap imap-id with backend caller 2020-09-17 08:59:28 +00:00
b4a358c084 User agent detected by fake IMAP extension instead of AUTH callback 2020-09-17 08:59:28 +00:00
3606a0ab9f QA build with option to change API URL by ENV variable 2020-09-17 08:30:31 +00:00
c5665d0dd7 Unsilent errors reading mbox files 2020-09-16 15:51:08 +02:00
d6464c0048 Fixes after rebase 2020-09-16 09:51:58 +00:00
5496a26f73 Finish tests for moving without MOVE support 2020-09-16 09:51:57 +00:00
ec9a799fe9 test - move like outlook - GODT-536 2020-09-16 09:51:57 +00:00
730abadfc3 Do not allow deleting messages from All Mail 2020-09-16 09:51:57 +00:00
60e1548685 log both timeouts in update send 2020-09-16 09:51:57 +00:00
7430c7f1f5 Timeout for sending IMAP update 2020-09-16 09:51:57 +00:00
6671b78799 Simplified integration tests 2020-09-16 09:51:57 +00:00
c7578cf53c \Deleted flag support finish 2020-09-16 09:51:57 +00:00
66e04dd5ed Implement deleted flag GODT-461 2020-09-16 09:51:57 +00:00
803353e300 Tests for deleted flag GODT-496 2020-09-16 09:51:57 +00:00
f3773c9d78 I/E measurements 2020-09-16 09:29:13 +00:00
41ac61bbe8 fix: less spammy go-message logs 2020-09-15 09:37:29 +00:00
0d3d6747ac fix: grammar in gui 2020-09-15 08:51:02 +00:00
eaa9a458c4 test: use actual broken eml 2020-09-15 06:31:45 +00:00
46e5cb9c83 test: use message.Parse for fakeapi import parser 2020-09-15 06:31:45 +00:00
dc5387a512 fix: bug report window title 2020-09-15 08:04:51 +02:00
4b7c234e78 feat: strip comments from addresses 2020-09-14 14:46:44 +02:00
5bca6fc3cf chore: tidy up before merge 2020-09-14 14:19:35 +02:00
97b64ebb70 fix: credits and release notes 2020-09-11 11:41:03 +02:00
9b3cc9dc34 feat: convert content type in html meta tags 2020-09-11 11:41:03 +02:00
afeed4a801 feat: use upstream go-message 2020-09-11 11:41:03 +02:00
dd70b30f76 fix: don't use full pk fingerprint, only use first 8 chars 2020-09-11 11:41:03 +02:00
3e8e3c912b fix: don't doubly apply 822 texwrapper 2020-09-11 11:41:03 +02:00
5d0e3f36b4 fix: unhandled charset in header 2020-09-11 11:41:03 +02:00
da751a38e3 fix: public key names and content types 2020-09-11 11:41:03 +02:00
f9af17dd9b fix: allow unknown encodings during initial parse 2020-09-11 11:41:03 +02:00
f622ecf678 feat: logging throughout parser 2020-09-11 11:41:03 +02:00
475e673b87 feat: add logging for encoding detection 2020-09-11 11:41:03 +02:00
3916ddc8e4 fix: allow overriding sign via contact settings if set 2020-09-11 11:41:03 +02:00
ef2ace0afe fix: always check charset before utf8 validity 2020-09-11 11:41:03 +02:00
b5d3737a7e fix: sign not overriding global 2020-09-11 11:41:03 +02:00
d872d77cf5 fix: draft mime type instead of composermode 2020-09-11 11:41:03 +02:00
1f17628399 fix: unequal number of rich/plain parts 2020-09-11 11:41:03 +02:00
4ab8f7d6b5 fix: pubkey should not be collected as attachment 2020-09-11 11:41:03 +02:00
fa5f4acdac docs: add docstring for buildBodies 2020-09-11 11:41:03 +02:00
642666fa59 docs: add docstrings for walker/visitor handlers/rules 2020-09-11 11:41:03 +02:00
a2cf5374b9 feat: more efficient regexp use in parser 2020-09-11 11:41:03 +02:00
6a7a77fc51 refactor: tidier encoding detection 2020-09-11 11:41:03 +02:00
f4dfadce52 feat: attach public key 2020-09-11 11:41:03 +02:00
9ba08e5edb refactor: remove dead code 2020-09-11 11:41:03 +02:00
9821b5bbc2 feat: recreate message with parser's writer 2020-09-11 11:41:03 +02:00
5343a6fc0f fix: fallback to detecting charset if cannot handle specified one 2020-09-11 11:41:03 +02:00
180c6699e0 fix: don't select multipart/alternative if length is 0 2020-09-11 11:41:03 +02:00
7d1b0d0a40 docs: changelog 2020-09-11 11:41:03 +02:00
caff73d06c docs: add HELP about 7bit filter 2020-09-11 11:41:03 +02:00
f4d073b4cf test: ignore weird test for now 2020-09-11 11:41:02 +02:00
65d8b382d0 fix: panic when no params available 2020-09-11 11:41:02 +02:00
0e7e13211b refactor: don't reconstruct mimeBody 2020-09-11 11:41:02 +02:00
7e1af9ff4e fix: linter issues 2020-09-11 11:41:02 +02:00
37186846db feat: wrap attachment lines as per rfc822 2020-09-11 11:41:02 +02:00
a5a61c9428 feat: set attachment headers 2020-09-11 11:41:02 +02:00
ea01c155da feat: handle foreign encodings 2020-09-11 11:41:02 +02:00
f4374a02da refactor: tidy a bit 2020-09-11 11:41:02 +02:00
0d4d95360f feat: set header 2020-09-11 11:41:02 +02:00
f88071b2ca feat: parse date 2020-09-11 11:41:02 +02:00
e01a523ae3 feat: pull out most things as attachments 2020-09-11 11:41:02 +02:00
c6b18b45b5 feat: better handling of multipart messages 2020-09-11 11:41:02 +02:00
a7da66ccbc feat: enter and exit handlers 2020-09-11 11:41:02 +02:00
8bd74c5edc feat: set mime type 2020-09-11 11:41:02 +02:00
2b36d3ab7b feat: attach public key 2020-09-11 11:41:02 +02:00
45b863f931 feat: parse most header values 2020-09-11 11:41:02 +02:00
953150cfdb feat: add part getter 2020-09-11 11:41:02 +02:00
6ea3fc1963 feat: initial parser exposing walker/writer 2020-09-11 11:41:02 +02:00
7207a5d59e docs: changelog 2020-09-11 09:08:19 +00:00
dd2264da6f fix: notify of unencrypted recipient 2020-09-11 09:08:19 +00:00
9261b6337e docs: changelog 2020-09-11 10:48:27 +02:00
4f6e8c30c7 fix: use correct package type for signed inline 2020-09-11 10:29:02 +02:00
614a00eac1 Update release date for Congo in Changelog 2020-09-09 12:23:42 +02:00
de58c7a905 Cookies for Import-Export 2020-09-09 09:09:35 +02:00
2e439e17cf Remove unused scope methods 2020-09-09 06:21:02 +00:00
f73aeec97f Update changelog 2020-09-08 08:43:05 +00:00
8a7b4bb919 Improve user agent 2020-09-08 08:43:05 +00:00
78fd73ee2a Merge branch 'release/congo' into devel 2020-09-08 09:37:05 +02:00
bfdfc81d65 release notes 2020-09-07 08:03:02 +02:00
bf6963859f rename IE app GODT-690 2020-09-04 11:55:30 +02:00
33bf64cc4e Fix hover on links in popups 2020-09-04 10:43:59 +02:00
bb1d27a5be Do not ignore errors 2020-09-03 14:36:12 +02:00
bc07896436 Sentry report after parser panic 2020-08-31 17:23:22 +02:00
1d2e584799 Convert panics from message parser to error 2020-08-31 15:57:45 +02:00
9218598140 Update routes to API v4 2020-08-31 07:42:20 +00:00
af89931f05 Hardcoded version 2020-08-27 13:59:07 +02:00
84147a2cb0 Fix flaky tests 2020-08-25 10:20:49 +02:00
2269a9edb7 Pause event loop while FETCHing to prevetn EXPUNGE 2020-08-24 08:26:31 +00:00
61867fbde7 Add hour when days don't match GODT-655 2020-08-24 10:11:51 +02:00
2d9417d501 Migrate from old credentials 2020-08-24 10:11:51 +02:00
4973e38748 Import-Export app everywhere 2020-08-24 10:11:51 +02:00
e4704cd459 Release notes 2020-08-24 10:11:51 +02:00
8592a264c0 Fix showing error msg 2020-08-24 10:11:51 +02:00
40aeb6c010 Fixing IE icon 2020-08-24 10:11:51 +02:00
71b9a3b205 Release notes for Import-Export 2020-08-24 10:11:51 +02:00
2182e573f9 Update maximal date on every DateInput dropdown toggle 2020-08-24 10:11:51 +02:00
5f02e59fa4 Fix showing table with errors 2020-08-24 10:11:51 +02:00
29ff8cf54b fix: double colon in window title again 2020-08-24 10:11:51 +02:00
f8cf4e966f fix: double colon in window title 2020-08-24 10:11:51 +02:00
df80e7eb27 Keep Import-Export credits up to date 2020-08-24 10:11:51 +02:00
658ead9fb3 Import/Export final touches 2020-08-24 10:11:51 +02:00
4f0af0fb02 Import/Export metrics 2020-08-24 10:11:51 +02:00
7e5e3d3dd4 Import/Export GUI 2020-08-24 10:11:51 +02:00
1c10cc5065 Import/Export backend 2020-08-24 10:11:51 +02:00
49316a935c Shared GUI for Bridge and Import/Export 2020-08-24 10:11:50 +02:00
b598779c0f Import/Export backend prep 2020-08-24 10:11:50 +02:00
9d65192ad7 feat: clear expired cookies from persistent storage 2020-08-24 09:08:42 +02:00
0e14155185 fix: cookie expiry needs to be set 2020-08-21 12:42:49 +02:00
56f4f3d017 fix: better error handling when message is still in send queue 2020-08-19 15:25:38 +02:00
f5617ced3f fix (GODT-597): duplicate send when draft creation takes a long time 2020-08-18 13:40:56 +00:00
35b37c7097 fix (GODT-597): duplicate send when draft creation takes a long time 2020-08-18 13:40:56 +00:00
77c6ba381e fix: mime type 2020-08-18 09:14:46 +00:00
34df24ede3 docs: changelog 2020-08-18 09:14:46 +00:00
33d705a39d test: fix test content types 2020-08-18 09:14:46 +00:00
64fbd0655f fix: sign when no contact present 2020-08-18 09:14:46 +00:00
b700a7823e fix: send to internal 2020-08-18 09:14:46 +00:00
145da7ffa5 refactor: pass mailSettings in to avoid extra call 2020-08-18 09:14:46 +00:00
61a841ced7 refactor: builder pattern for generateSendingInfo 2020-08-18 09:14:46 +00:00
29978b7014 ci: use bridge-internal image 2020-08-17 08:28:32 +00:00
dd73687555 ci: switch to using container from bridge-internal 2020-08-17 08:28:32 +00:00
5411b29d17 Merge branch 'release/v1.3.X' into devel 2020-08-17 09:58:39 +02:00
6c93f1f1ec Fix integration tests - compiting message flags 2020-08-17 09:10:03 +02:00
1dcaa200e0 fix: docstring mistakes 2020-08-13 11:54:52 +02:00
66082af40f test: add test that cookie jar loads cookies 2020-08-13 11:39:28 +02:00
209af59232 refactor: make cookie architecture less crazy 2020-08-13 11:31:11 +02:00
9f24c666b9 docs: add docstrings 2020-08-12 15:46:19 +02:00
3101fc5543 fix: add missing license 2020-08-12 15:05:14 +02:00
182bbd556f fix: failure to create cookie jar is not fatal error 2020-08-12 14:56:55 +02:00
e333ccd29e feat: persistent cookies 2020-08-12 14:55:24 +02:00
ce4a75caf5 fix: properly decide whether it is first gui start 2020-08-06 09:29:30 +02:00
01a8c9e9d7 Adding GUI troubleshoot popup GODT-554 GODT-583 2020-08-06 08:12:37 +02:00
2c910378ce feat: detect bad certificate error 2020-08-06 07:34:36 +02:00
34ef9063cb fix: better first start setting 2020-08-05 15:20:20 +02:00
f651d39820 chore: bump dependencies 2020-08-03 07:47:49 +00:00
7e6d09a247 test: generate tls cert/key in test 2020-08-03 09:24:39 +02:00
da381130a3 Check log file size more often to prevent huge log files 2020-07-31 13:24:25 +02:00
7baa4dc117 release notes 2020-07-29 07:07:50 +02:00
be07cb83c9 chore: bump linter to v1.29.0 2020-07-28 08:37:40 +00:00
dfbd86c7bc fix: add missing option to modify system keychain 2020-07-27 17:23:59 +02:00
e3ab829ad3 fix: missing command in exec call 2020-07-27 13:05:46 +02:00
b12ef1327c refactor: better confirmer result locking 2020-07-24 13:04:29 +00:00
d66bcc4b63 fix: bad ID in frontend 2020-07-24 13:04:29 +00:00
5ad307868e feat: add expiry 2020-07-24 13:04:29 +00:00
369c6ebf85 fix: clean up after setting result 2020-07-24 13:04:29 +00:00
c988d739a1 docs: add docstrings for confirmer 2020-07-24 13:04:29 +00:00
36ef9f20ae feat: use confirmer in smtp 2020-07-24 13:04:29 +00:00
c8f118a26b feat: implement confirmer 2020-07-24 13:04:29 +00:00
be20714842 feat: better way to add trusted cert in macOS 2020-07-24 14:51:30 +02:00
1711442878 Fix setting flags 2020-07-23 14:49:43 +02:00
79e6799f40 fix: panic in integration tests 2020-07-22 11:04:23 +00:00
6023162443 feat: add build tag for deterministic imap password 2020-07-22 09:32:20 +00:00
01e0fe4863 ci: use shared macos runner 2020-07-21 16:58:47 +02:00
f073301481 fix: versioning 2020-07-17 13:04:11 +02:00
1df81e4a34 chore: bump changelog 2020-07-16 11:51:56 +02:00
bf0945eaef fix: race condition in AuthRefresh that could cause user to be logged out 2020-07-16 10:19:50 +02:00
11e01ca163 chore: bump version to 1.3.0 2020-07-15 15:36:55 +02:00
a650a04a88 Bump bbolt dependency 2020-07-15 13:27:40 +02:00
bdc11c8358 ci: enable automated builds on all platforms 2020-07-14 14:57:25 +00:00
ed7a0dc9b3 fix: don't assume contact keys are armored 2020-07-14 16:43:06 +02:00
fc4e77604f fix: don't panic if not given tls connection in pin checker 2020-07-09 13:19:32 +02:00
abaeace4b3 chore: bump go-imap version to get select fix 2020-07-08 10:34:18 +02:00
457b524ba8 chore: bump go-imap to include delimiter fix 2020-07-07 11:10:37 +00:00
d89d627349 test: increase minimum listener event receive time 2020-07-06 16:11:53 +02:00
51ff880fd9 test: fix flaky test TestSyncAllMail 2020-07-03 14:43:44 +02:00
b25baa2524 test: set sent label properly 2020-07-03 07:45:16 +00:00
10e384f4df test: add tests for parsing mime message with bad 2231 filename 2020-07-03 09:19:18 +02:00
35ae2011b6 Merge branch master into devel 2020-07-02 10:16:08 +02:00
5348ae7d18 Changelog wording 2020-07-01 09:19:11 +02:00
2512d3647a chore: bump linter to v1.27.0 2020-07-01 07:06:55 +00:00
1e8cb35fcb test: add test for multiline 2020-06-30 16:33:29 +02:00
0b0991d682 fix: infinite loop when decoding invalid 2231 charset 2020-06-29 15:40:46 +02:00
813e99f399 Fix flaky integration tests 2020-06-26 09:51:56 +00:00
b6707749e5 chore: bump go-imap dependencies 2020-06-25 10:52:51 +02:00
7ec4309ae1 fix: correctly handle failure to unlock single key 2020-06-24 14:22:26 +02:00
ec224a962f fix: hang when reloading keys 2020-06-22 10:19:13 +02:00
012be60311 test: remove time checks 2020-06-17 15:30:41 +02:00
02804d067c fix: ensure doh connections are closed when it is disabled 2020-06-17 10:57:12 +02:00
9241a9bdbf feat: add reloadkeys method 2020-06-16 12:51:28 +02:00
f3e6af5571 feat: clear keys after unmarshaling 2020-06-16 10:23:21 +02:00
7a13b89274 test: reword scenario 2020-06-16 07:34:46 +00:00
5cb78b0a03 fix: review comments 2020-06-16 07:34:46 +00:00
c19bb0fa97 feat: migrate to gopenpgp v2 2020-06-16 07:34:46 +00:00
de16f6f2d1 Apply suggestion to internal/store/mailbox_message.go 2020-06-16 09:15:16 +02:00
7963b3c152 Apply suggestion to internal/store/mailbox_message.go 2020-06-16 07:05:35 +00:00
f82ab3189b Apply suggestion to internal/store/mailbox_message.go 2020-06-16 07:05:35 +00:00
49cc49b1e2 [GODT-354] Do not label/unlabel messsages from All Mail folder 2020-06-16 07:05:35 +00:00
9808c44714 fix: avoid listing credentials, prefer getting 2020-06-15 14:27:01 +02:00
c329711f9c fix: bad fish 2020-06-11 14:01:58 +02:00
928fa93765 fix: don't remove log dir on startup 2020-06-05 10:48:34 +02:00
45e99caa23 fix: handle double charset everywhere by using our ParseMediaType 2020-06-03 12:51:31 +00:00
80b2bfc2a5 fix: crash in message.combineParts when copying a nil slice 2020-06-03 12:41:51 +00:00
6070a3b7cc fix: crash if fail to find necessary html element 2020-06-03 14:05:20 +02:00
9e633400b0 feat: [GODT-360] detect charset embedded in html and xml 2020-06-02 09:44:50 +02:00
84d344cb0a chore: bump docker-credential-helpers version 2020-05-29 14:54:43 +02:00
e43033b42b feat: revert back to quoted-printable 2020-05-29 12:21:48 +00:00
e5d63edb62 test: add message.Parse tests 2020-05-29 12:21:48 +00:00
579e962980 check license 2020-05-29 14:01:10 +02:00
2919d1a3c0 fix: properly find parent id 2020-05-28 06:53:00 +00:00
1ba319bb69 Pimp up changelog 2020-05-28 06:18:29 +00:00
8cdebb6d05 Fix flaky store cooldown test 2020-05-27 15:20:38 +00:00
cc14b523cb fix: correct doh timeouts 2020-05-27 07:32:26 +00:00
ad877431de fix: check doh permission 2020-05-27 07:32:26 +00:00
40d8c458d2 Store factory to make store optional 2020-05-26 14:57:41 +00:00
3b0b1a457b docs: add locations of bridge files to readme 2020-05-26 08:02:15 +02:00
7ac4c9aecf fix: don't logout user if auth refresh fails because internet dropped 2020-05-25 15:21:20 +00:00
390182d247 do not complie windows twice, always pack to tgz 2020-05-25 13:46:50 +00:00
cb8a15a9fd fix: crash when removing account while messages are being returned 2020-05-25 08:29:42 +00:00
4d2baa6b85 Renamed bridge to general users and keep bridge only for bridge stuff 2020-05-25 09:02:34 +02:00
4e2ab9b389 Validate recipient emails in send before asking for their public keys 2020-05-21 07:26:34 +00:00
c6c6cfc7d7 Fix Changelog history 2020-05-21 09:27:46 +02:00
a78b1ca00f refactor: remove dead code 2020-05-20 11:33:22 +02:00
d718720b29 fix: custom message bad pgp using template 2020-05-20 10:59:00 +02:00
f64cb4b56d fix: wrong zip format 2020-05-19 16:19:51 +02:00
b2b43ac909 fix: missing ci package zip 2020-05-18 12:45:42 +00:00
f2b8d02cd2 fix: can't connect to docker 2020-05-18 14:19:19 +02:00
3c92ff18ff feat: use zip for windows targetos 2020-05-18 12:35:27 +02:00
41f6cd3bcd feat: build windows in pipeline 2020-05-18 12:13:44 +02:00
66f23bef99 feat: cross compilation for windows 2020-05-18 10:28:06 +02:00
bbf1364e30 feat: tls report cache 2020-05-14 14:12:41 +02:00
6147c214c3 Better error message when request is canceled 2020-05-12 10:49:04 +00:00
f87ca36ffd refactor: tidy up DecodeCharset 2020-05-12 10:12:19 +00:00
37f4e46bdc feat: fallback to latin1 if charset not specified and not utf8 2020-05-12 10:12:19 +00:00
a7b9572e6b Fix reference parsing 2020-05-11 14:48:12 +00:00
4090c490b1 Apply suggestion to pkg/pmapi/messages.go 2020-05-11 14:48:12 +00:00
d33d7237bd Apply suggestion to pkg/pmapi/messages.go 2020-05-11 14:48:12 +00:00
9ed778f2b3 Apply suggestion to pkg/pmapi/messages.go 2020-05-11 14:48:12 +00:00
70fca64a36 Pop-out messageID format into constants 2020-05-11 14:48:12 +00:00
30425d5fcd Fix few typos 2020-05-11 14:48:12 +00:00
2639f7333e Simplify references parsing 2020-05-11 14:48:12 +00:00
4a8d07d54e Update auto-generated files 2020-05-07 19:14:30 +00:00
833fce8702 chore: bump linter 2020-05-07 16:24:10 +00:00
c1a57a2e12 Apply suggestion to internal/store/event_loop.go 2020-05-07 16:19:06 +02:00
cda3000a7a Apply suggestion to internal/store/store_test_exports.go 2020-05-07 16:19:06 +02:00
4b2977041a fix: missing messages after changing primary address 2020-05-07 16:19:06 +02:00
2d200f6f8c test: add test with changing address order 2020-05-07 16:19:06 +02:00
c61e8bdc71 Merge remote-tracking branch 'origin/master' into devel 2020-05-07 15:30:08 +02:00
7e8dc22837 Add build-files into .gitignore 2020-05-06 05:05:35 +00:00
e43bd231ed Final touches of go-imap v1 implementation 2020-05-05 11:47:47 +00:00
1998d92432 Updates() needs to return imapBackend.Update instead of interface with go-imap v1 2020-05-05 11:47:47 +00:00
313e803fdd Implement new SearchCriteria from latest go-imap 2020-05-05 11:47:47 +00:00
cabcb3ae2b Upgrade to latest go-imap-quota with fix for go-imap v1 2020-05-05 11:47:47 +00:00
e57a3c2a3a Notify about new mailbox 2020-05-05 11:47:47 +00:00
7a87a7ea2f fix: fixes after rebase 2020-05-05 11:47:47 +00:00
c939893131 Unseen is first sequence number of unseen message not count of messages 2020-05-05 11:47:47 +00:00
ea0f3115a3 usage of latest upstream go-imap 2020-05-05 11:47:47 +00:00
3d3b91b242 Merge commit comments 2020-05-05 11:20:39 +00:00
cd38c86b4b CI build mac 2020-05-05 11:20:39 +00:00
2379598078 fix: Build without GNU sed 2020-05-05 11:08:08 +00:00
984b28e8f9 User Agent do not contain bridge version, only client in format 2020-05-05 11:00:18 +00:00
1d49a484a8 test: fix typo 2020-05-04 12:31:51 +02:00
99aabf07b3 Apply suggestion to pkg/config/pmapi_prod.go 2020-05-04 07:53:55 +00:00
6e537db5ff Apply suggestion to pkg/pmapi/client.go 2020-05-04 07:53:55 +00:00
668fc7f039 feat: MinSpeed -> MinBytesPerSecond, check every 3 seconds 2020-05-04 07:53:55 +00:00
284a097d4f fix: lower min speed 2020-05-04 07:53:55 +00:00
e5944518ca chore: improve logging 2020-05-04 07:37:51 +00:00
df3a9ea19e test: add comment for why tests are disabled 2020-05-04 07:37:51 +00:00
2db1b113e0 fix: correct timeouts according to spec 2020-05-04 07:37:51 +00:00
68d2591c73 test: fix tls tests 2020-05-04 07:37:51 +00:00
e9735c6110 refactor: set app version when enabling remote tls issue reporting 2020-05-04 07:37:51 +00:00
0fd5ca3a24 feat: dialer refactor to support modular dialing/checking/proxying 2020-05-04 07:37:51 +00:00
8c2f88fe70 Apply suggestion to Changelog.md 2020-04-30 09:20:03 +00:00
23f492705b fix: better draft detection for parentID 2020-04-30 09:20:03 +00:00
44233e5bd3 fix 404 errors by using absolute urls 2020-04-30 09:12:26 +00:00
33770ce129 fix: crash in fakeapi if user is nil 2020-04-30 09:03:16 +00:00
faec347054 test: use the correct constants.Version in integration tests 2020-04-30 09:44:15 +02:00
9b68625522 Update BUILDS.md to list libsecret dev files
Fixes #11
2020-04-29 15:02:39 +02:00
d42deb2ad5 fix: variable name in readme 2020-04-28 12:39:05 +00:00
bb5227c1f4 fix: app version and variable location 2020-04-28 12:39:05 +00:00
0589f329e9 refactor: dedicated constants package, no explicit bridge version 2020-04-28 12:39:05 +00:00
522cadb8b1 refactor: dedicated constants package, no explicit bridge version 2020-04-28 12:39:05 +00:00
32ca7b3903 fix: envvar conflict on fedora 2020-04-28 12:39:05 +00:00
7d30459417 test: empty auth update channel in tests 2020-04-28 12:21:54 +00:00
8f15041d8f fix: race condition when updating user auth 2020-04-28 12:21:54 +00:00
51846efed5 Merge branch 'release/v1.2.7' into devel 2020-04-27 15:54:02 +02:00
a51841158c docs: add libglvnd as build deps 2020-04-22 10:16:22 +02:00
1457005f86 fix: address review comments 2020-04-21 13:29:26 +02:00
febdf98349 test: attempt less flaky tests 2020-04-21 08:36:39 +00:00
d4482994ec fix: missing and incorrect comments 2020-04-21 08:36:39 +00:00
244a18ac8c feat: update changelog 2020-04-21 08:36:39 +00:00
e027aa5fae test: use clientmanager to logout fakeapi 2020-04-21 08:36:39 +00:00
99635cd56d feat: max retries of 5 for client logout 2020-04-21 08:36:39 +00:00
e95aece6d3 refactor: don't pass client directly to store syncer 2020-04-21 08:36:39 +00:00
38f0425670 refactor: make sentry report its own package 2020-04-21 08:36:39 +00:00
4809d97cb1 feat: clientmanager has checkconnection 2020-04-21 08:36:39 +00:00
bfc4069df4 feat: remove user from bridge users list if init failed 2020-04-21 08:36:39 +00:00
3f32fd95e0 feat: refresh expired access tokens in one goroutine 2020-04-21 08:36:39 +00:00
40e96b9d1e feat: retry client auth delete while api is unreachable 2020-04-21 08:36:39 +00:00
80f4e1e346 Fixing unit tests for client manager.
* [x] pmapi: refresh auth uid won't change
* [x] bridge tests:
    * update mocks
    * delete auth when FinishLogin fails
    * check for mailbox password
    * add `gomock.InOrder` for better test control
* [x] fix linter issues except TODOs
* [x] make rootScheme unexported
* [x] store tests: update mocks
2020-04-21 08:36:39 +00:00
debd374d75 fix: don't delete uid of anonymous clients 2020-04-21 08:36:39 +00:00
ed8595fa5b test: some work on integration tests (fake) 2020-04-21 08:36:39 +00:00
fec5f2d3c3 test: fix most integration tests (live) 2020-04-21 08:36:39 +00:00
bafd4e714e refactor: remove unnecessary getters 2020-04-21 08:36:39 +00:00
d787d8b223 fix: use clientsLocker mutex 2020-04-21 08:36:39 +00:00
abca7284dd refactor: make getHost and getScheme private 2020-04-21 08:36:39 +00:00
db02eb694d refactor: no more pmapifactory 2020-04-21 08:36:39 +00:00
5bf4d9c6f5 refactor: prefer anonymous clients 2020-04-21 08:36:39 +00:00
b01be382fc refactor: GetBridgeAuthChannel --> GetAuthUpdateChannel 2020-04-21 08:36:38 +00:00
042c340881 feat: make store use ClientManager 2020-04-21 08:36:38 +00:00
f269be4291 refactor: make pmapi.Client the interface 2020-04-21 08:36:38 +00:00
6e38a65bd8 feat: improve login flow 2020-04-21 08:36:38 +00:00
941e09079c feat: implement token expiration watcher 2020-04-21 08:36:38 +00:00
ce29d4d74e feat: switch to proxy when need be 2020-04-21 08:36:38 +00:00
f239e8f3bf feat: central auth channel for clients 2020-04-21 08:36:38 +00:00
0a55fac29a feat: simple client manager 2020-04-21 08:36:38 +00:00
909 changed files with 66630 additions and 36027 deletions

2
.gitattributes vendored
View File

@ -1 +1 @@
Changelog.md merge=union
unreleased.md merge=union

View File

@ -27,6 +27,9 @@ Issue tracker is ONLY used for reporting bugs with technical details. "It doesn'
3.
4.
## Version Information
<!--- Which version of the app(s) were you using when you experienced this issue? -->
## Context (Environment)
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->

24
.gitignore vendored
View File

@ -6,9 +6,6 @@
.*.sw?
*~
# Compiled Object files, Static and Dynamic libs (Shared Objects)
vendor
# Test files
godog.test
debug.test
@ -17,9 +14,18 @@ coverage.html
# Run files
mem.pprof
# Auto generated frontend
frontend/qml/BridgeUI/*.qmlc
frontend/qml/ProtonUI/*.qmlc
frontend/qml/ProtonUI/fontawesome.ttf
frontend/qml/ProtonUI/images
frontend/qml/*.qmlc
# Auto generated
internal/**/credits.go
vendor
vendor-cache
/main.go
# Build files
/launcher-*
/bridge_*_*.tgz
/ie_*_*.tgz
/versioner
/hasher
cmd/Desktop-Bridge/deploy
cmd/Import-Export/deploy

View File

@ -1,4 +1,4 @@
image: gitlab.protontech.ch:4567/go/bridge/ci
image: gitlab.protontech.ch:4567/go/bridge-internal:latest
before_script:
- eval $(ssh-agent -s)
@ -13,38 +13,15 @@ before_script:
cache:
key: go-mod
paths:
- .cache
- .cache
policy: pull
stages:
- image
- cache
- test
- build
- mirror
# Stage: IMAGE
build-ci-image:
stage: image
image: docker:stable
before_script: []
cache: {}
tags:
- heavy
only:
changes:
- ci/*
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker info
- docker build -t gitlab.protontech.ch:4567/go/bridge/ci:latest ci
- docker push gitlab.protontech.ch:4567/go/bridge/ci:latest
# Stage: CACHE
# This will ensure latest dependency versions and updates the cache for
@ -54,11 +31,11 @@ cache-push:
only:
- branches
script:
- echo ""
- echo ""
cache:
key: go-mod
paths:
- .cache
- .cache
# Stage: TEST
@ -66,10 +43,16 @@ lint:
stage: test
only:
- branches
before_script:
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
script:
- make lint
- env GOMAXPROCS=$(( ${CI_TAG_CPU} / 2 )) make lint
tags:
- medium
test:
test-linux:
stage: test
only:
- branches
@ -83,6 +66,16 @@ test:
- pass init `gpg --list-keys | grep "^ " | tail -1 | tr -d '[:space:]'`
# Then finally run the tests
- make test
tags:
- medium
test-windows:
extends: .build-windows-base
stage: test
only:
- branches
script:
- make test
test-integration:
stage: test
@ -90,6 +83,8 @@ test-integration:
- branches
script:
- VERBOSITY=debug make -C test test
tags:
- large
dependency-updates:
stage: test
@ -98,18 +93,130 @@ dependency-updates:
# Stage: BUILD
build-linux:
stage: build
# Test build every time (= we want to know build is possible).
build-qml:
tags:
- small
only:
- branches
stage: build
artifacts:
name: "bridge-qml-$CI_COMMIT_SHORT_SHA"
expire_in: 1 day
paths:
- bridge_qml.tgz
script:
- cd internal/frontend/qml
- tar -cvzf ../../../bridge_qml.tgz ./*
.build-base:
stage: build
only:
- manual
before_script:
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
script:
- make build
- git diff && git diff-index --quiet HEAD
artifacts:
name: "bridge-linux-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA"
# 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
tags:
- large
build-linux:
extends: .build-base
artifacts:
name: "bridge-linux-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
expire_in: 2 week
build-linux-qa:
extends: .build-base
only:
- web
- branches
script:
- BUILD_TAGS="build_qa" make build
artifacts:
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
.build-darwin-base:
extends: .build-base
before_script:
- export PATH=/usr/local/bin:$PATH
- export PATH=/usr/local/opt/git/bin:$PATH
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
- export PATH=/usr/local/opt/go@1.13/bin:$PATH
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
- export GOPATH=~/go
- export PATH=$GOPATH/bin:$PATH
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
cache: {}
tags:
- macOS
build-darwin:
extends: .build-darwin-base
artifacts:
name: "bridge-darwin-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
build-darwin-qa:
extends: .build-darwin-base
only:
- web
- branches
script:
- BUILD_TAGS="build_qa" make build
artifacts:
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
.build-windows-base:
extends: .build-base
before_script:
- export GOROOT=/c/Go
- export PATH=$GOROOT/bin:$PATH
- export GOARCH=amd64
- export GOPATH=~/go
- export GO111MODULE=on
- export PATH=$GOPATH/bin:$PATH
- export MSYSTEM=
- export PATH=$PATH:/c/grrrQt/5.13.2/mingw73_64/bin
tags:
- windows-bridge
build-windows:
extends: .build-windows-base
artifacts:
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
build-windows-qa:
extends: .build-windows-base
only:
- web
- branches
script:
- BUILD_TAGS="build_qa" make build
artifacts:
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
paths:
- bridge_*.tgz
# Stage: MIRROR
mirror-repo:
stage: mirror

0
.gitmodules vendored
View File

View File

@ -1,16 +1,17 @@
---
run:
timeout: 10m
build-tags:
- nogui
skip-dirs:
- pkg/mime
issues:
exclude-use-default: false
exclude:
- Using the variable on range scope `tt` in function literal
- should have comment (\([^)]+\) )?or be unexported # For now we are missing a lot of comments.
- at least one file in a package should have a package comment # For now we are missing a lot of comments.
- Using the variable on range scope `tt` in function literal
# For now we are missing a lot of comments.
- should have comment (\([^)]+\) )?or be unexported
# For now we are missing a lot of comments.
- at least one file in a package should have a package comment
exclude-rules:
- path: _test\.go
@ -21,10 +22,16 @@ issues:
- gochecknoinits
- gosec
linters-settings:
godox:
keywords:
- TODO
- FIXME
linters:
# setting disable-all will make only explicitly enabled linters run
disable-all: true
enable:
- deadcode # Finds unused code [fast: true, auto-fix: false]
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
@ -43,24 +50,59 @@ linters:
- 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]
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
- interfacer # Linter that suggests narrower interface types [fast: true, auto-fix: false]
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
- prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
- scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
- unparam # Reports unused function parameters [fast: true, auto-fix: false]
- whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
#- wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
#- lll # Reports long lines [fast: true, auto-fix: false]
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
- godot # Check if comments end in a period [fast: true, auto-fix: true]
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
- goprintffuncname # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
- rowserrcheck # checks whether Err of rows is checked successfully [fast: false, auto-fix: false]
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false]
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false]
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
# - wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
# - lll # Reports long lines [fast: true, auto-fix: false]
# Consider to include:
# - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
# - cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
# - errorlint # go-errorlint is a source code linter for Go software that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
# - exhaustivestruct # Checks if all struct's fields are initialized [fast: false, auto-fix: false]
# - forbidigo # Forbids identifiers [fast: true, auto-fix: false]
# - gci # Gci control golang package import order and make it always deterministic. [fast: true, auto-fix: true]
# - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
# - goerr113 # Golang linter to check the errors handling expressions [fast: false, auto-fix: false]
# - gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
# - gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
# - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
# - ifshort # Checks that your code uses short syntax for if-statements whenever possible [fast: true, auto-fix: false]
# - nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
# - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
# - noctx # noctx finds sending http request without context.Context [fast: false, auto-fix: false]
# - nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
# - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test [fast: true, auto-fix: false]
# - testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false]
# - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
# - wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]

View File

@ -1,23 +1,37 @@
# Building ProtonMail Bridge app
# Building Proton Mail Bridge and Import-Export app
## Prerequisites
* 64-bit AMD OS:
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
- the Apple M1 builds are not supported yet due to dependencies
* Go 1.13
* 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/)
* GCC (linux, 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.
Otherwise, the sending of crash reports will be disabled.
## Build
* for Windows please unset the `MSYSTEM` variable
In order to build Bridge or Import-Export app with Qt interface we are using
[Qt Go Binding](https://github.com/therecipe/qt). The dependencies and
installation of this tool is part of `make build` target. If you have issues
with installation of therecipe/qt we recommend to follow [this
wiki](https://github.com/therecipe/qt/wiki/Installation-on-Linux)
Please note that `$(go env GOPATH)/bin` must be in your `PATH` to ensure
binaries installed by `therecipe/qt` (such as `qtdeploy`) are found. Also,
before you start build **on Windows**, please unset the `MSYSTEM` variable
```bash
export MSYSTEM=
```
### Build Bridge
* in project root run
```bash
@ -25,9 +39,44 @@ make build
```
* The result will be stored in `./cmd/Destop-Bridge/deploy/${GOOS}/`
* for `linux`, the binary will have the name of the project directory (e.g `bridge`)
* for `windows`, the binary will have the file extension `.exe` (e.g `bridge.exe`)
* for `darwin`, the application will be created with name of the project directory (e.g `bridge.app`)
* for `linux`, the binary will have the name of the project directory (e.g `proton-bridge`)
* for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`)
* for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`)
#### Build Bridge without GUI
* If you need to build bridge without Qt dependencies, you can do so by running
```bash
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
### Build Import-Export
* in project root run
```bash
make build-ie
```
* The result will be stored in `./cmd/Import-Export/deploy/${GOOS}/`
* for `linux`, the binary will have the name of the project directory (e.g `proton-bridge`)
* for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`)
* for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`)
### Launchers
Launchers are only included in official distributions and provide the public
key used to verify signed app binaries, allowing the automatic update feature.
See README for more information.
### Tags
Note that repository contains both Bridge and Import-Export apps and they are
not released together. Therefore, each app has own tag prefix. Bridge tags
starts with `br-` and Import-Export tags starts with `ie-`. Both tags continue
with semantic versioning `MAJOR.MINOR.PATCH`. An example of full tag is
`br-1.4.4` or `ie-1.1.2` (current versions in October 2020).
## Useful tests, lints and checks
In order to be able to run following commands please install the development dependencies:

View File

@ -2,8 +2,7 @@
By making a contribution to this project:
1. I assign any and all copyright related to the contribution to
Proton Technologies AG;
1. I assign any and all copyright related to the contribution to Proton AG;
2. I certify that the contribution was created in whole by me;
3. I understand and agree that this project and the contribution are public
and that a record of the contribution (including all personal information I

View File

@ -1,73 +0,0 @@
# Copying
Copyright (c) 2020 Proton Technologies AG
ProtonMail Bridge is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
ProtonMail Bridge is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
ProtonMail Bridge. If not, see https://www.gnu.org/licenses.
# Dependencies
ProtonMail Bridge app includes the following libraries from Proton Technologies AG:
* [GopenPGP library](https://gopenpgp.org/) | The [MIT License](https://github.com/ProtonMail/gopenpgp/blob/master/LICENSE).
ProtonMail Bridge includes the following 3rd party software:
* [The Go Project libraries](https://golang.org/project/) | Available under [BSD license](https://golang.org/LICENSE)
* [Qt Go binding](https://github.com/therecipe/qt) | Available under [LGPLv3 license](https://github.com/therecipe/qt/blob/master/LICENSE)
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
* [Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/) | Available under [multiple licenses](https://fontawesome.com/v4.7.0/license/)
* [notificator](github.com/0xAX/notificator) | Available under [license](https://github.com/0xAX/notificator/blob/master/LICENSE)
* [ishell](github.com/abiosoft/ishell) | Available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE)
* [readline](github.com/abiosoft/readline) | Available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
* [singleinstance](github.com/allan-simon/go-singleinstance) | Available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
* [cascadia](github.com/andybalholm/cascadia) | Available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
* [gocertifi](github.com/certifi/gocertifi) | Available under [license](https://github.com/certifi/gocertifi/blob/master/LICENSE)
* [logex](github.com/chzyer/logex) | Available under [license](https://github.com/chzyer/logex/blob/master/LICENSE)
* [test](github.com/chzyer/test) | Available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
* [godog](github.com/cucumber/godog) | Available under [license](https://github.com/cucumber/godog/blob/master/LICENSE)
* [wincred](github.com/danieljoos/wincred) | Available under [license](https://github.com/danieljoos/wincred/blob/master/LICENSE)
* [credential-helpers](github.com/docker/docker-credential-helpers) | Available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
* [imap](github.com/emersion/go-imap) | Available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
* [imap-appendlimit](github.com/emersion/go-imap-appendlimit) | Available under [license](https://github.com/emersion/go-imap-appendlimit/blob/master/LICENSE)
* [imap-idle](github.com/emersion/go-imap-idle) | Available under [license](https://github.com/emersion/go-imap-idle/blob/master/LICENSE)
* [imap-quota](github.com/emersion/go-imap-quota) | Available under [license](https://github.com/emersion/go-imap-quota/blob/master/LICENSE)
* [imap-specialuse](github.com/emersion/go-imap-specialuse) | Available under [license](https://github.com/emersion/go-imap-specialuse/blob/master/LICENSE)
* [sasl](github.com/emersion/go-sasl) | Available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
* [smtp](github.com/emersion/go-smtp) | Available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
* [textwrapper](github.com/emersion/go-textwrapper) | Available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
* [vcard](github.com/emersion/go-vcard) | Available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [color](github.com/fatih/color) | Available under [license](https://github.com/fatih/color/blob/master/LICENSE)
* [shlex](github.com/flynn-archive/go-shlex) | Available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
* [raven](github.com/getsentry/raven-go) | Available under [license](https://github.com/getsentry/raven-go/blob/master/LICENSE)
* [resty](github.com/go-resty/resty/v2) | Available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
* [mock](github.com/golang/mock) | Available under [license](https://github.com/golang/mock/blob/master/LICENSE)
* [cmp](github.com/google/go-cmp) | Available under [license](https://github.com/google/go-cmp/blob/master/LICENSE)
* [gopherjs](github.com/gopherjs/gopherjs) | Available under [license](https://github.com/gopherjs/gopherjs/blob/master/LICENSE)
* [multierror](github.com/hashicorp/go-multierror) | Available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
* [bcrypt](github.com/jameskeane/bcrypt) | Available under [license](https://github.com/jameskeane/bcrypt/blob/master/LICENSE)
* [html2text](github.com/jaytaylor/html2text) | Available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
* [enmime](github.com/jhillyerd/enmime) | Available under [license](https://github.com/jhillyerd/enmime/blob/master/LICENSE)
* [osext](github.com/kardianos/osext) | Available under [license](https://github.com/kardianos/osext/blob/master/LICENSE)
* [keychain](github.com/keybase/go-keychain) | Available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [aurora](github.com/logrusorgru/aurora) | Available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
* [dns](github.com/miekg/dns) | Available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [uuid](github.com/myesui/uuid) | Available under [license](https://github.com/myesui/uuid/blob/master/LICENSE)
* [jsondiff](github.com/nsf/jsondiff) | Available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
* [logrus](github.com/sirupsen/logrus) | Available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
* [golang](github.com/skratchdot/open-golang) | Available under [license](https://github.com/skratchdot/open-golang/blob/master/LICENSE)
* [testify](github.com/stretchr/testify) | Available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
* [uuid](github.com/twinj/uuid) | Available under [license](https://github.com/twinj/uuid/blob/master/LICENSE)
* [cli](github.com/urfave/cli) | Available under [license](https://github.com/urfave/cli/blob/master/LICENSE)
* [BBolt](https://pkg.go.dev/go.etcd.io/bbolt/?tab=doc) | Available under [license](https://pkg.go.dev/go.etcd.io/bbolt?tab=licenses#LICENSE)
* [testify.v1](https://gopkg.in/stretchr/testify.v1) | Available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)

93
COPYING_NOTES.md Normal file
View File

@ -0,0 +1,93 @@
# Copying
Copyright (c) 2022 Proton AG
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.
# Dependencies
Proton Mail Bridge includes the following 3rd party software:
* [The Go Project libraries](https://golang.org/project/) | Available under [BSD license](https://golang.org/LICENSE)
* [Qt](https://www.qt.io/) | Available under [multiple licences](https://www.qt.io/licensing)
<!-- START AUTOGEN -->
* [docker-credential-helpers](https://github.com/docker/docker-credential-helpers) available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
* [go-imap](https://github.com/emersion/go-imap) available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
* [bcrypt](https://github.com/jameskeane/bcrypt) available under [license](https://github.com/jameskeane/bcrypt/blob/master/LICENSE)
* [notificator](https://github.com/0xAX/notificator) available under [license](https://github.com/0xAX/notificator/blob/master/LICENSE)
* [semver](https://github.com/Masterminds/semver/v3) available under [license](https://github.com/Masterminds/semver/v3/blob/master/LICENSE)
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
* [go-imap-id](https://github.com/ProtonMail/go-imap-id) available under [license](https://github.com/ProtonMail/go-imap-id/blob/master/LICENSE)
* [go-rfc5322](https://github.com/ProtonMail/go-rfc5322) available under [license](https://github.com/ProtonMail/go-rfc5322/blob/master/LICENSE)
* [go-srp](https://github.com/ProtonMail/go-srp) available under [license](https://github.com/ProtonMail/go-srp/blob/master/LICENSE)
* [go-vcard](https://github.com/ProtonMail/go-vcard) available under [license](https://github.com/ProtonMail/go-vcard/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)
* [readline](https://github.com/abiosoft/readline) available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
* [go-singleinstance](https://github.com/allan-simon/go-singleinstance) available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
* [logex](https://github.com/chzyer/logex) available under [license](https://github.com/chzyer/logex/blob/master/LICENSE)
* [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
* [godog](https://github.com/cucumber/godog) available under [license](https://github.com/cucumber/godog/blob/master/LICENSE)
* [messages-go](https://github.com/cucumber/messages-go/v16) available under [license](https://github.com/cucumber/messages-go/v16/blob/master/LICENSE)
* [go-sysinfo](https://github.com/elastic/go-sysinfo) available under [license](https://github.com/elastic/go-sysinfo/blob/master/LICENSE)
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
* [go-imap-appendlimit](https://github.com/emersion/go-imap-appendlimit) available under [license](https://github.com/emersion/go-imap-appendlimit/blob/master/LICENSE)
* [go-imap-move](https://github.com/emersion/go-imap-move) available under [license](https://github.com/emersion/go-imap-move/blob/master/LICENSE)
* [go-imap-quota](https://github.com/emersion/go-imap-quota) available under [license](https://github.com/emersion/go-imap-quota/blob/master/LICENSE)
* [go-imap-unselect](https://github.com/emersion/go-imap-unselect) available under [license](https://github.com/emersion/go-imap-unselect/blob/master/LICENSE)
* [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE)
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/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)
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/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)
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
* [mock](https://github.com/golang/mock) available under [license](https://github.com/golang/mock/blob/master/LICENSE)
* [go-cmp](https://github.com/google/go-cmp) available under [license](https://github.com/google/go-cmp/blob/master/LICENSE)
* [uuid](https://github.com/google/uuid) available under [license](https://github.com/google/uuid/blob/master/LICENSE)
* [go-multierror](https://github.com/hashicorp/go-multierror) available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
* [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)
* [text](https://github.com/kr/text) available under [license](https://github.com/kr/text/blob/master/LICENSE)
* [aurora](https://github.com/logrusorgru/aurora) available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
* [go-runewidth](https://github.com/mattn/go-runewidth) available under [license](https://github.com/mattn/go-runewidth/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [pretty](https://github.com/niemeyer/pretty) available under [license](https://github.com/niemeyer/pretty/blob/master/LICENSE)
* [jsondiff](https://github.com/nsf/jsondiff) available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
* [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE)
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
* [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE)
* [du](https://github.com/ricochet2200/go-disk-usage/du) available under [license](https://github.com/ricochet2200/go-disk-usage/du/blob/master/LICENSE)
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
* [qt](https://github.com/therecipe/qt) available under [license](https://github.com/therecipe/qt/blob/master/LICENSE)
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)
* [msgpack](https://github.com/vmihailenco/msgpack/v5) available under [license](https://github.com/vmihailenco/msgpack/v5/blob/master/LICENSE)
* [bbolt](https://go.etcd.io/bbolt) available under [license](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
* [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)
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/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 -->

File diff suppressed because it is too large Load Diff

259
Makefile
View File

@ -1,96 +1,153 @@
export GO111MODULE=on
# By default, the target OS is the same as the host OS,
# but this can be overridden by setting TARGET_OS to "windows"/"darwin"/"linux".
GOOS:=$(shell go env GOOS)
TARGET_CMD?=Desktop-Bridge
TARGET_OS?=${GOOS}
## Build
.PHONY: build build-nogui check-has-go
.PHONY: build build-nogui build-launcher versioner hasher
VERSION?=1.2.7-git
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=2.2.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico
SRC_ICNS:=Bridge.icns
SRC_SVG:=logo.svg
EXE_NAME:=proton-bridge
CONFIGNAME:=bridge
REVISION:=$(shell git rev-parse --short=10 HEAD)
BUILD_TIME:=$(shell date +%FT%T%z)
BUILD_TAGS?=pmapi_prod
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui'
GO_LDFLAGS:=$(addprefix -X main.,Version=${VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
BUILD_FLAGS_GUI:=-tags='${BUILD_TAGS} build_qt'
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/internal/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
ifneq "${BUILD_LDFLAGS}" ""
GO_LDFLAGS+= ${BUILD_LDFLAGS}
GO_LDFLAGS+=${BUILD_LDFLAGS}
endif
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
ifeq "${TARGET_OS}" "windows"
GO_LDFLAGS_LAUNCHER+=-H=windowsgui
endif
GO_LDFLAGS:=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS+= ${GO_LDFLAGS}
BUILD_FLAGS_NOGUI+= ${GO_LDFLAGS}
DEPLOY_DIR:=cmd/Desktop-Bridge/deploy
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS_GUI+=-ldflags '${GO_LDFLAGS}'
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
ICO_FILES:=
EXE:=$(shell basename ${CURDIR})
ifeq "${GOOS}" "windows"
EXE+=.exe
ICO_FILES:=logo.ico icon.rc icon_windows.syso
DIRNAME:=$(shell basename ${CURDIR})
EXE:=${EXE_NAME}
EXE_QT:=${DIRNAME}
ifeq "${TARGET_OS}" "windows"
EXE:=${EXE}.exe
EXE_QT:=${EXE_QT}.exe
RESOURCE_FILE:=resource.syso
endif
ifeq "${GOOS}" "darwin"
ifeq "${TARGET_OS}" "darwin"
DARWINAPP_CONTENTS:=${DEPLOY_DIR}/darwin/${EXE}.app/Contents
EXE:=${EXE}.app/Contents/MacOS/${EXE}
EXE:=${EXE}.app
EXE_QT:=${EXE_QT}.app
EXE_BINARY_DARWIN:=/Contents/MacOS/${EXE_NAME}
endif
EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
EXE_QT_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE_QT}
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
ifdef QT_API
VENDOR_TARGET:=prepare-vendor update-qt-docs
else
VENDOR_TARGET=update-vendor
endif
EXE_TARGET:=${DEPLOY_DIR}/${GOOS}/${EXE}
TGZ_TARGET:=bridge_${GOOS}_${REVISION}.tgz
build: ${TGZ_TARGET}
build-nogui:
go build ${BUILD_FLAGS_NOGUI} -o Desktop-Bridge cmd/Desktop-Bridge/main.go
build-nogui: gofiles
go build ${BUILD_FLAGS} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
${TGZ_TARGET}: ${DEPLOY_DIR}/${GOOS}
ifeq "${GOOS}" "windows"
PRERESOURCECMD:=cp ./resource.syso ./cmd/launcher/resource.syso
POSTRESOURCECMD:=rm -f ./cmd/launcher/resource.syso
endif
build-launcher: ${RESOURCE_FILE}
${PRERESOURCECMD}
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${EXE} ./cmd/launcher/
${POSTRESOURCECMD}
versioner:
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
hasher:
go build -o hasher utils/hasher/main.go
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
rm -f $@
cd ${DEPLOY_DIR} && tar czf ../../../$@ ${GOOS}
cd ${DEPLOY_DIR}/${TARGET_OS} && tar -czvf ../../../../$@ .
${DEPLOY_DIR}/linux: ${EXE_TARGET}
cp -pf ./internal/frontend/share/icons/logo.svg ${DEPLOY_DIR}/linux/
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
cp ./internal/frontend/share/icons/Bridge.icns ${DARWINAPP_CONTENTS}/Resources/
cp -r "utils/addcert.scpt" ${DARWINAPP_CONTENTS}/Resources/
if [ "${DIRNAME}" != "${EXE_NAME}" ]; then \
mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
fi
cp ./internal/frontend/share/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngineCore.framework"
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
${DEPLOY_DIR}/windows: ${EXE_TARGET}
cp ./internal/frontend/share/icons/logo.ico ${DEPLOY_DIR}/windows/
cp ./internal/frontend/share/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
cp LICENSE ${DEPLOY_DIR}/windows/
${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} update-vendor
rm -rf deploy ${GOOS} ${DEPLOY_DIR}
cp cmd/Desktop-Bridge/main.go .
qtdeploy ${BUILD_FLAGS} build desktop
mv deploy cmd/Desktop-Bridge
rm -rf ${GOOS} main.go
QT_BUILD_TARGET:=build desktop
ifneq "${GOOS}" "${TARGET_OS}"
ifeq "${TARGET_OS}" "windows"
QT_BUILD_TARGET:=-docker build windows_64_shared
endif
endif
logo.ico: ./internal/frontend/share/icons/logo.ico
cp $^ .
icon.rc: ./internal/frontend/share/icon.rc
cp $^ .
./internal/frontend/qt/icon_windows.syso: ./internal/frontend/share/icon.rc logo.ico
windres $< $@
icon_windows.syso: ./internal/frontend/qt/icon_windows.syso
cp $^ .
${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR}
cp cmd/${TARGET_CMD}/main.go .
qtdeploy ${BUILD_FLAGS_GUI} ${QT_BUILD_TARGET}
mv deploy cmd/${TARGET_CMD}
if [ "${EXE_QT_TARGET}" != "${EXE_TARGET}" ]; then mv ${EXE_QT_TARGET} ${EXE_TARGET}; fi
rm -rf ${TARGET_OS} main.go
WINDRES_YEAR:=$(shell date +%Y)
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/${SRC_ICO} .FORCE
rm -f ./*.syso
windres --target=pe-x86-64 -I ./internal/frontend/share/ -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
## Rules for therecipe/qt
.PHONY: prepare-vendor update-vendor
THERECIPE_QTVER:=$(shell grep "github.com/therecipe/qt " go.mod | sed -r 's;.* v[0-9\.]+-[0-9]+-([a-f0-9]*).*;\1;')
THERECIPE_ENV:=github.com/therecipe/env_${GOOS}_amd64_513
.PHONY: prepare-vendor update-vendor update-qt-docs
THERECIPE_ENV:=github.com/therecipe/env_${TARGET_OS}_amd64_513
# vendor folder will be deleted by gomod hence we cache the big repo
# therecipe/env in order to download it only once
vendor-cache/${THERECIPE_ENV}:
git clone https://${THERECIPE_ENV}.git vendor-cache/${THERECIPE_ENV}
if [ "${TARGET_OS}" == "darwin" ]; then cp -f "./utils/QTBUG-88600/libqcocoa.dylib" "./vendor-cache/${THERECIPE_ENV}/5.13.0/clang_64/plugins/platforms/"; fi;
# The command used to make symlinks is different on windows.
# So if the GOOS is windows and we aren't crossbuilding (in which case the host os would still be *nix)
# we need to change the LINKCMD to something windowsy.
LINKCMD:=ln -sf ${CURDIR}/vendor-cache/${THERECIPE_ENV} vendor/${THERECIPE_ENV}
ifeq "${GOOS}" "windows"
WINDIR:=$(subst /c/,c:\\,${CURDIR})/vendor-cache/${THERECIPE_ENV}
LINKCMD:=cmd //c 'mklink $(subst /,\,vendor\${THERECIPE_ENV} ${WINDIR})'
WINDIR:=$(subst /c/,c:\\,${CURDIR})/vendor-cache/${THERECIPE_ENV}
LINKCMD:=cmd //c 'mklink $(subst /,\,vendor\${THERECIPE_ENV} ${WINDIR})'
endif
prepare-vendor:
@ -101,10 +158,12 @@ prepare-vendor:
update-vendor: vendor-cache/${THERECIPE_ENV} prepare-vendor
${LINKCMD}
update-qt-docs:
go get github.com/therecipe/qt/internal/binding/files/docs/$(QT_API)
## Dev dependencies
.PHONY: install-devel-tools install-linter install-go-mod-outdated
LINTVER:="v1.23.6"
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
LINTVER:="v1.39.0"
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
@ -120,27 +179,41 @@ install-linter: check-has-go
install-go-mod-outdated:
which go-mod-outdated || go get -u github.com/psampaz/go-mod-outdated
install-git-hooks:
cp utils/githooks/* .git/hooks/
chmod +x .git/hooks/*
## Checks, mocks and docs
.PHONY: check-has-go check-license test bench coverage mocks lint updates doc
.PHONY: check-has-go add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc release-notes
check-has-go:
@which go || (echo "Install Go-lang!" && exit 1)
check-license:
find . -not -path "./vendor/*" -not -name "*mock*.go" -regextype posix-egrep -regex ".*\.go|.*\.qml" -exec grep -L "Copyright (c) 2020 Proton Technologies AG" {} \;
add-license:
./utils/missing_license.sh add
change-copyright-year:
./utils/missing_license.sh change-year
test: gofiles
@# Listing packages manually to not run Qt folder (which needs to run qtsetup first) and integration tests.
go test -coverprofile=/tmp/coverage.out -run=${TESTRUN} \
./internal/api/... \
./internal/bridge/... \
./internal/config/... \
./internal/constants/... \
./internal/cookies/... \
./internal/crash/... \
./internal/events/... \
./internal/frontend/autoconfig/... \
./internal/frontend/cli/... \
./internal/imap/... \
./internal/preferences/... \
./internal/locations/... \
./internal/logging/... \
./internal/metrics/... \
./internal/smtp/... \
./internal/store/... \
./internal/updater/... \
./internal/users/... \
./internal/versioner/... \
./pkg/...
bench:
@ -151,13 +224,31 @@ bench:
coverage: test
go tool cover -html=/tmp/coverage.out -o=coverage.html
mocks:
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/bridge Configer,PreferenceProvider,PanicHandler,PMAPIProvider,CredentialsStorer > internal/bridge/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser > internal/store/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
integration-test-bridge:
${MAKE} -C test test-bridge
lint:
mocks:
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Locator,PanicHandler,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/users/mocks/listener_mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser,ChangeNotifier,Storer > internal/store/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client,Manager > pkg/pmapi/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/message Fetcher > pkg/message/mocks/mocks.go
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog
lint-license:
./utils/missing_license.sh check
lint-dependencies:
./utils/dependency_license.sh check
lint-changelog:
./utils/changelog_linter.sh Changelog.md
lint-golang:
which golangci-lint || $(MAKE) install-linter
$(info linting with GOMAXPROCS=${GOMAXPROCS})
golangci-lint run ./...
updates: install-go-mod-outdated
@ -167,46 +258,64 @@ updates: install-go-mod-outdated
doc:
godoc -http=:6060
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html
release-notes/%.html: release-notes/%.md
./utils/release_notes.sh $^
.PHONY: gofiles
# Following files are for the whole app so it makes sense to have them in bridge package.
# (Options like cmd or internal were considered and bridge package is the best place for them.)
gofiles: ./internal/bridge/credits.go ./internal/bridge/release_notes.go
gofiles: ./internal/bridge/credits.go
./internal/bridge/credits.go: ./utils/credits.sh go.mod
cd ./utils/ && ./credits.sh
./internal/bridge/release_notes.go: ./utils/release-notes.sh ./release-notes/notes.txt ./release-notes/bugs.txt
cd ./utils/ && ./release-notes.sh
cd ./utils/ && ./credits.sh bridge
## Run and debug
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug qmlpreview qt-fronted-clean clean
VERBOSITY?=debug-client
RUN_FLAGS:=-m -l=${VERBOSITY}
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview clean-vendor clean-frontend-qt clean-frontend-qt-common clean
LOG?=debug
LOG_IMAP?=client # client/server/all, or empty to turn it off
LOG_SMTP?=--log-smtp # empty to turn it off
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
run: run-nogui-cli
run-qt: ${EXE_TARGET}
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} | tee last.log
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} 2>&1 | tee last.log
run-qt-cli: ${EXE_TARGET}
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
run-nogui: clean-vendor gofiles
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/Desktop-Bridge/main.go ${RUN_FLAGS} | tee last.log
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log
run-nogui-cli: clean-vendor gofiles
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/Desktop-Bridge/main.go ${RUN_FLAGS} -c
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c
run-debug:
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS_NOGUI}" cmd/Desktop-Bridge/main.go -- ${RUN_FLAGS}
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS} --noninteractive
run-qml-preview:
make -C internal/frontend/qt -f Makefile.local qmlpreview
find internal/frontend/qml/ -iname '*qmlc' | xargs rm -f
bridge_preview internal/frontend/qml/Bridge_test.qml
clean-frontend-qt:
make -C internal/frontend/qt -f Makefile.local clean
$(MAKE) -C internal/frontend -f Makefile.local clean
clean-vendor: clean-frontend-qt
clean-vendor: clean-frontend-qt clean-frontend-qt-common
rm -rf ./vendor
clean: clean-frontend-qt
clean: clean-vendor
rm -rf vendor-cache
rm -rf cmd/Desktop-Bridge/deploy
rm -f build last.log mem.pprof
rm -rf cmd/Import-Export/deploy
rm -f build last.log mem.pprof main.go
rm -f resource.syso
rm -f release-notes/bridge.html
rm -f release-notes/import-export.html
.PHONY: generate
generate:
go generate ./...
$(MAKE) add-license
.FORCE:

View File

@ -1,21 +1,22 @@
# ProtonMail Bridge
Copyright (c) 2020 Proton Technologies AG
# Proton Mail Bridge and Import Export app
Copyright (c) 2022 Proton AG
This repository holds the ProtonMail Bridge application.
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
For a detailed build information see [BUILDS](./BUILDS.md).
For licensing information see [COPYING](./COPYING.md).
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
## Description
ProtonMail Bridge for e-mail clients.
## Description Bridge
Proton Mail Bridge for e-mail clients.
When launched, Bridge will initialize local IMAP/SMTP servers and render
its GUI.
To configure an e-mail client, firstly log in using your ProtonMail credentials.
To configure an e-mail client, firstly log in using your Proton Mail credentials.
Open your e-mail client and add a new account using the settings which are
located in the Bridge GUI. The client will only be able to sync with
your ProtonMail account when the Bridge is running, thus the option
your Proton Mail account when the Bridge is running, thus the option
to start Bridge on startup is enabled by default.
When the main window is closed, Bridge will continue to run in the
@ -23,13 +24,42 @@ background.
More details [on the public website](https://protonmail.com/bridge).
## Description Import-Export app
Proton Mail Import-Export app for importing and exporting messages.
To transfer messages, firstly log in using your Proton Mail credentials.
For import, expand your account, and pick the address to which to import
messages from IMAP server or local EML or MBOX files. For export, pick
the whole account or only a specific address. Then, in both cases,
configure transfer rules (match source and target mailboxes, set time
range limits and so on) and hit start. Once the transfer is complete,
check the results.
More details [on the public website](https://protonmail.com/import-export).
The Import-Export app is developed in separate branch `master-ie`.
## Launchers
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
Official distributions of the Proton Mail Bridge and Import-Export apps contain
both a launcher and the app itself. The launcher is installed in a protected
area of the system (i.e. an area accessible only with admin privileges) and is
used to run the app. The launcher ensures that nobody tampered with the app's
files by verifying their signature using a hardcoded public key. App files are
placed in regular userspace and are signed by Proton's private key. This
feature enables the app to securely update itself automatically without asking
the user for a password.
## Keychain
You need to have a keychain in order to run the ProtonMail Bridge. On Mac or
Windows, Bridge uses native credential managers. On Linux, use
[Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/)
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
or
[pass](https://www.passwordstore.org/).
[pass](https://www.passwordstore.org/). We are working on allowing other secret
services (e.g. KeepassXC), but for now only gnome-keyring is usable without
major problems.
## Environment Variables
@ -37,9 +67,9 @@ or
- `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
- `VERBOSITY`: set log level used during test time and by the makefile.
- `VERSION`: set the bridge app version used during testing or building.
- `VERBOSITY`: set log level used during test time and by the makefile
### Integration testing
- `TEST_ENV`: set which env to use (fake or live)
@ -48,5 +78,34 @@ or
- `FEATURES`: set feature dir, file or scenario to test
## 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`
### 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`

1
TODO.md Normal file
View File

@ -0,0 +1 @@
- when cache is full, we need to stop the watcher? don't want to keep downloading messages and throwing them away when we try to cache them.

View File

@ -1,4 +0,0 @@
FROM gitlab.protontech.ch:4567/protonmail/ci-containers/go
RUN apt-get -y update
RUN apt-get -y install openssh-client libsecret-1-dev libgl1-mesa-dev time connect-proxy

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package main
@ -35,400 +35,40 @@ package main
*/
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/frontend"
"github.com/ProtonMail/proton-bridge/internal/imap"
"github.com/ProtonMail/proton-bridge/internal/pmapifactory"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/internal/smtp"
"github.com/ProtonMail/proton-bridge/pkg/args"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/updates"
"github.com/allan-simon/go-singleinstance"
"github.com/getsentry/raven-go"
"github.com/ProtonMail/proton-bridge/internal/app/base"
"github.com/ProtonMail/proton-bridge/internal/app/bridge"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
// cacheVersion is used for cache files such as lock, events, preferences, user_info, db files.
// Different number will drop old files and create new ones.
const cacheVersion = "c11"
// Following variables are set via ldflags during build.
var (
// Version of the build.
Version = "" //nolint[gochecknoglobals]
// Revision is current hash of the build.
Revision = "" //nolint[gochecknoglobals]
// BuildTime stamp of the build.
BuildTime = "" //nolint[gochecknoglobals]
// AppShortName to make setup
AppShortName = "bridge" //nolint[gochecknoglobals]
// DSNSentry client keys to be able to report crashes to Sentry
DSNSentry = "" //nolint[gochecknoglobals]
)
var (
longVersion = Version + " (" + Revision + ")" //nolint[gochecknoglobals]
buildVersion = longVersion + " " + BuildTime //nolint[gochecknoglobals]
log = config.GetLogEntry("main") //nolint[gochecknoglobals]
// How many crashes in a row.
numberOfCrashes = 0 //nolint[gochecknoglobals]
// After how many crashes bridge gives up starting.
maxAllowedCrashes = 10 //nolint[gochecknoglobals]
const (
appName = "Proton Mail Bridge"
appUsage = "Proton Mail IMAP and SMTP Bridge"
configName = "bridge"
updateURLName = "bridge"
keychainName = "bridge"
cacheVersion = "c11"
)
func main() {
if err := raven.SetDSN(DSNSentry); err != nil {
log.WithError(err).Errorln("Can not setup sentry DSN")
}
raven.SetRelease(Revision)
bridge.UpdateCurrentUserAgent(Version, runtime.GOOS, "", "")
args.FilterProcessSerialNumberFromArgs()
filterRestartNumberFromArgs()
app := cli.NewApp()
app.Name = "Protonmail Bridge"
app.Version = buildVersion
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "log-level, l",
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug, debug-client, debug-server)"},
cli.BoolFlag{
Name: "no-window",
Usage: "Don't show window after start"},
cli.BoolFlag{
Name: "cli, c",
Usage: "Use command line interface"},
cli.BoolFlag{
Name: "noninteractive",
Usage: "Start Bridge entirely noninteractively"},
cli.StringFlag{
Name: "version-json, g",
Usage: "Generate json version file"},
cli.BoolFlag{
Name: "mem-prof, m",
Usage: "Generate memory profile"},
cli.BoolFlag{
Name: "cpu-prof, p",
Usage: "Generate CPU profile"},
}
app.Usage = "ProtonMail IMAP and SMTP Bridge"
app.Action = run
// Always log the basic info about current bridge.
logrus.SetLevel(logrus.InfoLevel)
log.WithField("version", Version).
WithField("revision", Revision).
WithField("runtime", runtime.GOOS).
WithField("build", BuildTime).
WithField("args", os.Args).
WithField("appLong", app.Name).
WithField("appShort", AppShortName).
Info("Run app")
if err := app.Run(os.Args); err != nil {
log.Error("Program exited with error: ", err)
}
}
type panicHandler struct {
cfg *config.Config
err *error // Pointer to error of cli action.
}
func (ph *panicHandler) HandlePanic() {
r := recover()
if r == nil {
return
}
config.HandlePanic(ph.cfg, fmt.Sprintf("Recover: %v", r))
frontend.HandlePanic()
*ph.err = cli.NewExitError("Panic and restart", 666)
numberOfCrashes++
log.Error("Restarting after panic")
restartApp()
os.Exit(666)
}
// run initializes and starts everything in a precise order.
//
// IMPORTANT: ***Read the comments before CHANGING the order ***
func run(context *cli.Context) (contextError error) { // nolint[funlen]
// We need to have config instance to setup a logs, panic handler, etc ...
cfg := config.New(AppShortName, Version, Revision, cacheVersion)
// We want to know about any problem. Our PanicHandler calls sentry which is
// not dependent on anything else. If that fails, it tries to create crash
// report which will not be possible if no folder can be created. That's the
// only problem we will not be notified about in any way.
panicHandler := &panicHandler{cfg, &contextError}
defer panicHandler.HandlePanic()
// First we need config and create necessary folder; it's dependency for everything.
if err := cfg.CreateDirs(); err != nil {
log.Fatal("Cannot create necessary folders: ", err)
}
// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
logLevel := context.GlobalString("log-level")
debugClient, debugServer := config.SetupLog(cfg, logLevel)
// Should be called after logs are configured but before preferences are created.
migratePreferencesFromC10(cfg)
if err := cfg.ClearOldData(); err != nil {
log.Error("Cannot clear old data: ", err)
}
// Doesn't make sense to continue when Bridge was invoked with wrong arguments.
// We should tell that to the user before we do anything else.
if context.Args().First() != "" {
_ = cli.ShowAppHelp(context)
return cli.NewExitError("Unknown argument", 4)
}
// It's safe to get version JSON file even when other instance is running.
// (thus we put it before check of presence of other Bridge instance).
updates := updates.New(AppShortName, Version, Revision, BuildTime, bridge.ReleaseNotes, bridge.ReleaseFixedBugs, cfg.GetUpdateDir())
if dir := context.GlobalString("version-json"); dir != "" {
generateVersionFiles(updates, dir)
return nil
}
// ClearOldData before starting new bridge to do a proper setup.
//
// IMPORTANT: If you the change position of this you will need to wait
// until force-update to be applied on all currently used bridge
// versions
if err := cfg.ClearOldData(); err != nil {
log.Error("Cannot clear old data: ", err)
}
// GetTLSConfig is needed for IMAP, SMTL and local bridge API (to check second instance).
//
// This should be called after ClearOldData, in order to re-create the
// certificates if clean data will remove them (accidentally or on purpose).
tls, err := config.GetTLSConfig(cfg)
base, err := base.New(
appName,
appUsage,
configName,
updateURLName,
keychainName,
cacheVersion,
)
if err != nil {
log.WithError(err).Fatal("Cannot get TLS certificate")
logrus.WithError(err).Fatal("Failed to create app base")
}
pref := preferences.New(cfg)
// Now we can try to proceed with starting the bridge. First we need to ensure
// this is the only instance. If not, we will end and focus the existing one.
lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
if err != nil {
log.Warn("Bridge is already running")
if err := api.CheckOtherInstanceAndFocus(pref.GetInt(preferences.APIPortKey), tls); err != nil {
numberOfCrashes = maxAllowedCrashes
log.Error("Second instance: ", err)
}
return cli.NewExitError("Bridge is already running.", 3)
}
defer lock.Close() //nolint[errcheck]
// In case user wants to do CPU or memory profiles...
if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
f, err := os.Create("cpu.pprof")
if err != nil {
log.Fatal("Could not create CPU profile: ", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("Could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
defer makeMemoryProfile()
}
// Now we initialize all Bridge parts.
log.Debug("Initializing bridge...")
eventListener := listener.New()
events.SetupEvents(eventListener)
credentialsStore, credentialsError := credentials.NewStore()
if credentialsError != nil {
log.Error("Could not get credentials store: ", credentialsError)
}
pmapiClientFactory := pmapifactory.New(cfg, eventListener)
bridgeInstance := bridge.New(cfg, pref, panicHandler, eventListener, Version, pmapiClientFactory, credentialsStore)
imapBackend := imap.NewIMAPBackend(panicHandler, eventListener, cfg, bridgeInstance)
smtpBackend := smtp.NewSMTPBackend(panicHandler, eventListener, pref, bridgeInstance)
go func() {
defer panicHandler.HandlePanic()
apiServer := api.NewAPIServer(pref, tls, cfg.GetTLSCertPath(), cfg.GetTLSKeyPath(), eventListener)
apiServer.ListenAndServe()
}()
go func() {
defer panicHandler.HandlePanic()
imapPort := pref.GetInt(preferences.IMAPPortKey)
imapServer := imap.NewIMAPServer(debugClient, debugServer, imapPort, tls, imapBackend, eventListener)
imapServer.ListenAndServe()
}()
go func() {
defer panicHandler.HandlePanic()
smtpPort := pref.GetInt(preferences.SMTPPortKey)
useSSL := pref.GetBool(preferences.SMTPSSLKey)
smtpServer := smtp.NewSMTPServer(debugClient || debugServer, smtpPort, useSSL, tls, smtpBackend, eventListener)
smtpServer.ListenAndServe()
}()
// Decide about frontend mode before initializing rest of bridge.
var frontendMode string
switch {
case context.GlobalBool("cli"):
frontendMode = "cli"
case context.GlobalBool("noninteractive"):
frontendMode = "noninteractive"
default:
frontendMode = "qt"
}
log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")
// If we are starting bridge in noninteractive mode, simply block instead of starting a frontend.
if frontendMode == "noninteractive" {
<-(make(chan struct{}))
return nil
}
showWindowOnStart := !context.GlobalBool("no-window")
frontend := frontend.New(Version, buildVersion, frontendMode, showWindowOnStart, panicHandler, cfg, pref, eventListener, updates, bridgeInstance, smtpBackend)
// Last part is to start everything.
log.Debug("Starting frontend...")
if err := frontend.Loop(credentialsError); err != nil {
log.Error("Frontend failed with error: ", err)
return cli.NewExitError("Frontend error", 2)
}
if frontend.IsAppRestarting() {
restartApp()
}
return nil
}
// migratePreferencesFromC10 will copy preferences from c10 folder to c11.
// It will happen only when c10/prefs.json exists and c11/prefs.json not.
// No configuration changed between c10 and c11 versions.
func migratePreferencesFromC10(cfg *config.Config) {
pref10Path := config.New(AppShortName, Version, Revision, "c10").GetPreferencesPath()
if _, err := os.Stat(pref10Path); os.IsNotExist(err) {
log.WithField("path", pref10Path).Trace("Old preferences does not exist, migration skipped")
// Other instance already running.
if base == nil {
return
}
pref11Path := cfg.GetPreferencesPath()
if _, err := os.Stat(pref11Path); err == nil {
log.WithField("path", pref11Path).Trace("New preferences already exists, migration skipped")
return
}
data, err := ioutil.ReadFile(pref10Path) //nolint[gosec]
if err != nil {
log.WithError(err).Error("Problem to load old preferences")
return
}
err = ioutil.WriteFile(pref11Path, data, 0644) //nolint[gosec]
if err != nil {
log.WithError(err).Error("Problem to migrate preferences")
return
}
log.Info("Preferences migrated")
}
// generateVersionFiles writes a JSON file with details about current build.
// Those files are used for upgrading the app.
func generateVersionFiles(updates *updates.Updates, dir string) {
log.Info("Generating version files")
for _, goos := range []string{"windows", "darwin", "linux"} {
log.Debug("Generating JSON for ", goos)
if err := updates.CreateJSONAndSign(dir, goos); err != nil {
log.Error(err)
}
}
}
func makeMemoryProfile() {
name := "./mem.pprof"
f, err := os.Create(name)
if err != nil {
log.Error("Could not create memory profile: ", err)
}
if abs, err := filepath.Abs(name); err == nil {
name = abs
}
log.Info("Writing memory profile to ", name)
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Error("Could not write memory profile: ", err)
}
_ = f.Close()
}
// filterRestartNumberFromArgs removes flag with a number how many restart we already did.
// See restartApp how that number is used.
func filterRestartNumberFromArgs() {
tmp := os.Args[:0]
for i, arg := range os.Args {
if !strings.HasPrefix(arg, "--restart_") {
tmp = append(tmp, arg)
continue
}
var err error
numberOfCrashes, err = strconv.Atoi(os.Args[i][10:])
if err != nil {
numberOfCrashes = maxAllowedCrashes
}
}
os.Args = tmp
}
// restartApp starts a new instance in background.
func restartApp() {
if numberOfCrashes >= maxAllowedCrashes {
log.Error("Too many crashes")
return
}
if exeFile, err := os.Executable(); err == nil {
arguments := append(os.Args[1:], fmt.Sprintf("--restart_%d", numberOfCrashes))
cmd := exec.Command(exeFile, arguments...) //nolint[gosec]
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
if err := cmd.Start(); err != nil {
log.Error("Restart failed: ", err)
}
if err := bridge.New(base).Run(os.Args); err != nil {
logrus.WithError(err).Fatal("Bridge exited with error")
}
}

204
cmd/launcher/main.go Normal file
View File

@ -0,0 +1,204 @@
// 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 main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/crash"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/logging"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/internal/versioner"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const (
appName = "Proton Mail Launcher"
configName = "bridge"
exeName = "proton-bridge"
)
func main() { //nolint:funlen
reporter := sentry.NewReporter(appName, constants.Version, useragent.New())
crashHandler := crash.NewHandler(reporter.ReportException)
defer crashHandler.HandlePanic()
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
if err != nil {
logrus.WithError(err).Fatal("Failed to get locations provider")
}
locations := locations.New(locationsProvider, configName)
logsPath, err := locations.ProvideLogsPath()
if err != nil {
logrus.WithError(err).Fatal("Failed to get logs path")
}
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
if err := logging.Init(logsPath); err != nil {
logrus.WithError(err).Fatal("Failed to setup logging")
}
logging.SetLevel(os.Getenv("VERBOSITY"))
updatesPath, err := locations.ProvideUpdatesPath()
if err != nil {
logrus.WithError(err).Fatal("Failed to get updates path")
}
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
if err != nil {
logrus.WithError(err).Fatal("Failed to create new verification key")
}
kr, err := crypto.NewKeyRing(key)
if err != nil {
logrus.WithError(err).Fatal("Failed to create new verification keyring")
}
versioner := versioner.New(updatesPath)
exe, err := getPathToUpdatedExecutable(exeName, versioner, kr, reporter)
if err != nil {
if exe, err = getFallbackExecutable(exeName, versioner); err != nil {
logrus.WithError(err).Fatal("Failed to find any launchable executable")
}
}
launcher, err := os.Executable()
if err != nil {
logrus.WithError(err).Fatal("Failed to determine path to launcher")
}
cmd := exec.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) //nolint:gosec
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// On windows, if you use Run(), a terminal stays open; we don't want that.
if runtime.GOOS == "windows" {
err = cmd.Start()
} else {
err = cmd.Run()
}
if err != nil {
logrus.WithError(err).Fatal("Failed to launch")
}
}
func appendLauncherPath(path string, args []string) []string {
res := append([]string{}, args...)
hasFlag := false
for k, v := range res {
if v != "--launcher" {
continue
}
hasFlag = true
if k+1 >= len(res) {
continue
}
res[k+1] = path
}
if !hasFlag {
res = append(res, "--launcher", path)
}
return res
}
func getPathToUpdatedExecutable(
name string,
versioner *versioner.Versioner,
kr *crypto.KeyRing,
reporter *sentry.Reporter,
) (string, error) {
versions, err := versioner.ListVersions()
if err != nil {
return "", errors.Wrap(err, "failed to list available versions")
}
currentVersion, err := semver.StrictNewVersion(constants.Version)
if err != nil {
logrus.WithField("version", constants.Version).WithError(err).Error("Failed to parse current version")
}
for _, version := range versions {
vlog := logrus.WithField("version", version)
if err := version.VerifyFiles(kr); err != nil {
vlog.WithError(err).Error("Files failed verification and will be removed")
if err := reporter.ReportMessage(fmt.Sprintf("version %v failed verification: %v", version, err)); err != nil {
vlog.WithError(err).Error("Failed to report corrupt update files")
}
if err := version.Remove(); err != nil {
vlog.WithError(err).Error("Failed to remove files")
}
continue
}
// Skip versions that are less or equal to launcher version.
if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) {
continue
}
exe, err := version.GetExecutable(name)
if err != nil {
vlog.WithError(err).Error("Failed to get executable")
continue
}
return exe, nil
}
return "", errors.New("no available newer versions")
}
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
logrus.Info("Searching for fallback executable")
launcher, err := os.Executable()
if err != nil {
return "", errors.Wrap(err, "failed to determine path to launcher")
}
return versioner.GetExecutableInDirectory(name, filepath.Dir(launcher))
}

11
dist/proton-bridge.desktop vendored Normal file
View File

@ -0,0 +1,11 @@
[Desktop Entry]
Type=Application
Version=1.1
Name=Proton Mail Bridge
GenericName=Proton Mail Bridge for Linux
Comment=The Bridge is an application that runs on your computer in the background and seamlessly encrypts and decrypts your mail as it enters and leaves your computer.
Icon=protonmail-bridge
Exec=protonmail-bridge
Terminal=false
Categories=Office;Email;Network
StartupWMClass=protonmail-bridge

View File

@ -51,7 +51,7 @@ PMAPI directly.
graph TD
C["Client (e.g. Thunderbird)"]
PM[ProtonMail Server]
PM[Proton Mail Server]
subgraph "Bridge app"
subgraph "Bridge core"

View File

@ -1,12 +1,12 @@
# Encryption
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
in PMAPI and bridge utils (in pacakge such as messages). All packages are using our high-level
GopenPGP library on top of openpgp.
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
GopenPGP library on top of OpenPGP.
## `gopenpgp.KeyRing`
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
key is always on the first position, then there old ones to be able to decrypt last e-mail.
Openpgp encrypts given message with all available keys, so we need to first get first (primary)
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
key for encryption to have message encrypted only once with primary key.

View File

@ -1,9 +1,9 @@
# Documentation
# Bridge Documentation
Documentation pages in order to read for a novice:
* [Development cycle](development.md)
* [Bridge code](bridge.md)
* [Internal Bridge database](database.md)
* [Communication between Bridge, Client and Server](communication.md)
* [Encryption](encryption.md)

103
doc/updates.md Normal file
View File

@ -0,0 +1,103 @@
# Update mechanism of Bridge
There are mulitple options how to change version of application:
* Automatic in-app update
* Manual in-app update
* Manual install
In-app update ends with restarting bridge into new version. Automatic in-app
update is downloading, verifying and installing the new version immediatelly
without user confirmation. For manual in-app update user needs to confirm first.
Update is done from special update file published on website.
The manual installation requires user to download, verify and install manually
using installer for given OS.
The bridge is installed and executed differently for given OS:
* Windows and Linux apps are using launcher mechanism:
* There is system protected installation path which is created on first
install. It contains bridge exe and launcher exe. When users starts
bridge the launcher is executed first. It will check update path compare
version with installed one. The newer version then is then executed.
* Update mechanism means to replace files in update folder which is located
in user space.
* macOS app does not use launcher
* No launcher, only one executable
* In-App udpate replaces the bridge files in installation path directly
```mermaid
flowchart LR
subgraph Frontend
U[User requests<br>version check]
ManIns((Notify user about<br>manual install<br>is needed))
R((Notify user<br>about restart))
ManUp((Notify user about<br>manual update))
NF((Notify user about<br>force update))
ManUp -->|Install| InstFront[Install]
InstFront -->|Ok| R
InstFront -->|Error| ManIns
U --> CheckFront[Check online]
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
CheckFront -->|Error| ManIns
IAFront -->|No| Latest((Notify user<br>has latest version))
IAFront -->|Yes| CanInstall{Can update?}
CanInstall -->|No| ManIns
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
NotifOrInstall -->|Manual| ManUp
end
subgraph Backend
W[Wait for next check]
W --> Check[Check online]
Check --> NV{Has new<br>version?}
Check -->|Error| W
NV -->|No new version| W
IA{Is install<br>applicable?}
NV -->|New version<br>available| IA
IA -->|Local rollout<br>not enough| W
IA -->|Yes| AU{Is automatic\nupdate enabled?}
AU -->|Yes| CanUp{Can update?}
CanUp -->|No| ManIns
CanUp -->|Yes| Ins[Install]
Ins -->|Error| ManIns
Ins -->|Ok| R
AU -->|No| ManUp
ManUp -->|Ignore| W
F[Force update]
F --> NF
end
ManIns --> Web[Open web page]
NF --> Web
ManUp --> Web
R --> Re[Restart]
NF --> Q[Quit bridge]
NotifOrInstall -->|Automatic| W
```
The non-trivial is to combine the update with setting change:
* turn off/on automatic in-app updates
* change from stable to beta or back
_TODO fill flow chart details_
We are not support downgrade functionality. Only some circumstances can lead to
downgrading the app version.
_TODO fill flow chart details_

114
go.mod
View File

@ -1,79 +1,85 @@
module github.com/ProtonMail/proton-bridge
go 1.13
go 1.15
// These dependencies are `replace`d below, so the version numbers should be ignored.
// They are in a separate require block to highlight this.
require (
github.com/docker/docker-credential-helpers v0.0.0-00010101000000-000000000000
github.com/emersion/go-imap v0.0.0-20171113213225-939ec3994dbe
github.com/emersion/go-imap-quota v0.0.0-20171113212021-e883a2bc54d6
github.com/emersion/go-smtp v0.0.0-20180712174835-db5eec195e67
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
github.com/docker/docker-credential-helpers v0.6.3
github.com/emersion/go-imap v1.0.6
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 // indirect
)
require (
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
github.com/ProtonMail/go-appdir v1.1.0
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6
github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
github.com/ProtonMail/go-imap-id v0.0.0-20171219160728-ed0baee567ee
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
github.com/ProtonMail/go-rfc5322 v0.8.0
github.com/ProtonMail/go-srp v0.0.1
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
github.com/ProtonMail/gopenpgp v1.0.1-0.20190912180537-d398098113ed
github.com/ProtonMail/gopenpgp/v2 v2.4.1
github.com/PuerkitoBio/goquery v1.5.1
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
github.com/andybalholm/cascadia v1.1.0
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/cucumber/godog v0.8.1
github.com/danieljoos/wincred v1.0.2 // indirect
github.com/emersion/go-imap-appendlimit v0.0.0-20160923165328-beeb382f2a42
github.com/emersion/go-imap-idle v0.0.0-20161227184850-e03ba1e0ed89
github.com/emersion/go-imap-move v0.0.0-20161227183138-88aef42b0f1d
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
github.com/emersion/go-imap-unselect v0.0.0-20161227183655-1e6dc73ac8fe
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe
github.com/cucumber/godog v0.12.1
github.com/cucumber/messages-go/v16 v16.0.1
github.com/elastic/go-sysinfo v1.7.1
github.com/elastic/go-windows v1.0.1 // indirect
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.14.0
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
github.com/fatih/color v1.9.0
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/getsentry/raven-go v0.2.0
github.com/go-resty/resty/v2 v2.2.0
github.com/golang/mock v1.4.3
github.com/google/go-cmp v0.4.0
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
github.com/hashicorp/go-multierror v1.0.0
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195
github.com/jhillyerd/enmime v0.8.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/keybase/go-keychain v0.0.0-20200218013740-86d4642e4ce2
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381
github.com/miekg/dns v1.1.29
github.com/myesui/uuid v1.0.0 // indirect
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6
github.com/getsentry/sentry-go v0.12.0
github.com/go-resty/resty/v2 v2.6.0
github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.4.4
github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.1.1
github.com/hashicorp/go-multierror v1.1.0
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
github.com/keybase/go-keychain v0.0.0
github.com/kr/text v0.2.0 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/miekg/dns v1.1.41
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.5.1
github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200126204426-5074eb6d8c41 // indirect
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200126204426-5074eb6d8c41 // indirect
github.com/twinj/uuid v1.0.0 // indirect
github.com/urfave/cli v1.22.3
go.etcd.io/bbolt v1.3.3
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/text v0.3.2
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
github.com/sirupsen/logrus v1.7.0
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.7.0
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
github.com/urfave/cli/v2 v2.2.0
github.com/vmihailenco/msgpack/v5 v5.1.3
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sys v0.0.0-20220111092808-5a964db01320
golang.org/x/text v0.3.7
howett.net/plist v1.0.0 // indirect
)
replace (
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.0.0
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20190327080220-0e686f0e855f
github.com/emersion/go-imap-quota => github.com/ProtonMail/go-imap-quota v0.0.0-20171219161528-20f0ba8904de
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20190604143603-d3d8a14a4d4f
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/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
)

722
go.sum
View File

@ -1,175 +1,408 @@
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=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
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=
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/crypto v0.0.0-20190604143603-d3d8a14a4d4f h1:cFhATQTJGK2iZ0dc+jRhr75mh6bsc5Ug6NliaBya8Kw=
github.com/ProtonMail/crypto v0.0.0-20190604143603-d3d8a14a4d4f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
github.com/ProtonMail/docker-credential-helpers v1.0.0 h1:0DQXbZNvUszWgXUuP7TzvQdwnkK1D5Zf/glBgCFJFCk=
github.com/ProtonMail/docker-credential-helpers v1.0.0/go.mod h1:R1gQindzdYFcWJuuGXteYHDJzUCVtyU+EpEqp9aWcFs=
github.com/ProtonMail/go-appdir v1.0.0 h1:PZXQ0HkveuEugga3LeDycxWtybrXQfKR0ThxURd6ojw=
github.com/ProtonMail/go-appdir v1.0.0/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
github.com/ProtonMail/go-appdir v1.1.0 h1:9hdNDlU9kTqRKVNzmoqah8qqrj5QZyLByQdwQNlFWig=
github.com/ProtonMail/go-appdir v1.1.0/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6 h1:YsSJ/mvZFYydQm/hRrt8R8UtgETixN2y3LK98f5LT60=
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6/go.mod h1:EtDfBMIDWmVe4viZCuBTEfe3OIIo0ghbpOaAZVO+hVg=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
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/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-imap v0.0.0-20190327080220-0e686f0e855f h1:QkLm4yfhBQuBxrC46Vhy2sonOWVrwIJo5bgKpA82+TY=
github.com/ProtonMail/go-imap v0.0.0-20190327080220-0e686f0e855f/go.mod h1:+m2uLXghuYktgE/vc5AkmCxx1qhu33ZKHFWg1cGZPD0=
github.com/ProtonMail/go-imap-id v0.0.0-20171219160728-ed0baee567ee h1:Q/nK7A9xzUimAZsQDa/yaw3xW9PkTTnJnkT5wAkXrmI=
github.com/ProtonMail/go-imap-id v0.0.0-20171219160728-ed0baee567ee/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
github.com/ProtonMail/go-imap-quota v0.0.0-20171219161528-20f0ba8904de h1:+LA9teDYUwGkBvg0kqZPZetmxIv1r7s9/npBP1yzKs0=
github.com/ProtonMail/go-imap-quota v0.0.0-20171219161528-20f0ba8904de/go.mod h1:85zbnYVWIY7//iScX9fnB/kKOGH9B86YPqtpr7f1i7A=
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72 h1:hGCc4Oc2fD3I5mNnZ1VlREncVc9EXJF8dxW3sw16gWM=
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309 h1:2pzfKjhBjSnw3BgmfTYRFQr1rFGxhfhUY0KKkg+RYxE=
github.com/ProtonMail/go-smtp v0.0.0-20181206232543-8261df20d309/go.mod h1:6UoBvDAMA/cTBwS3Y7tGpKnY5RH1F1uYHschT6eqAkI=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
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-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
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-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
github.com/ProtonMail/gopenpgp v1.0.1-0.20190912180537-d398098113ed h1:3gib6hGF61VfRu7cqqkODyRUgES5uF/fkLQanPPJiO8=
github.com/ProtonMail/gopenpgp v1.0.1-0.20190912180537-d398098113ed/go.mod h1:NstNbZx1OIoyq+2qHAFLwDFpHbMk8L2i2Vr+LioJ3/g=
github.com/ProtonMail/gopenpgp/v2 v2.4.1 h1:b3El0zabaKi73u4sRnb3hOOUczuKuYpN8wnp7wRsZSc=
github.com/ProtonMail/gopenpgp/v2 v2.4.1/go.mod h1:RFjoVjfhV8f78tjz/fLrp/OXkugL3QmWsiJq/fsQYA4=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/aslakhellesoy/gox v1.0.100/go.mod h1:AJl542QsKKG96COVsv0N74HHzVQgDIQPceVUh1aeU2M=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cucumber/gherkin-go/v11 v11.0.0 h1:cwVwN1Qn2VRSfHZNLEh5x00tPBmZcjATBWDpxsR5Xug=
github.com/cucumber/gherkin-go/v11 v11.0.0/go.mod h1:CX33k2XU2qog4e+TFjOValoq6mIUq0DmVccZs238R9w=
github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
github.com/cucumber/godog v0.9.0 h1:QOb8wyC7f+FVFXzY3RdgowwJUb4WeJfqbnQqaH4jp+A=
github.com/cucumber/godog v0.9.0/go.mod h1:roWCHkpeK6UTOyIRRl7IR+fgfBeZ4vZR7OSq2J/NbM4=
github.com/cucumber/messages-go/v10 v10.0.1/go.mod h1:kA5T38CBlBbYLU12TIrJ4fk4wSkVVOgyh7Enyy8WnSg=
github.com/cucumber/messages-go/v10 v10.0.3 h1:m/9SD/K/A15WP7i1aemIv7cwvUw+viS51Ui5HBw1cdE=
github.com/cucumber/messages-go/v10 v10.0.3/go.mod h1:9jMZ2Y8ZxjLY6TG2+x344nt5rXstVVDYSdS5ySfI1WY=
github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/cronokirby/saferith v0.31.0 h1:TIlhldetKLeGAb19bZvWiuwQEzfzwSPthDEyJ9Ah8xs=
github.com/cronokirby/saferith v0.31.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
github.com/cucumber/godog v0.12.1 h1:IhWVYFKDReM5WsuA9AuRLRPWOyvFNO9UBUKrNfLPais=
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe h1:KRj3wdvA9yE92prNmOjS7x5DOqoyjxqdE30qnrmTasc=
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emersion/go-imap-appendlimit v0.0.0-20160923165328-beeb382f2a42 h1:3TeZ5gy3We/LVL0sqmGhM8dFDTSM7Hyj7PMIdl6OTs4=
github.com/emersion/go-imap-appendlimit v0.0.0-20160923165328-beeb382f2a42/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
github.com/emersion/go-imap-idle v0.0.0-20161227184850-e03ba1e0ed89 h1:AzbVhcrxgJO5MfSvzG5q4IfrYVm0Jw4AHNPz47+DiR0=
github.com/emersion/go-imap-idle v0.0.0-20161227184850-e03ba1e0ed89/go.mod h1:o14zPKCmEH5WC1vU5SdPoZGgNvQx7zzKSnxPQlobo78=
github.com/emersion/go-imap-move v0.0.0-20161227173100-88aef42b0f1d h1:STRZFC+5HZITdsSFkhFfyYRb+tkiTwhxFz3sRW1lYjk=
github.com/emersion/go-imap-move v0.0.0-20161227173100-88aef42b0f1d/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/emersion/go-imap-move v0.0.0-20161227183138-88aef42b0f1d h1:E/ezdheD3QUe47cM0LpAPuJ6Pk1x0EFDmjoysaZhtaw=
github.com/emersion/go-imap-move v0.0.0-20161227183138-88aef42b0f1d/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/elastic/go-sysinfo v1.7.1 h1:Wx4DSARcKLllpKT2TnFVdSUJOsybqMYCNQZq1/wO+s0=
github.com/elastic/go-sysinfo v1.7.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
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-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62 h1:4ZAfwfc8aDlj26kkEap1UDSwwDnJp9Ie8Uj1MSXAkPk=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
github.com/emersion/go-imap-unselect v0.0.0-20161227183655-1e6dc73ac8fe h1:2R2XpJkmbyy7PcSjnCPOnNfu+GuRzgWR9U2+j/d1O+0=
github.com/emersion/go-imap-unselect v0.0.0-20161227183655-1e6dc73ac8fe/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
github.com/emersion/go-imap-unselect v0.0.0-20161227193600-1e6dc73ac8fe h1:WeXweyFnbM2DQx0wxHkJKXYXwXpApopIeAjDTipW5Z4=
github.com/emersion/go-imap-unselect v0.0.0-20161227193600-1e6dc73ac8fe/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1yFiZjBgi7gGnQiLhpSgghJ0YTnKD0l4EUqqc=
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8+XBQQSkcX3ubr+1tAtlRJBYaFmRZqOAweZ9Wy8=
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-resty/resty/v2 v2.2.0 h1:vgZ1cdblp8Aw4jZj3ZsKh6yKAlMg3CHMrqFSFFd+jgY=
github.com/go-resty/resty/v2 v2.2.0/go.mod h1:nYW/8rxqQCmI3bPz9Fsmjbr2FBjGuR2Mzt6kDh3zZ7w=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs=
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-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=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 h1:j0UEFmS7wSjAwKEIkgKBn8PRDfjcuggzr93R9wk53nQ=
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jhillyerd/enmime v0.8.0 h1:PHc/2LXtnDmCDm0V4+5NlBx+MoubmufhuNXwpKSV2o8=
github.com/jhillyerd/enmime v0.8.0/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-keychain v0.0.0-20200218013740-86d4642e4ce2 h1:1XZArHAPddaXKbg51etNbCjkNUkKgSa0s8dSz2LYB2g=
github.com/keybase/go-keychain v0.0.0-20200218013740-86d4642e4ce2/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621 h1:aMQ7pA4f06yOVXSulygyGvy4xA94fyzjUGs0iqQdMOI=
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621/go.mod h1:enrU/ug069Om7vWxuFE6nikLI2BZNwevMiGSo43Kt5w=
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
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/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6 h1:qsqscDgSJy+HqgMTR+3NwjYJBbp1+honwDsszLoS+pA=
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.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/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=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 h1:d54EL9l+XteliUfUCGsEwwuk65dmmxX85VXF+9T6+50=
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285/go.mod h1:fxIDly1xtudczrZeOOlfaUvd2OPb2qZAPuWdU2BsBTk=
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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -179,66 +412,261 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41 h1:yBVcrpbaQYJBdKT2pxTdlL4hBE/eM4UPcyj9YpyvSok=
github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU=
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/msgpack/v5 v5.1.3 h1:FwC9KPjyW8OqTUqMt6rQw9y50vA2cTLXPKCcBCRbQgg=
github.com/vmihailenco/msgpack/v5 v5.1.3/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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/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=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
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/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/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/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw=
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
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=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20210927094055-39ccf1dd6fa6/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-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
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=
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
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/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=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
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=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
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=

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package api provides HTTP API of the Bridge.
//
@ -22,39 +22,32 @@
package api
import (
"crypto/tls"
"fmt"
"net/http"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/sirupsen/logrus"
)
var (
log = config.GetLogEntry("api") //nolint[gochecknoglobals]
log = logrus.WithField("pkg", "api") //nolint:gochecknoglobals
)
type apiServer struct {
host string
pref *config.Preferences
tls *tls.Config
certPath string
keyPath string
settings *settings.Settings
eventListener listener.Listener
}
// NewAPIServer returns prepared API server struct.
func NewAPIServer(pref *config.Preferences, tls *tls.Config, certPath, keyPath string, eventListener listener.Listener) *apiServer { //nolint[golint]
func NewAPIServer(settings *settings.Settings, eventListener listener.Listener) *apiServer { //nolint:revive
return &apiServer{
host: bridge.Host,
pref: pref,
tls: tls,
certPath: certPath,
keyPath: keyPath,
settings: settings,
eventListener: eventListener,
}
}
@ -66,25 +59,23 @@ func (api *apiServer) ListenAndServe() {
addr := api.getAddress()
server := &http.Server{
Addr: addr,
Handler: mux,
TLSConfig: api.tls,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
Addr: addr,
Handler: mux,
}
log.Info("API listening at ", addr)
if err := server.ListenAndServeTLS(api.certPath, api.keyPath); err != nil {
if err := server.ListenAndServe(); err != nil {
api.eventListener.Emit(events.ErrorEvent, "API failed: "+err.Error())
log.Error("API failed: ", err)
}
defer server.Close() //nolint[errcheck]
defer server.Close() //nolint:errcheck
}
func (api *apiServer) getAddress() string {
port := api.pref.GetInt(preferences.APIPortKey)
port := api.settings.GetInt(settings.APIPortKey)
newPort := ports.FindFreePortFrom(port)
if newPort != port {
api.pref.SetInt(preferences.APIPortKey, newPort)
api.settings.SetInt(settings.APIPortKey, newPort)
}
return getAPIAddress(api.host, newPort)
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package api

View File

@ -1,24 +1,23 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package api
import (
"crypto/tls"
"fmt"
"net/http"
@ -37,16 +36,13 @@ func focusHandler(ctx handlerContext) error {
// CheckOtherInstanceAndFocus is helper for new instances to check if there is
// already a running instance and get it's focus.
func CheckOtherInstanceAndFocus(port int, tls *tls.Config) error {
transport := &http.Transport{TLSClientConfig: tls}
client := &http.Client{Transport: transport}
func CheckOtherInstanceAndFocus(port int) error {
addr := getAPIAddress(bridge.Host, port)
resp, err := client.Get("https://" + addr + "/focus")
resp, err := (&http.Client{}).Get("http://" + addr + "/focus")
if err != nil {
return err
}
defer resp.Body.Close() //nolint[errcheck]
defer resp.Body.Close() //nolint:errcheck
if resp.StatusCode != 200 {
log.Error("Focus error: ", resp.StatusCode)

View File

@ -1,35 +1,35 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package args
package base
import (
"os"
"strings"
)
import "strings"
// FilterProcessSerialNumberFromArgs removes additional flag from MacOS. More info ProcessSerialNumber
// StripProcessSerialNumber removes additional flag from macOS.
// More info:
// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/Process_Manager/prmref_main/data_type_5.html#//apple_ref/doc/uid/TP30000208/C001951
func FilterProcessSerialNumberFromArgs() {
tmp := os.Args[:0]
for _, arg := range os.Args {
func StripProcessSerialNumber(args []string) []string {
res := args[:0]
for _, arg := range args {
if !strings.Contains(arg, "-psn_") {
tmp = append(tmp, arg)
res = append(res, arg)
}
}
os.Args = tmp
return res
}

405
internal/app/base/base.go Normal file
View File

@ -0,0 +1,405 @@
// 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 base implements a common application base currently shared by bridge and IE.
// The base includes the following:
// - access to standard filesystem locations like config, cache, logging dirs
// - an extensible crash handler
// - versioned cache directory
// - persistent settings
// - event listener
// - credentials store
// - pmapi Manager
// In addition, the base initialises logging and reacts to command line arguments
// which control the log verbosity and enable cpu/memory profiling.
package base
import (
"math/rand"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"time"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/config/cache"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/tls"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/cookies"
"github.com/ProtonMail/proton-bridge/internal/crash"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/logging"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
"github.com/ProtonMail/proton-bridge/internal/versioner"
"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/allan-simon/go-singleinstance"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
const (
flagCPUProfile = "cpu-prof"
flagCPUProfileShort = "p"
flagMemProfile = "mem-prof"
flagMemProfileShort = "m"
flagLogLevel = "log-level"
flagLogLevelShort = "l"
// FlagCLI indicate to start with command line interface.
FlagCLI = "cli"
flagCLIShort = "c"
flagRestart = "restart"
FlagLauncher = "launcher"
FlagNoWindow = "no-window"
)
type Base struct {
SentryReporter *sentry.Reporter
CrashHandler *crash.Handler
Locations *locations.Locations
Settings *settings.Settings
Lock *os.File
Cache *cache.Cache
Listener listener.Listener
Creds *credentials.Store
CM pmapi.Manager
CookieJar *cookies.Jar
UserAgent *useragent.UserAgent
Updater *updater.Updater
Versioner *versioner.Versioner
TLS *tls.TLS
Autostart *autostart.App
Name string // the app's name
usage string // the app's usage description
command string // the command used to launch the app (either the exe path or the launcher path)
restart bool // whether the app is currently set to restart
teardown []func() error // actions to perform when app is exiting
}
func New( //nolint:funlen
appName,
appUsage,
configName,
updateURLName,
keychainName,
cacheVersion string,
) (*Base, error) {
userAgent := useragent.New()
sentryReporter := sentry.NewReporter(appName, constants.Version, userAgent)
crashHandler := crash.NewHandler(
sentryReporter.ReportException,
crash.ShowErrorNotification(appName),
)
defer crashHandler.HandlePanic()
rand.Seed(time.Now().UnixNano())
os.Args = StripProcessSerialNumber(os.Args)
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
if err != nil {
return nil, err
}
locations := locations.New(locationsProvider, configName)
logsPath, err := locations.ProvideLogsPath()
if err != nil {
return nil, err
}
if err := logging.Init(logsPath); err != nil {
return nil, err
}
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
if err := migrateFiles(configName); err != nil {
logrus.WithError(err).Warn("Old config files could not be migrated")
}
if err := locations.Clean(); err != nil {
return nil, err
}
settingsPath, err := locations.ProvideSettingsPath()
if err != nil {
return nil, err
}
settingsObj := settings.New(settingsPath)
lock, err := singleinstance.CreateLockFile(locations.GetLockFile())
if err != nil {
logrus.Warnf("%v is already running", appName)
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
}
if err := migrateRebranding(settingsObj, keychainName); err != nil {
logrus.WithError(err).Warn("Rebranding migration failed")
}
cachePath, err := locations.ProvideCachePath()
if err != nil {
return nil, err
}
cache, err := cache.New(cachePath, cacheVersion)
if err != nil {
return nil, err
}
if err := cache.RemoveOldVersions(); err != nil {
return nil, err
}
listener := listener.New()
events.SetupEvents(listener)
// If we can't load the keychain for whatever reason,
// we signal to frontend and supply a dummy keychain that always returns errors.
kc, err := keychain.NewKeychain(settingsObj, keychainName)
if err != nil {
listener.Emit(events.CredentialsErrorEvent, err.Error())
kc = keychain.NewMissingKeychain()
}
cfg := pmapi.NewConfig(configName, constants.Version)
cfg.GetUserAgent = userAgent.String
cfg.UpgradeApplicationHandler = func() { listener.Emit(events.UpgradeApplicationEvent, "") }
cfg.TLSIssueHandler = func() { listener.Emit(events.TLSCertIssue, "") }
cm := pmapi.New(cfg)
sentryReporter.SetClientFromManager(cm)
cm.AddConnectionObserver(pmapi.NewConnectionObserver(
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOff) },
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOn) },
))
jar, err := cookies.NewCookieJar(settingsObj)
if err != nil {
return nil, err
}
cm.SetCookieJar(jar)
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
if err != nil {
return nil, err
}
kr, err := crypto.NewKeyRing(key)
if err != nil {
return nil, err
}
updatesDir, err := locations.ProvideUpdatesPath()
if err != nil {
return nil, err
}
versioner := versioner.New(updatesDir)
installer := updater.NewInstaller(versioner)
updater := updater.New(
cm,
installer,
settingsObj,
kr,
semver.MustParse(constants.Version),
updateURLName,
runtime.GOOS,
)
exe, err := os.Executable()
if err != nil {
return nil, err
}
autostart := &autostart.App{
Name: startupNameForRebranding(appName),
DisplayName: appName,
Exec: []string{exe, "--" + FlagNoWindow},
}
return &Base{
SentryReporter: sentryReporter,
CrashHandler: crashHandler,
Locations: locations,
Settings: settingsObj,
Lock: lock,
Cache: cache,
Listener: listener,
Creds: credentials.NewStore(kc),
CM: cm,
CookieJar: jar,
UserAgent: userAgent,
Updater: updater,
Versioner: versioner,
TLS: tls.New(settingsPath),
Autostart: autostart,
Name: appName,
usage: appUsage,
// By default, the command is the app's executable.
// This can be changed at runtime by using the "--launcher" flag.
command: exe,
}, nil
}
func (b *Base) NewApp(mainLoop func(*Base, *cli.Context) error) *cli.App {
app := cli.NewApp()
app.Name = b.Name
app.Usage = b.usage
app.Version = constants.Version
app.Action = b.wrapMainLoop(mainLoop)
app.Flags = []cli.Flag{
&cli.BoolFlag{
Name: flagCPUProfile,
Aliases: []string{flagCPUProfileShort},
Usage: "Generate CPU profile",
},
&cli.BoolFlag{
Name: flagMemProfile,
Aliases: []string{flagMemProfileShort},
Usage: "Generate memory profile",
},
&cli.StringFlag{
Name: flagLogLevel,
Aliases: []string{flagLogLevelShort},
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug)",
},
&cli.BoolFlag{
Name: FlagCLI,
Aliases: []string{flagCLIShort},
Usage: "Use command line interface",
},
&cli.BoolFlag{
Name: FlagNoWindow,
Usage: "Don't show window after start",
},
&cli.StringFlag{
Name: flagRestart,
Usage: "The number of times the application has already restarted",
Hidden: true,
},
&cli.StringFlag{
Name: FlagLauncher,
Usage: "The launcher to use to restart the application",
Hidden: true,
},
}
return app
}
// SetToRestart sets the app to restart the next time it is closed.
func (b *Base) SetToRestart() {
b.restart = true
}
// AddTeardownAction adds an action to perform during app teardown.
func (b *Base) AddTeardownAction(fn func() error) {
b.teardown = append(b.teardown, fn)
}
func (b *Base) wrapMainLoop(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc { //nolint:funlen
return func(c *cli.Context) error {
defer b.CrashHandler.HandlePanic()
defer func() { _ = b.Lock.Close() }()
// If launcher was used to start the app, use that for restart
// and autostart.
if launcher := c.String(FlagLauncher); launcher != "" {
b.command = launcher
// Bridge supports no-window option which we should use
// for autostart.
b.Autostart.Exec = []string{launcher, "--" + FlagNoWindow}
}
if c.Bool(flagCPUProfile) {
startCPUProfile()
defer pprof.StopCPUProfile()
}
if c.Bool(flagMemProfile) {
defer makeMemoryProfile()
}
logging.SetLevel(c.String(flagLogLevel))
b.CM.SetLogging(logrus.WithField("pkg", "pmapi"), logrus.GetLevel() == logrus.TraceLevel)
logrus.
WithField("appName", b.Name).
WithField("version", constants.Version).
WithField("revision", constants.Revision).
WithField("build", constants.BuildTime).
WithField("runtime", runtime.GOOS).
WithField("args", os.Args).
Info("Run app")
b.CrashHandler.AddRecoveryAction(func(interface{}) error {
sentry.Flush(2 * time.Second)
if c.Int(flagRestart) > maxAllowedRestarts {
logrus.
WithField("restart", c.Int("restart")).
Warn("Not restarting, already restarted too many times")
os.Exit(1)
return nil
}
return b.restartApp(true)
})
if err := appMainLoop(b, c); err != nil {
return err
}
if err := b.doTeardown(); err != nil {
return err
}
if b.restart {
return b.restartApp(false)
}
return nil
}
}
func (b *Base) doTeardown() error {
for _, action := range b.teardown {
if err := action(); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,131 @@
// 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 base
import (
"os"
"path/filepath"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/sirupsen/logrus"
)
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
// We can remove this eventually.
//
// | entity | old location | new location |
// |-----------|-------------------------------------------|----------------------------------------|
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
// | c11 1.5.x | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
// | c11 1.6.x | ~/.cache/protonmail/<app>/cache/c11 | ~/.config/protonmail/<app>/cache/c11 |
// | updates | ~/.cache/protonmail/<app>/updates | ~/.config/protonmail/<app>/updates |.
func migrateFiles(configName string) error {
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
if err != nil {
return err
}
locations := locations.New(locationsProvider, configName)
userCacheDir := locationsProvider.UserCache()
if err := migratePrefsFrom15x(locations, userCacheDir); err != nil {
return err
}
if err := migrateCacheFromBoth15xAnd16x(locations, userCacheDir); err != nil {
return err
}
if err := migrateUpdatesFrom16x(configName, locations); err != nil { //nolint:revive It is more clear to structure this way
return err
}
return nil
}
func migratePrefsFrom15x(locations *locations.Locations, userCacheDir string) error {
newSettingsDir, err := locations.ProvideSettingsPath()
if err != nil {
return err
}
return moveIfExists(
filepath.Join(userCacheDir, "c11", "prefs.json"),
filepath.Join(newSettingsDir, "prefs.json"),
)
}
func migrateCacheFromBoth15xAnd16x(locations *locations.Locations, userCacheDir string) error {
olderCacheDir := userCacheDir
newerCacheDir := locations.GetOldCachePath()
latestCacheDir, err := locations.ProvideCachePath()
if err != nil {
return err
}
// Migration for versions before 1.6.x.
if err := moveIfExists(
filepath.Join(olderCacheDir, "c11"),
filepath.Join(latestCacheDir, "c11"),
); err != nil {
return err
}
// Migration for versions 1.6.x.
return moveIfExists(
filepath.Join(newerCacheDir, "c11"),
filepath.Join(latestCacheDir, "c11"),
)
}
func migrateUpdatesFrom16x(configName string, locations *locations.Locations) error {
// In order to properly update Bridge 1.6.X and higher we need to
// change the launcher first. Since this is not part of automatic
// updates the migration must wait until manual update. Until that
// we need to keep old path.
if configName == "bridge" {
return nil
}
oldUpdatesPath := locations.GetOldUpdatesPath()
// Do not use ProvideUpdatesPath, that creates dir right away.
newUpdatesPath := locations.GetUpdatesPath()
return moveIfExists(oldUpdatesPath, newUpdatesPath)
}
func moveIfExists(source, destination string) error {
l := logrus.WithField("source", source).WithField("destination", destination)
if _, err := os.Stat(source); os.IsNotExist(err) {
l.Info("No need to migrate file, source doesn't exist")
return nil
}
if _, err := os.Stat(destination); !os.IsNotExist(err) {
// Once migrated, files should not stay in source anymore. Therefore
// if some files are still in source location but target already exist,
// it's suspicious. Could happen by installing new version, then the
// old one because of some reason, and then the new one again.
// Good to see as warning because it could be a reason why Bridge is
// behaving weirdly, like wrong configuration, or db re-sync and so on.
l.Warn("No need to migrate file, target already exists")
return nil
}
l.Info("Migrating files")
return os.Rename(source, destination)
}

View File

@ -0,0 +1,197 @@
// 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 base
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus"
)
const darwin = "darwin"
func migrateRebranding(settingsObj *settings.Settings, keychainName string) (result error) {
if err := migrateStartupBeforeRebranding(); err != nil {
result = multierror.Append(result, err)
}
lastUsedVersion := settingsObj.Get(settings.LastVersionKey)
// Skipping migration: it is first bridge start or cache was cleared.
if lastUsedVersion == "" {
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
return
}
// Skipping rest of migration: already done
if settingsObj.GetBool(settings.RebrandingMigrationKey) {
return
}
switch runtime.GOOS {
case "windows", "linux":
// GODT-1260 we would need admin rights to changes desktop files
// and start menu items.
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
case darwin:
if shouldContinue, err := isMacBeforeRebranding(); !shouldContinue || err != nil {
if err != nil {
result = multierror.Append(result, err)
}
break
}
if err := migrateMacKeychainBeforeRebranding(settingsObj, keychainName); err != nil {
result = multierror.Append(result, err)
}
settingsObj.SetBool(settings.RebrandingMigrationKey, true)
}
return result
}
// migrateMacKeychainBeforeRebranding deals with write access restriction to
// mac keychain passwords which are caused by application renaming. The old
// passwords are copied under new name in order to have write access afer
// renaming.
func migrateMacKeychainBeforeRebranding(settingsObj *settings.Settings, keychainName string) error {
l := logrus.WithField("pkg", "app/base/migration")
l.Warn("Migrating mac keychain")
helperConstructor, ok := keychain.Helpers["macos-keychain"]
if !ok {
return errors.New("cannot find macos-keychain helper")
}
oldKC, err := helperConstructor("ProtonMailBridgeService")
if err != nil {
l.WithError(err).Error("Keychain constructor failed")
return err
}
idByURL, err := oldKC.List()
if err != nil {
l.WithError(err).Error("List old keychain failed")
return err
}
newKC, err := keychain.NewKeychain(settingsObj, keychainName)
if err != nil {
return err
}
for url, id := range idByURL {
li := l.WithField("id", id).WithField("url", url)
userID, secret, err := oldKC.Get(url)
if err != nil {
li.WithField("userID", userID).
WithField("err", err).
Error("Faild to get old item")
continue
}
if _, _, err := newKC.Get(userID); err == nil {
li.Warn("Skipping migration, item already exists.")
continue
}
if err := newKC.Put(userID, secret); err != nil {
li.WithError(err).Error("Failed to migrate user")
}
li.Info("Item migrated")
}
return nil
}
// migrateStartupBeforeRebranding removes old startup links. The creation of new links is
// handled by bridge initialisation.
func migrateStartupBeforeRebranding() error {
path, err := os.UserHomeDir()
if err != nil {
return err
}
switch runtime.GOOS {
case "windows":
path = filepath.Join(path, `AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\ProtonMail Bridge.lnk`)
case "linux":
path = filepath.Join(path, `.config/autostart/ProtonMail Bridge.desktop`)
case darwin:
path = filepath.Join(path, `Library/LaunchAgents/ProtonMail Bridge.plist`)
default:
return errors.New("unknown GOOS")
}
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil
}
logrus.WithField("pkg", "app/base/migration").Warn("Migrating autostartup links")
return os.Remove(path)
}
// startupNameForRebranding returns the name for autostart launcher based on
// type of rebranded instance i.e. update or manual.
//
// This only affects darwin when udpate re-writes the old startup and then
// manual installed it would not run proper exe. Therefore we return "old" name
// for updates and "new" name for manual which would be properly migrated.
//
// For orther (linux and windows) the link is always pointing to launcher which
// path didn't changed.
func startupNameForRebranding(origin string) string {
if runtime.GOOS == darwin {
if path, err := os.Executable(); err == nil && strings.Contains(path, "ProtonMail Bridge") {
return "ProtonMail Bridge"
}
}
// No need to solve for other OS. See comment above.
return origin
}
// isBeforeRebranding decide if last used version was older than 2.2.0. If
// cannot decide it returns false with error.
func isMacBeforeRebranding() (bool, error) {
// previous version | update | do mac migration |
// | first | false |
// cleared-cache | manual | false |
// cleared-cache | in-app | false |
// old | in-app | false |
// old in-app | in-app | false |
// old | manual | true |
// old in-app | manual | true |
// manual | in-app | false |
// Skip if it was in-app update and not manual
if path, err := os.Executable(); err != nil || strings.Contains(path, "ProtonMail Bridge") {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,56 @@
// 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 base
import (
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"github.com/sirupsen/logrus"
)
// startCPUProfile starts CPU pprof.
func startCPUProfile() {
f, err := os.Create("./cpu.pprof")
if err != nil {
logrus.Fatal("Could not create CPU profile: ", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
logrus.Fatal("Could not start CPU profile: ", err)
}
}
// makeMemoryProfile generates memory pprof.
func makeMemoryProfile() {
name := "./mem.pprof"
f, err := os.Create(name)
if err != nil {
logrus.Fatal("Could not create memory profile: ", err)
}
if abs, err := filepath.Abs(name); err == nil {
name = abs
}
logrus.Info("Writing memory profile to ", name)
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
logrus.Fatal("Could not write memory profile: ", err)
}
_ = f.Close()
}

View File

@ -0,0 +1,80 @@
// 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 base
import (
"os"
"os/exec"
"strconv"
"github.com/sirupsen/logrus"
)
// maxAllowedRestarts controls after how many crashes the app will give up restarting.
const maxAllowedRestarts = 10
func (b *Base) restartApp(crash bool) error {
var args []string
if crash {
args = incrementRestartFlag(os.Args)[1:]
defer func() { os.Exit(1) }()
} else {
args = os.Args[1:]
}
logrus.
WithField("command", b.command).
WithField("args", args).
Warn("Restarting")
return exec.Command(b.command, args...).Start() //nolint:gosec
}
// incrementRestartFlag increments the value of the restart flag.
// If no such flag is present, it is added with initial value 1.
func incrementRestartFlag(args []string) []string {
res := append([]string{}, args...)
hasFlag := false
for k, v := range res {
if v != "--restart" {
continue
}
hasFlag = true
if k+1 >= len(res) {
continue
}
n, err := strconv.Atoi(res[k+1])
if err != nil {
res[k+1] = "1"
} else {
res[k+1] = strconv.Itoa(n + 1)
}
}
if !hasFlag {
res = append(res, "--restart", "1")
}
return res
}

View File

@ -0,0 +1,49 @@
// 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 base
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIncrementRestartFlag(t *testing.T) {
var tests = []struct {
in []string
out []string
}{
{[]string{"./bridge", "--restart", "1"}, []string{"./bridge", "--restart", "2"}},
{[]string{"./bridge", "--restart", "2"}, []string{"./bridge", "--restart", "3"}},
{[]string{"./bridge", "--other", "--restart", "2"}, []string{"./bridge", "--other", "--restart", "3"}},
{[]string{"./bridge", "--restart", "2", "--other"}, []string{"./bridge", "--restart", "3", "--other"}},
{[]string{"./bridge", "--restart", "2", "--other", "2"}, []string{"./bridge", "--restart", "3", "--other", "2"}},
{[]string{"./bridge"}, []string{"./bridge", "--restart", "1"}},
{[]string{"./bridge", "--something"}, []string{"./bridge", "--something", "--restart", "1"}},
{[]string{"./bridge", "--something", "--else"}, []string{"./bridge", "--something", "--else", "--restart", "1"}},
{[]string{"./bridge", "--restart", "bad"}, []string{"./bridge", "--restart", "1"}},
{[]string{"./bridge", "--restart", "bad", "--other"}, []string{"./bridge", "--restart", "1", "--other"}},
}
for _, tt := range tests {
t.Run(strings.Join(tt.in, " "), func(t *testing.T) {
assert.Equal(t, tt.out, incrementRestartFlag(tt.in))
})
}
}

View File

@ -0,0 +1,314 @@
// 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 bridge implements the bridge CLI application.
package bridge
import (
"crypto/tls"
"time"
"github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/app/base"
pkgBridge "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/frontend"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/imap"
"github.com/ProtonMail/proton-bridge/internal/smtp"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/internal/store/cache"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
const (
flagLogIMAP = "log-imap"
flagLogSMTP = "log-smtp"
flagNonInteractive = "noninteractive"
// Memory cache was estimated by empirical usage in past and it was set to 100MB.
// NOTE: This value must not be less than maximal size of one email (~30MB).
inMemoryCacheLimnit = 100 * (1 << 20)
)
func New(base *base.Base) *cli.App {
app := base.NewApp(mailLoop)
app.Flags = append(app.Flags, []cli.Flag{
&cli.StringFlag{
Name: flagLogIMAP,
Usage: "Enable logging of IMAP communications (all|client|server) (may contain decrypted data!)"},
&cli.BoolFlag{
Name: flagLogSMTP,
Usage: "Enable logging of SMTP communications (may contain decrypted data!)"},
&cli.BoolFlag{
Name: flagNonInteractive,
Usage: "Start Bridge entirely noninteractively"},
}...)
return app
}
func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
tlsConfig, err := loadTLSConfig(b)
if err != nil {
return err
}
// GODT-1481: Always turn off reporting of unencrypted recipient in v2.
b.Settings.SetBool(settings.ReportOutgoingNoEncKey, false)
cache, cacheErr := loadMessageCache(b)
if cacheErr != nil {
logrus.WithError(cacheErr).Error("Could not load local cache.")
}
builder := message.NewBuilder(
b.Settings.GetInt(settings.FetchWorkers),
b.Settings.GetInt(settings.AttachmentWorkers),
)
bridge := pkgBridge.New(
b.Locations,
b.Cache,
b.Settings,
b.SentryReporter,
b.CrashHandler,
b.Listener,
cache,
builder,
b.CM,
b.Creds,
b.Updater,
b.Versioner,
b.Autostart,
)
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, b.Settings, bridge)
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)
if cacheErr != nil {
bridge.AddError(pkgBridge.ErrLocalCacheUnavailable)
}
go func() {
defer b.CrashHandler.HandlePanic()
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
}()
go func() {
defer b.CrashHandler.HandlePanic()
imapPort := b.Settings.GetInt(settings.IMAPPortKey)
imap.NewIMAPServer(
b.CrashHandler,
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
c.String(flagLogIMAP) == "server" || c.String(flagLogIMAP) == "all",
imapPort, tlsConfig, imapBackend, b.UserAgent, b.Listener).ListenAndServe()
}()
go func() {
defer b.CrashHandler.HandlePanic()
smtpPort := b.Settings.GetInt(settings.SMTPPortKey)
useSSL := b.Settings.GetBool(settings.SMTPSSLKey)
smtp.NewSMTPServer(
b.CrashHandler,
c.Bool(flagLogSMTP),
smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe()
}()
// We want to remove old versions if the app exits successfully.
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
// We want cookies to be saved to disk so they are loaded the next time.
b.AddTeardownAction(b.CookieJar.PersistCookies)
var frontendMode string
switch {
case c.Bool(base.FlagCLI):
frontendMode = "cli"
case c.Bool(flagNonInteractive):
return <-(make(chan error)) // Block forever.
default:
frontendMode = "qt"
}
f := frontend.New(
constants.Version,
constants.BuildVersion,
b.Name,
frontendMode,
!c.Bool(base.FlagNoWindow),
b.CrashHandler,
b.Locations,
b.Settings,
b.Listener,
b.Updater,
b.UserAgent,
bridge,
smtpBackend,
b,
)
// Watch for updates routine
go func() {
ticker := time.NewTicker(constants.UpdateCheckInterval)
for {
checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey))
<-ticker.C
}
}()
return f.Loop()
}
func loadTLSConfig(b *base.Base) (*tls.Config, error) {
if !b.TLS.HasCerts() {
if err := generateTLSCerts(b); err != nil {
return nil, err
}
}
tlsConfig, err := b.TLS.GetConfig()
if err == nil {
return tlsConfig, nil
}
logrus.WithError(err).Error("Failed to load TLS config, regenerating certificates")
if err := generateTLSCerts(b); err != nil {
return nil, err
}
return b.TLS.GetConfig()
}
func generateTLSCerts(b *base.Base) error {
template, err := pkgTLS.NewTLSTemplate()
if err != nil {
return errors.Wrap(err, "failed to generate TLS template")
}
if err := b.TLS.GenerateCerts(template); err != nil {
return errors.Wrap(err, "failed to generate TLS certs")
}
if err := b.TLS.InstallCerts(); err != nil {
return errors.Wrap(err, "failed to install TLS certs")
}
return nil
}
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) {
log := logrus.WithField("pkg", "app/bridge")
version, err := u.Check()
if err != nil {
log.WithError(err).Error("An error occurred while checking for updates")
return
}
f.WaitUntilFrontendIsReady()
// Update links in UI
f.SetVersion(version)
if !u.IsUpdateApplicable(version) {
log.Info("No need to update")
return
}
log.WithField("version", version.Version).Info("An update is available")
if !autoUpdate {
f.NotifyManualUpdate(version, u.CanInstall(version))
return
}
if !u.CanInstall(version) {
log.Info("A manual update is required")
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
return
}
if err := u.InstallUpdate(version); err != nil {
if errors.Cause(err) == updater.ErrDownloadVerify {
log.WithError(err).Warning("Skipping update installation due to temporary error")
} else {
log.WithError(err).Error("The update couldn't be installed")
f.NotifySilentUpdateError(err)
}
return
}
f.NotifySilentUpdateInstalled()
}
// loadMessageCache loads local cache in case it is enabled in settings and available.
// In any other case it is returning in-memory cache. Could also return an error in case
// local cache is enabled but unavailable (in-memory cache will be returned nevertheless).
func loadMessageCache(b *base.Base) (cache.Cache, error) {
if !b.Settings.GetBool(settings.CacheEnabledKey) {
return cache.NewInMemoryCache(inMemoryCacheLimnit), nil
}
var compressor cache.Compressor
// NOTE(GODT-1158): Changing compression is not an option currently
// available for user but, if user changes compression setting we have
// to nuke the cache.
if b.Settings.GetBool(settings.CacheCompressionKey) {
compressor = &cache.GZipCompressor{}
} else {
compressor = &cache.NoopCompressor{}
}
var path string
if customPath := b.Settings.Get(settings.CacheLocationKey); customPath != "" {
path = customPath
} else {
path = b.Cache.GetDefaultMessageCacheDir()
// Store path so it will allways persist if default location
// will be changed in new version.
b.Settings.Set(settings.CacheLocationKey, path)
}
// To prevent memory peaks we set maximal write concurency for store
// build jobs.
store.SetBuildAndCacheJobLimit(b.Settings.GetInt(settings.CacheConcurrencyWrite))
messageCache, err := cache.NewOnDiskCache(path, compressor, cache.Options{
MinFreeAbs: uint64(b.Settings.GetInt(settings.CacheMinFreeAbsKey)),
MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey),
ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead),
ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite),
})
if err != nil {
return cache.NewInMemoryCache(inMemoryCacheLimnit), err
}
return messageCache, nil
}

View File

@ -0,0 +1,38 @@
// 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 bridge provides core functionality of Bridge app.
package bridge
import "github.com/ProtonMail/proton-bridge/internal/config/settings"
// IsAutostartEnabled checks if link file exits.
func (b *Bridge) IsAutostartEnabled() bool {
return b.autostart.IsEnabled()
}
// EnableAutostart creates link and sets the preferences.
func (b *Bridge) EnableAutostart() error {
b.settings.SetBool(settings.AutostartKey, true)
return b.autostart.Enable()
}
// DisableAutostart removes link and sets the preferences.
func (b *Bridge) DisableAutostart() error {
b.settings.SetBool(settings.AutostartKey, false)
return b.autostart.Disable()
}

View File

@ -1,119 +1,119 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package bridge provides core business logic providing API over credentials store and PM API.
// Package bridge provides core functionality of Bridge app.
package bridge
import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/internal/events"
m "github.com/ProtonMail/proton-bridge/internal/metrics"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/metrics"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/store/cache"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/hashicorp/go-multierror"
"github.com/ProtonMail/proton-bridge/pkg/listener"
logrus "github.com/sirupsen/logrus"
)
var (
log = config.GetLogEntry("bridge") //nolint[gochecknoglobals]
isApplicationOutdated = false //nolint[gochecknoglobals]
)
var log = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
var ErrLocalCacheUnavailable = errors.New("local cache is unavailable")
// Bridge is a struct handling users.
type Bridge struct {
config Configer
pref PreferenceProvider
panicHandler PanicHandler
events listener.Listener
version string
pmapiClientFactory PMAPIProviderFactory
credStorer CredentialsStorer
storeCache *store.Cache
*users.Users
// users is a list of accounts that have been added to bridge.
// They are stored sorted in the credentials store in the order
// that they were added to bridge chronologically.
// People are used to that and so we preserve that ordering here.
users []*User
locations Locator
settings SettingsProvider
clientManager pmapi.Manager
updater Updater
versioner Versioner
cacheProvider CacheProvider
autostart *autostart.App
// Bridge's global errors list.
errors []error
// idleUpdates is a channel which the imap backend listens to and which it uses
// to send idle updates to the mail client (eg thunderbird).
// The user stores should send idle updates on this channel.
idleUpdates chan interface{}
lock sync.RWMutex
userAgentClientName string
userAgentClientVersion string
userAgentOS string
isFirstStart bool
lastVersion string
}
func New(
config Configer,
pref PreferenceProvider,
panicHandler PanicHandler,
locations Locator,
cacheProvider CacheProvider,
setting SettingsProvider,
sentryReporter *sentry.Reporter,
panicHandler users.PanicHandler,
eventListener listener.Listener,
version string,
pmapiClientFactory PMAPIProviderFactory,
credStorer CredentialsStorer,
cache cache.Cache,
builder *message.Builder,
clientManager pmapi.Manager,
credStorer users.CredentialsStorer,
updater Updater,
versioner Versioner,
autostart *autostart.App,
) *Bridge {
log.Trace("Creating new bridge")
// Allow DoH before starting the app if the user has previously set this setting.
// This allows us to start even if protonmail is blocked.
if setting.GetBool(settings.AllowProxyKey) {
clientManager.AllowProxy()
}
u := users.New(
locations,
panicHandler,
eventListener,
clientManager,
credStorer,
newStoreFactory(cacheProvider, sentryReporter, panicHandler, eventListener, cache, builder),
)
b := &Bridge{
config: config,
pref: pref,
panicHandler: panicHandler,
events: eventListener,
version: version,
pmapiClientFactory: pmapiClientFactory,
credStorer: credStorer,
storeCache: store.NewCache(config.GetIMAPCachePath()),
idleUpdates: make(chan interface{}),
lock: sync.RWMutex{},
Users: u,
locations: locations,
settings: setting,
clientManager: clientManager,
updater: updater,
versioner: versioner,
cacheProvider: cacheProvider,
autostart: autostart,
isFirstStart: false,
}
// Allow DoH before starting bridge if the user has previously set this setting.
// This allows us to start even if protonmail is blocked.
if pref.GetBool(preferences.AllowProxyKey) {
AllowDoH()
if setting.GetBool(settings.FirstStartKey) {
b.isFirstStart = true
if err := b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(constants.Version))); err != nil {
logrus.WithError(err).Error("Failed to send metric")
}
setting.SetBool(settings.FirstStartKey, false)
}
go func() {
defer panicHandler.HandlePanic()
b.watchBridgeOutdated()
}()
if b.credStorer == nil {
log.Error("Bridge has no credentials store")
} else if err := b.loadUsersFromCredentialsStore(); err != nil {
log.WithError(err).Error("Could not load all users from credentials store")
}
if pref.GetBool(preferences.FirstStartKey) {
b.SendMetric(m.New(m.Setup, m.FirstStart, m.Label(version)))
}
// Keep in bridge and update in settings the last used version.
b.lastVersion = b.settings.Get(settings.LastVersionKey)
b.settings.Set(settings.LastVersionKey, constants.Version)
go b.heartbeat()
@ -122,389 +122,183 @@ func New(
// heartbeat sends a heartbeat signal once a day.
func (b *Bridge) heartbeat() {
for range time.NewTicker(1 * time.Hour).C {
next, err := strconv.ParseInt(b.pref.Get(preferences.NextHeartbeatKey), 10, 64)
for range time.Tick(time.Minute) {
lastHeartbeatDay, err := strconv.ParseInt(b.settings.Get(settings.LastHeartbeatKey), 10, 64)
if err != nil {
continue
}
nextTime := time.Unix(next, 0)
if time.Now().After(nextTime) {
b.SendMetric(m.New(m.Heartbeat, m.Daily, m.NoLabel))
nextTime = nextTime.Add(24 * time.Hour)
b.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
}
}
}
func (b *Bridge) loadUsersFromCredentialsStore() (err error) {
b.lock.Lock()
defer b.lock.Unlock()
userIDs, err := b.credStorer.List()
if err != nil {
return
}
for _, userID := range userIDs {
l := log.WithField("user", userID)
apiClient := b.pmapiClientFactory(userID)
user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, apiClient, b.storeCache, b.config.GetDBDir())
if newUserErr != nil {
l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping")
// If we're still on the same day, don't send a heartbeat.
if time.Now().YearDay() == int(lastHeartbeatDay) {
continue
}
b.users = append(b.users, user)
if initUserErr := user.init(b.idleUpdates, apiClient); initUserErr != nil {
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
// We're on the next (or a different) day, so send a heartbeat.
if err := b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel)); err != nil {
logrus.WithError(err).Error("Failed to send heartbeat")
continue
}
}
return err
}
func (b *Bridge) watchBridgeOutdated() {
ch := make(chan string)
b.events.Add(events.UpgradeApplicationEvent, ch)
for range ch {
isApplicationOutdated = true
b.closeAllConnections()
// Heartbeat was sent successfully so update the last heartbeat day.
b.settings.Set(settings.LastHeartbeatKey, fmt.Sprintf("%v", time.Now().YearDay()))
}
}
func (b *Bridge) closeAllConnections() {
for _, user := range b.users {
user.closeAllConnections()
}
// GetUpdateChannel returns currently set update channel.
func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
return updater.UpdateChannel(b.settings.Get(settings.UpdateChannelKey))
}
// Login authenticates a user.
// The login flow:
// * Authenticate user:
// client, auth, err := bridge.Authenticate(username, password)
//
// * In case user `auth.HasTwoFactor()`, ask for it and fully authenticate the user.
// auth2FA, err := client.Auth2FA(twoFactorCode)
//
// * In case user `auth.HasMailboxPassword()`, ask for it, otherwise use `password`
// and then finish the login procedure.
// user, err := bridge.FinishLogin(client, auth, mailboxPassword)
func (b *Bridge) Login(username, password string) (loginClient PMAPIProvider, auth *pmapi.Auth, err error) {
log.WithField("username", username).Trace("Logging in to bridge")
// SetUpdateChannel switches update channel.
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) {
b.settings.Set(settings.UpdateChannelKey, string(channel))
}
b.crashBandicoot(username)
// We need to use "login" client because we need userID to properly
// assign access tokens into token manager.
loginClient = b.pmapiClientFactory("login")
authInfo, err := loginClient.AuthInfo(username)
func (b *Bridge) resetToLatestStable() error {
version, err := b.updater.Check()
if err != nil {
log.WithField("username", username).WithError(err).Error("Could not get auth info for user")
return nil, nil, err
}
if auth, err = loginClient.Auth(username, password, authInfo); err != nil {
log.WithField("username", username).WithError(err).Error("Could not get auth for user")
return loginClient, auth, err
}
return loginClient, auth, nil
}
// FinishLogin finishes the login procedure and adds the user into the credentials store.
// See `Login` for more details of the login flow.
func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPassword string) (user *User, err error) { //nolint[funlen]
log.Trace("Finishing bridge login")
defer func() {
if err == pmapi.ErrUpgradeApplication {
b.events.Emit(events.UpgradeApplicationEvent, "")
// If we can not check for updates - just remove all local updates and reset to base installer version.
// Not using `b.locations.ClearUpdates()` because `versioner.RemoveOtherVersions` can also handle
// case when it is needed to remove currently running verion.
if err := b.versioner.RemoveOtherVersions(semver.MustParse("0.0.0")); err != nil {
log.WithError(err).Error("Failed to clear updates while downgrading channel")
}
}()
b.lock.Lock()
defer b.lock.Unlock()
mbPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt)
if err != nil {
log.WithError(err).Error("Could not hash mailbox password")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after hash password failed.")
}
return
return nil
}
if _, err = loginClient.Unlock(mbPassword); err != nil {
log.WithError(err).Error("Could not decrypt keyring")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after unlock failed.")
}
return
// If current version is same as upstream stable version - do nothing.
if version.Version.Equal(semver.MustParse(constants.Version)) {
return nil
}
apiUser, err := loginClient.CurrentUser()
if err != nil {
log.WithError(err).Error("Could not get login API user")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after get current user failed.")
}
return
}
user, hasUser := b.hasUser(apiUser.ID)
// If the user exists and is logged in, we don't want to do anything.
if hasUser && user.IsConnected() {
err = errors.New("user is already logged in")
log.WithError(err).Warn("User is already logged in")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Warn("Could not discard auth generated during second login")
}
return
}
apiToken := auth.UID() + ":" + auth.RefreshToken
apiClient := b.pmapiClientFactory(apiUser.ID)
auth, err = apiClient.AuthRefresh(apiToken)
if err != nil {
log.WithError(err).Error("Could refresh token in new client")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Warn("Could not discard auth generated after auth refresh")
}
return
}
// We load the current user again because it should now have addresses loaded.
apiUser, err = apiClient.CurrentUser()
if err != nil {
log.WithError(err).Error("Could not get current API user")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after get current user failed.")
}
return
}
apiToken = auth.UID() + ":" + auth.RefreshToken
activeEmails := apiClient.Addresses().ActiveEmails()
if _, err = b.credStorer.Add(apiUser.ID, apiUser.Name, apiToken, mbPassword, activeEmails); err != nil {
log.WithError(err).Error("Could not add user to credentials store")
return
}
// If it's a new user, generate the user object.
if !hasUser {
user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, apiClient, b.storeCache, b.config.GetDBDir())
if err != nil {
log.WithField("user", apiUser.ID).WithError(err).Error("Could not create user")
return
}
}
// Set up the user auth and store (which we do for both new and existing users).
if err = user.init(b.idleUpdates, apiClient); err != nil {
log.WithField("user", user.userID).WithError(err).Error("Could not initialise user")
return
}
if !hasUser {
b.users = append(b.users, user)
b.SendMetric(m.New(m.Setup, m.NewUser, m.NoLabel))
}
b.events.Emit(events.UserRefreshEvent, apiUser.ID)
return user, err
}
// GetUsers returns all added users into keychain (even logged out users).
func (b *Bridge) GetUsers() []*User {
b.lock.RLock()
defer b.lock.RUnlock()
return b.users
}
// GetUser returns a user by `query` which is compared to users' ID, username
// or any attached e-mail address.
func (b *Bridge) GetUser(query string) (*User, error) {
b.crashBandicoot(query)
b.lock.RLock()
defer b.lock.RUnlock()
for _, user := range b.users {
if strings.EqualFold(user.ID(), query) || strings.EqualFold(user.Username(), query) {
return user, nil
}
for _, address := range user.GetAddresses() {
if strings.EqualFold(address, query) {
return user, nil
}
}
}
return nil, errors.New("user " + query + " not found")
}
// ClearData closes all connections (to release db files and so on) and clears all data.
func (b *Bridge) ClearData() error {
var result *multierror.Error
for _, user := range b.users {
if err := user.Logout(); err != nil {
result = multierror.Append(result, err)
}
if err := user.closeStore(); err != nil {
result = multierror.Append(result, err)
}
}
if err := b.config.ClearData(); err != nil {
result = multierror.Append(result, err)
}
return result.ErrorOrNil()
}
// DeleteUser deletes user completely; it logs user out from the API, stops any
// active connection, deletes from credentials store and removes from the Bridge struct.
func (b *Bridge) DeleteUser(userID string, clearStore bool) error {
b.lock.Lock()
defer b.lock.Unlock()
log := log.WithField("user", userID)
for idx, user := range b.users {
if user.ID() == userID {
if err := user.Logout(); err != nil {
log.WithError(err).Error("Cannot logout user")
// We can try to continue to remove the user.
// Token will still be valid, but will expire eventually.
}
if err := user.closeStore(); err != nil {
log.WithError(err).Error("Failed to close user store")
}
if clearStore {
// Clear cache after closing connections (done in logout).
if err := user.clearStore(); err != nil {
log.WithError(err).Error("Failed to clear user")
}
}
if err := b.credStorer.Delete(userID); err != nil {
log.WithError(err).Error("Cannot remove user")
return err
}
b.users = append(b.users[:idx], b.users[idx+1:]...)
return nil
}
}
return errors.New("user " + userID + " not found")
}
// ReportBug reports a new bug from the user.
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
apiClient := b.pmapiClientFactory("bug_reporter")
title := "[Bridge] Bug"
err := apiClient.ReportBugWithEmailClient(
osType,
osVersion,
title,
description,
accountName,
address,
emailClient,
)
if err != nil {
log.Error("Reporting bug failed: ", err)
if err := b.updater.InstallUpdate(version); err != nil {
return err
}
log.Info("Bug successfully reported")
return b.versioner.RemoveOtherVersions(version.Version)
}
// FactoryReset will remove all local cache and settings.
// It will also downgrade to latest stable version if user is on early version.
func (b *Bridge) FactoryReset() {
wasEarly := b.GetUpdateChannel() == updater.EarlyChannel
b.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel))
if wasEarly {
if err := b.resetToLatestStable(); err != nil {
log.WithError(err).Error("Failed to reset to latest stable version")
}
}
if err := b.Users.ClearData(); err != nil {
log.WithError(err).Error("Failed to remove bridge data")
}
if err := b.Users.ClearUsers(); err != nil {
log.WithError(err).Error("Failed to remove bridge users")
}
}
// GetKeychainApp returns current keychain helper.
func (b *Bridge) GetKeychainApp() string {
return b.settings.Get(settings.PreferredKeychainKey)
}
// SetKeychainApp sets current keychain helper.
func (b *Bridge) SetKeychainApp(helper string) {
b.settings.Set(settings.PreferredKeychainKey, helper)
}
func (b *Bridge) EnableCache() error {
if err := b.Users.EnableCache(); err != nil {
return err
}
b.settings.SetBool(settings.CacheEnabledKey, true)
return nil
}
// SendMetric sends a metric. We don't want to return any errors, only log them.
func (b *Bridge) SendMetric(m m.Metric) {
apiClient := b.pmapiClientFactory("metric_reporter")
cat, act, lab := m.Get()
err := apiClient.SendSimpleMetric(string(cat), string(act), string(lab))
if err != nil {
log.Error("Sending metric failed: ", err)
}
log.WithFields(logrus.Fields{
"cat": cat,
"act": act,
"lab": lab,
}).Debug("Metric successfully sent")
}
// GetCurrentClient returns currently connected client (e.g. Thunderbird).
func (b *Bridge) GetCurrentClient() string {
res := b.userAgentClientName
if b.userAgentClientVersion != "" {
res = res + " " + b.userAgentClientVersion
}
return res
}
// SetCurrentClient updates client info (e.g. Thunderbird) and sets the user agent
// on pmapi. By default no client is used, IMAP has to detect it on first login.
func (b *Bridge) SetCurrentClient(clientName, clientVersion string) {
b.userAgentClientName = clientName
b.userAgentClientVersion = clientVersion
b.updateCurrentUserAgent()
}
// SetCurrentOS updates OS and sets the user agent on pmapi. By default we use
// `runtime.GOOS`, but this can be overridden in case of better detection.
func (b *Bridge) SetCurrentOS(os string) {
b.userAgentOS = os
b.updateCurrentUserAgent()
}
// GetIMAPUpdatesChannel sets the channel on which idle events should be sent.
func (b *Bridge) GetIMAPUpdatesChannel() chan interface{} {
if b.idleUpdates == nil {
log.Warn("Bridge updates channel is nil")
func (b *Bridge) DisableCache() error {
if err := b.Users.DisableCache(); err != nil {
return err
}
return b.idleUpdates
b.settings.SetBool(settings.CacheEnabledKey, false)
// Reset back to the default location when disabling.
b.settings.Set(settings.CacheLocationKey, b.cacheProvider.GetDefaultMessageCacheDir())
return nil
}
// AllowDoH instructs bridge to use DoH to access an API proxy if necessary.
// It also needs to work before bridge is initialised (because we may need to use the proxy at startup).
func AllowDoH() {
pmapi.GlobalAllowDoH()
func (b *Bridge) MigrateCache(from, to string) error {
if err := b.Users.MigrateCache(from, to); err != nil {
return err
}
b.settings.Set(settings.CacheLocationKey, to)
return nil
}
// DisallowDoH instructs bridge to not use DoH to access an API proxy if necessary.
// It also needs to work before bridge is initialised (because we may need to use the proxy at startup).
func DisallowDoH() {
pmapi.GlobalDisallowDoH()
// SetProxyAllowed instructs the app whether to use DoH to access an API proxy if necessary.
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
func (b *Bridge) SetProxyAllowed(proxyAllowed bool) {
b.settings.SetBool(settings.AllowProxyKey, proxyAllowed)
if proxyAllowed {
b.clientManager.AllowProxy()
} else {
b.clientManager.DisallowProxy()
}
}
func (b *Bridge) updateCurrentUserAgent() {
UpdateCurrentUserAgent(b.version, b.userAgentOS, b.userAgentClientName, b.userAgentClientVersion)
// GetProxyAllowed returns whether use of DoH is enabled to access an API proxy if necessary.
func (b *Bridge) GetProxyAllowed() bool {
return b.settings.GetBool(settings.AllowProxyKey)
}
// hasUser returns whether the bridge currently has a user with ID `id`.
func (b *Bridge) hasUser(id string) (user *User, ok bool) {
for _, u := range b.users {
if u.ID() == id {
user, ok = u, true
// AddError add an error to a global error list if it does not contain it yet. Adding nil is noop.
func (b *Bridge) AddError(err error) {
if err == nil {
return
}
if b.HasError(err) {
return
}
b.errors = append(b.errors, err)
}
// DelError removes an error from global error list.
func (b *Bridge) DelError(err error) {
for idx, val := range b.errors {
if val == err {
b.errors = append(b.errors[:idx], b.errors[idx+1:]...)
return
}
}
return
}
// "Easter egg" for testing purposes.
func (b *Bridge) crashBandicoot(username string) {
if username == "crash@bandicoot" {
panic("Your wish is my command… I crash!")
// HasError returnes true if global error list contains an err.
func (b *Bridge) HasError(err error) bool {
for _, val := range b.errors {
if val == err {
return true
}
}
return false
}
// GetLastVersion returns the version which was used in previous execution of
// Bridge.
func (b *Bridge) GetLastVersion() string {
return b.lastVersion
}
// IsFirstStart returns true when Bridge is running for first time or after
// factory reset.
func (b *Bridge) IsFirstStart() bool {
return b.isFirstStart
}

View File

@ -1,233 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"errors"
"testing"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/metrics"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func TestBridgeFinishLoginBadPassword(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Init bridge with no user from keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Set up mocks for FinishLogin.
err := errors.New("bad password")
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, err)
m.pmapiClient.EXPECT().Logout().Return(nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", err)
}
func TestBridgeFinishLoginUpgradeApplication(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Init bridge with no user from keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Set up mocks for FinishLogin.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, pmapi.ErrUpgradeApplication)
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "")
err := errors.New("Cannot logout when upgrade needed")
m.pmapiClient.EXPECT().Logout().Return(err)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", pmapi.ErrUpgradeApplication)
}
func refreshWithToken(token string) *pmapi.Auth {
return &pmapi.Auth{
RefreshToken: token,
KeySalt: "", // No salting in tests.
}
}
func credentialsWithToken(token string) *credentials.Credentials {
tmp := &credentials.Credentials{}
*tmp = *testCredentials
tmp.APIToken = token
return tmp
}
func TestBridgeFinishLoginNewUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Bridge finds no users in the keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Get user to be able to setup new client with proper userID.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
// Setup of new client.
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
// Set up mocks for authorising the new user (in user.init).
m.credentialsStore.EXPECT().Add("user", "username", ":afterLogin", testCredentials.MailboxPassword, []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil).Times(2)
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil)
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken("afterCredentials"), nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":afterCredentials").Return(nil)
// Set up mocks for creating the user's store (in store.New).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
// Emit event for new user and send metrics.
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Setup), string(metrics.NewUser), string(metrics.NoLabel))
// Set up mocks for starting the store's event loop (in store.New).
// The event loop runs in another goroutine so this might happen at any time.
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Set up mocks for performing the initial store sync.
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
}
func TestBridgeFinishLoginExistingUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
loggedOutCreds := *testCredentials
loggedOutCreds.APIToken = ""
loggedOutCreds.MailboxPassword = ""
// Bridge finds one logged out user in the keychain.
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
// New user
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil)
// Init user
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil)
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrInvalidToken)
m.pmapiClient.EXPECT().Addresses().Return(nil)
// Get user to be able to setup new client with proper userID.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
// Setup of new client.
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
// Set up mocks for authorising the new user (in user.init).
m.credentialsStore.EXPECT().Add("user", "username", ":afterLogin", testCredentials.MailboxPassword, []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil)
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil)
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken("afterCredentials"), nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":afterCredentials").Return(nil)
// Set up mocks for creating the user's store (in store.New).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
// Reload account list in GUI.
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
// Set up mocks for starting the store's event loop (in store.New)
// The event loop runs in another goroutine so this might happen at any time.
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Set up mocks for performing the initial store sync.
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
}
func TestBridgeDoubleLogin(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Firstly, start bridge with existing user...
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
// Then, try to log in again...
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Logout()
_, err := bridge.FinishLogin(m.pmapiClient, testAuth, testCredentials.MailboxPassword)
assert.Equal(t, "user is already logged in", err.Error())
}
func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) {
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
user, err := bridge.FinishLogin(m.pmapiClient, auth, mailboxPassword)
waitForEvents()
assert.Equal(t, expectedErr, err)
if expectedUserID != "" {
assert.Equal(t, expectedUserID, user.ID())
assert.Equal(t, 1, len(bridge.users))
assert.Equal(t, expectedUserID, bridge.users[0].ID())
} else {
assert.Equal(t, (*User)(nil), user)
assert.Equal(t, 0, len(bridge.users))
}
}

View File

@ -1,162 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"errors"
"testing"
credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/metrics"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func TestNewBridgeNoKeychain(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{}, errors.New("no keychain"))
checkBridgeNew(t, m, []*credentials.Credentials{})
}
func TestNewBridgeWithoutUsersInCredentialsStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
checkBridgeNew(t, m, []*credentials.Credentials{})
}
func TestNewBridgeWithDisconnectedUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil).Times(2)
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
}
func TestNewBridgeWithConnectedUserWithBadToken(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, errors.New("bad token"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
}
func TestNewBridgeWithConnectedUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
// Set up mocks for store initialisation for the authorized user.
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
checkBridgeNew(t, m, []*credentials.Credentials{testCredentials})
}
// Tests two users with different states and checks also the order from
// credentials store is kept also in array of Bridge users.
func TestNewBridgeWithUsers(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().List().Return([]string{"user", "user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil).Times(2)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
// Set up mocks for store initialisation for the unauth user.
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
// Set up mocks for store initialisation for the authorized user.
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected, testCredentials})
}
func TestNewBridgeFirstStart(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.prefProvider.EXPECT().GetBool(preferences.FirstStartKey).Return(true)
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Setup), string(metrics.FirstStart), gomock.Any())
testNewBridge(t, m)
}
func checkBridgeNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
assert.Equal(m.t, len(expectedCredentials), len(bridge.GetUsers()))
credentials := []*credentials.Credentials{}
for _, user := range bridge.users {
credentials = append(credentials, user.creds)
}
assert.Equal(m.t, expectedCredentials, credentials)
}

View File

@ -1,256 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
bridgemocks "github.com/ProtonMail/proton-bridge/internal/bridge/mocks"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/metrics"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
if os.Getenv("VERBOSITY") == "trace" {
logrus.SetLevel(logrus.TraceLevel)
}
os.Exit(m.Run())
}
var (
testAuth = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "tok",
KeySalt: "", // No salting in tests.
}
testAuthRefresh = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "reftok",
KeySalt: "", // No salting in tests.
}
testCredentials = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "user",
Name: "username",
Emails: "user@pm.me",
APIToken: "token",
MailboxPassword: "pass",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: true,
}
testCredentialsSplit = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "users",
Name: "usersname",
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
APIToken: "token",
MailboxPassword: "pass",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: false,
}
testCredentialsDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "user",
Name: "username",
Emails: "user@pm.me",
APIToken: "",
MailboxPassword: "",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: true,
}
testPMAPIUser = &pmapi.User{ //nolint[gochecknoglobals]
ID: "user",
Name: "username",
}
testPMAPIAddress = &pmapi.Address{ //nolint[gochecknoglobals]
ID: "testAddressID",
Type: pmapi.OriginalAddress,
Email: "user@pm.me",
Receive: pmapi.CanReceive,
}
testPMAPIAddresses = []*pmapi.Address{ //nolint[gochecknoglobals]
{ID: "usersAddress1ID", Email: "users@pm.me", Receive: pmapi.CanReceive, Type: pmapi.OriginalAddress},
{ID: "usersAddress2ID", Email: "anotheruser@pm.me", Receive: pmapi.CanReceive, Type: pmapi.AliasAddress},
{ID: "usersAddress3ID", Email: "alsouser@pm.me", Receive: pmapi.CanReceive, Type: pmapi.AliasAddress},
}
testPMAPIEvent = &pmapi.Event{ // nolint[gochecknoglobals]
EventID: "ACXDmTaBub14w==",
}
)
func waitForEvents() {
// Wait for goroutine to add listener.
// E.g. calling login to invoke firstsync event. Functions can end sooner than
// goroutines call the listener mock. We need to wait a little bit before the end of
// the test to capture all event calls. This allows us to detect whether there were
// missing calls, or perhaps whether something was called too many times.
time.Sleep(100 * time.Millisecond)
}
type mocks struct {
t *testing.T
ctrl *gomock.Controller
config *bridgemocks.MockConfiger
PanicHandler *bridgemocks.MockPanicHandler
prefProvider *bridgemocks.MockPreferenceProvider
pmapiClient *bridgemocks.MockPMAPIProvider
credentialsStore *bridgemocks.MockCredentialsStorer
eventListener *MockListener
storeCache *store.Cache
}
func initMocks(t *testing.T) mocks {
mockCtrl := gomock.NewController(t)
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
require.NoError(t, err, "could not get temporary file for store cache")
m := mocks{
t: t,
ctrl: mockCtrl,
config: bridgemocks.NewMockConfiger(mockCtrl),
PanicHandler: bridgemocks.NewMockPanicHandler(mockCtrl),
pmapiClient: bridgemocks.NewMockPMAPIProvider(mockCtrl),
prefProvider: bridgemocks.NewMockPreferenceProvider(mockCtrl),
credentialsStore: bridgemocks.NewMockCredentialsStorer(mockCtrl),
eventListener: NewMockListener(mockCtrl),
storeCache: store.NewCache(cacheFile.Name()),
}
// Ignore heartbeat calls because they always happen.
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Heartbeat), gomock.Any(), gomock.Any()).AnyTimes()
m.prefProvider.EXPECT().Get(preferences.NextHeartbeatKey).AnyTimes()
m.prefProvider.EXPECT().Set(preferences.NextHeartbeatKey, gomock.Any()).AnyTimes()
// Called during clean-up.
m.PanicHandler.EXPECT().HandlePanic().AnyTimes()
return m
}
func testNewBridgeWithUsers(t *testing.T, m mocks) *Bridge {
// Init for user.
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Init for users.
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("users", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
m.credentialsStore.EXPECT().List().Return([]string{"user", "users"}, nil)
return testNewBridge(t, m)
}
func testNewBridge(t *testing.T, m mocks) *Bridge {
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
require.NoError(t, err, "could not get temporary file for store cache")
m.prefProvider.EXPECT().GetBool(preferences.FirstStartKey).Return(false).AnyTimes()
m.prefProvider.EXPECT().GetBool(preferences.AllowProxyKey).Return(false).AnyTimes()
m.config.EXPECT().GetDBDir().Return("/tmp").AnyTimes()
m.config.EXPECT().GetIMAPCachePath().Return(cacheFile.Name()).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any()).AnyTimes()
m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
pmapiClientFactory := func(userID string) PMAPIProvider {
log.WithField("userID", userID).Info("Creating new pmclient")
return m.pmapiClient
}
bridge := New(m.config, m.prefProvider, m.PanicHandler, m.eventListener, "ver", pmapiClientFactory, m.credentialsStore)
waitForEvents()
return bridge
}
func cleanUpBridgeUserData(b *Bridge) {
for _, user := range b.users {
_ = user.clearStore()
}
}
func TestClearData(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "alsouser@pm.me")
m.pmapiClient.EXPECT().Logout()
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Logout()
m.credentialsStore.EXPECT().Logout("users").Return(nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil)
m.config.EXPECT().ClearData().Return(nil)
require.NoError(t, bridge.ClearData())
waitForEvents()
}

View File

@ -1,121 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"errors"
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/stretchr/testify/assert"
)
func TestGetNoUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "nouser", -1, "user nouser not found")
}
func TestGetUserByID(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "user", 0, "")
checkBridgeGetUser(t, m, "users", 1, "")
}
func TestGetUserByName(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "username", 0, "")
checkBridgeGetUser(t, m, "usersname", 1, "")
}
func TestGetUserByEmail(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "user@pm.me", 0, "")
checkBridgeGetUser(t, m, "users@pm.me", 1, "")
checkBridgeGetUser(t, m, "anotheruser@pm.me", 1, "")
checkBridgeGetUser(t, m, "alsouser@pm.me", 1, "")
}
func TestDeleteUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Delete("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := bridge.DeleteUser("user", true)
assert.NoError(t, err)
assert.Equal(t, 1, len(bridge.users))
}
// Even when logout fails, delete is done.
func TestDeleteUserWithFailingLogout(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed"))
m.credentialsStore.EXPECT().Delete("user").Return(nil).Times(2)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := bridge.DeleteUser("user", true)
assert.NoError(t, err)
assert.Equal(t, 1, len(bridge.users))
}
func checkBridgeGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
user, err := bridge.GetUser(query)
waitForEvents()
if expectedError != "" {
assert.Equal(m.t, expectedError, err.Error())
} else {
assert.NoError(m.t, err)
}
var expectedUser *User
if index >= 0 {
expectedUser = bridge.users[index]
}
assert.Equal(m.t, expectedUser, user)
}

View File

@ -0,0 +1,205 @@
// 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 bridge
import (
"archive/zip"
"bytes"
"context"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/ProtonMail/proton-bridge/internal/logging"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
const MaxAttachmentSize = 7 * 1024 * 1024 // 7 MB total limit
const MaxCompressedFilesCount = 6
var ErrSizeTooLarge = errors.New("file is too big")
// ReportBug reports a new bug from the user.
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string, attachLogs bool) error {
if user, err := b.GetUser(address); err == nil {
accountName = user.Username()
} else if users := b.GetUsers(); len(users) > 0 {
accountName = users[0].Username()
}
report := pmapi.ReportBugReq{
OS: osType,
OSVersion: osVersion,
Browser: emailClient,
Title: "[Bridge] Bug",
Description: description,
Username: accountName,
Email: address,
}
if attachLogs {
logs, err := b.getMatchingLogs(
func(filename string) bool {
return logging.MatchLogName(filename) && !logging.MatchStackTraceName(filename)
},
)
if err != nil {
log.WithError(err).Error("Can't get log files list")
}
crashes, err := b.getMatchingLogs(
func(filename string) bool {
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
},
)
if err != nil {
log.WithError(err).Error("Can't get crash files list")
}
var matchFiles []string
matchFiles = append(matchFiles, logs[max(0, len(logs)-(MaxCompressedFilesCount/2)):]...)
matchFiles = append(matchFiles, crashes[max(0, len(crashes)-(MaxCompressedFilesCount/2)):]...)
archive, err := zipFiles(matchFiles)
if err != nil {
log.WithError(err).Error("Can't zip logs and crashes")
}
if archive != nil {
report.AddAttachment("logs.zip", "application/zip", archive)
}
}
return b.clientManager.ReportBug(context.Background(), report)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func (b *Bridge) getMatchingLogs(filenameMatchFunc func(string) bool) (filenames []string, err error) {
logsPath, err := b.locations.ProvideLogsPath()
if err != nil {
return nil, err
}
files, err := ioutil.ReadDir(logsPath)
if err != nil {
return nil, err
}
var matchFiles []string
for _, file := range files {
if filenameMatchFunc(file.Name()) {
matchFiles = append(matchFiles, filepath.Join(logsPath, file.Name()))
}
}
sort.Strings(matchFiles) // Sorted by timestamp: oldest first.
return matchFiles, nil
}
type LimitedBuffer struct {
capacity int
buf *bytes.Buffer
}
func NewLimitedBuffer(capacity int) *LimitedBuffer {
return &LimitedBuffer{
capacity: capacity,
buf: bytes.NewBuffer(make([]byte, 0, capacity)),
}
}
func (b *LimitedBuffer) Write(p []byte) (n int, err error) {
if len(p)+b.buf.Len() > b.capacity {
return 0, ErrSizeTooLarge
}
return b.buf.Write(p)
}
func (b *LimitedBuffer) Read(p []byte) (n int, err error) {
return b.buf.Read(p)
}
func zipFiles(filenames []string) (io.Reader, error) {
if len(filenames) == 0 {
return nil, nil
}
buf := NewLimitedBuffer(MaxAttachmentSize)
w := zip.NewWriter(buf)
defer w.Close() //nolint:errcheck
for _, file := range filenames {
err := addFileToZip(file, w)
if err != nil {
return nil, err
}
}
if err := w.Close(); err != nil {
return nil, err
}
return buf, nil
}
func addFileToZip(filename string, writer *zip.Writer) error {
fileReader, err := os.Open(filepath.Clean(filename))
if err != nil {
return err
}
defer fileReader.Close() //nolint:errcheck,gosec
fileInfo, err := fileReader.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(fileInfo)
if err != nil {
return err
}
header.Method = zip.Deflate
header.Name = filepath.Base(filename)
fileWriter, err := writer.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(fileWriter, fileReader)
if err != nil {
return err
}
err = fileReader.Close()
return err
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge

View File

@ -1,22 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./credits.sh at Thu Apr 16 13:43:04 CEST 2020. DO NOT EDIT.
package bridge
const Credits = "github.com/0xAX/notificator;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-imap-quota;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/danieljoos/wincred;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"

View File

@ -1,107 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./listener/listener.go
// Package bridge is a generated GoMock package.
package bridge
import (
reflect "reflect"
time "time"
gomock "github.com/golang/mock/gomock"
)
// MockListener is a mock of Listener interface
type MockListener struct {
ctrl *gomock.Controller
recorder *MockListenerMockRecorder
}
// MockListenerMockRecorder is the mock recorder for MockListener
type MockListenerMockRecorder struct {
mock *MockListener
}
// NewMockListener creates a new mock instance
func NewMockListener(ctrl *gomock.Controller) *MockListener {
mock := &MockListener{ctrl: ctrl}
mock.recorder = &MockListenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
return m.recorder
}
// SetLimit mocks base method
func (m *MockListener) SetLimit(eventName string, limit time.Duration) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetLimit", eventName, limit)
}
// SetLimit indicates an expected call of SetLimit
func (mr *MockListenerMockRecorder) SetLimit(eventName, limit interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), eventName, limit)
}
// Add mocks base method
func (m *MockListener) Add(eventName string, channel chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Add", eventName, channel)
}
// Add indicates an expected call of Add
func (mr *MockListenerMockRecorder) Add(eventName, channel interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), eventName, channel)
}
// Remove mocks base method
func (m *MockListener) Remove(eventName string, channel chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Remove", eventName, channel)
}
// Remove indicates an expected call of Remove
func (mr *MockListenerMockRecorder) Remove(eventName, channel interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), eventName, channel)
}
// Emit mocks base method
func (m *MockListener) Emit(eventName, data string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Emit", eventName, data)
}
// Emit indicates an expected call of Emit
func (mr *MockListenerMockRecorder) Emit(eventName, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), eventName, data)
}
// SetBuffer mocks base method
func (m *MockListener) SetBuffer(eventName string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetBuffer", eventName)
}
// SetBuffer indicates an expected call of SetBuffer
func (mr *MockListenerMockRecorder) SetBuffer(eventName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), eventName)
}
// RetryEmit mocks base method
func (m *MockListener) RetryEmit(eventName string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RetryEmit", eventName)
}
// RetryEmit indicates an expected call of RetryEmit
func (mr *MockListenerMockRecorder) RetryEmit(eventName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), eventName)
}

View File

@ -1,924 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ProtonMail/proton-bridge/internal/bridge (interfaces: Configer,PreferenceProvider,PanicHandler,PMAPIProvider,CredentialsStorer)
// Package mocks is a generated GoMock package.
package mocks
import (
io "io"
reflect "reflect"
crypto "github.com/ProtonMail/gopenpgp/crypto"
credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
)
// MockConfiger is a mock of Configer interface
type MockConfiger struct {
ctrl *gomock.Controller
recorder *MockConfigerMockRecorder
}
// MockConfigerMockRecorder is the mock recorder for MockConfiger
type MockConfigerMockRecorder struct {
mock *MockConfiger
}
// NewMockConfiger creates a new mock instance
func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger {
mock := &MockConfiger{ctrl: ctrl}
mock.recorder = &MockConfigerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder {
return m.recorder
}
// ClearData mocks base method
func (m *MockConfiger) ClearData() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClearData")
ret0, _ := ret[0].(error)
return ret0
}
// ClearData indicates an expected call of ClearData
func (mr *MockConfigerMockRecorder) ClearData() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearData", reflect.TypeOf((*MockConfiger)(nil).ClearData))
}
// GetAPIConfig mocks base method
func (m *MockConfiger) GetAPIConfig() *pmapi.ClientConfig {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAPIConfig")
ret0, _ := ret[0].(*pmapi.ClientConfig)
return ret0
}
// GetAPIConfig indicates an expected call of GetAPIConfig
func (mr *MockConfigerMockRecorder) GetAPIConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIConfig", reflect.TypeOf((*MockConfiger)(nil).GetAPIConfig))
}
// GetDBDir mocks base method
func (m *MockConfiger) GetDBDir() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetDBDir")
ret0, _ := ret[0].(string)
return ret0
}
// GetDBDir indicates an expected call of GetDBDir
func (mr *MockConfigerMockRecorder) GetDBDir() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDBDir", reflect.TypeOf((*MockConfiger)(nil).GetDBDir))
}
// GetIMAPCachePath mocks base method
func (m *MockConfiger) GetIMAPCachePath() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIMAPCachePath")
ret0, _ := ret[0].(string)
return ret0
}
// GetIMAPCachePath indicates an expected call of GetIMAPCachePath
func (mr *MockConfigerMockRecorder) GetIMAPCachePath() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIMAPCachePath", reflect.TypeOf((*MockConfiger)(nil).GetIMAPCachePath))
}
// MockPreferenceProvider is a mock of PreferenceProvider interface
type MockPreferenceProvider struct {
ctrl *gomock.Controller
recorder *MockPreferenceProviderMockRecorder
}
// MockPreferenceProviderMockRecorder is the mock recorder for MockPreferenceProvider
type MockPreferenceProviderMockRecorder struct {
mock *MockPreferenceProvider
}
// NewMockPreferenceProvider creates a new mock instance
func NewMockPreferenceProvider(ctrl *gomock.Controller) *MockPreferenceProvider {
mock := &MockPreferenceProvider{ctrl: ctrl}
mock.recorder = &MockPreferenceProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPreferenceProvider) EXPECT() *MockPreferenceProviderMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockPreferenceProvider) Get(arg0 string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(string)
return ret0
}
// Get indicates an expected call of Get
func (mr *MockPreferenceProviderMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPreferenceProvider)(nil).Get), arg0)
}
// GetBool mocks base method
func (m *MockPreferenceProvider) GetBool(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBool", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// GetBool indicates an expected call of GetBool
func (mr *MockPreferenceProviderMockRecorder) GetBool(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockPreferenceProvider)(nil).GetBool), arg0)
}
// GetInt mocks base method
func (m *MockPreferenceProvider) GetInt(arg0 string) int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInt", arg0)
ret0, _ := ret[0].(int)
return ret0
}
// GetInt indicates an expected call of GetInt
func (mr *MockPreferenceProviderMockRecorder) GetInt(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockPreferenceProvider)(nil).GetInt), arg0)
}
// Set mocks base method
func (m *MockPreferenceProvider) Set(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Set", arg0, arg1)
}
// Set indicates an expected call of Set
func (mr *MockPreferenceProviderMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockPreferenceProvider)(nil).Set), arg0, arg1)
}
// MockPanicHandler is a mock of PanicHandler interface
type MockPanicHandler struct {
ctrl *gomock.Controller
recorder *MockPanicHandlerMockRecorder
}
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
type MockPanicHandlerMockRecorder struct {
mock *MockPanicHandler
}
// NewMockPanicHandler creates a new mock instance
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
mock := &MockPanicHandler{ctrl: ctrl}
mock.recorder = &MockPanicHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
return m.recorder
}
// HandlePanic mocks base method
func (m *MockPanicHandler) HandlePanic() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "HandlePanic")
}
// HandlePanic indicates an expected call of HandlePanic
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
}
// MockPMAPIProvider is a mock of PMAPIProvider interface
type MockPMAPIProvider struct {
ctrl *gomock.Controller
recorder *MockPMAPIProviderMockRecorder
}
// MockPMAPIProviderMockRecorder is the mock recorder for MockPMAPIProvider
type MockPMAPIProviderMockRecorder struct {
mock *MockPMAPIProvider
}
// NewMockPMAPIProvider creates a new mock instance
func NewMockPMAPIProvider(ctrl *gomock.Controller) *MockPMAPIProvider {
mock := &MockPMAPIProvider{ctrl: ctrl}
mock.recorder = &MockPMAPIProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPMAPIProvider) EXPECT() *MockPMAPIProviderMockRecorder {
return m.recorder
}
// Addresses mocks base method
func (m *MockPMAPIProvider) Addresses() pmapi.AddressList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Addresses")
ret0, _ := ret[0].(pmapi.AddressList)
return ret0
}
// Addresses indicates an expected call of Addresses
func (mr *MockPMAPIProviderMockRecorder) Addresses() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockPMAPIProvider)(nil).Addresses))
}
// Auth mocks base method
func (m *MockPMAPIProvider) Auth(arg0, arg1 string, arg2 *pmapi.AuthInfo) (*pmapi.Auth, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Auth", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Auth)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Auth indicates an expected call of Auth
func (mr *MockPMAPIProviderMockRecorder) Auth(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth", reflect.TypeOf((*MockPMAPIProvider)(nil).Auth), arg0, arg1, arg2)
}
// Auth2FA mocks base method
func (m *MockPMAPIProvider) Auth2FA(arg0 string, arg1 *pmapi.Auth) (*pmapi.Auth2FA, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1)
ret0, _ := ret[0].(*pmapi.Auth2FA)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Auth2FA indicates an expected call of Auth2FA
func (mr *MockPMAPIProviderMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockPMAPIProvider)(nil).Auth2FA), arg0, arg1)
}
// AuthInfo mocks base method
func (m *MockPMAPIProvider) AuthInfo(arg0 string) (*pmapi.AuthInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthInfo", arg0)
ret0, _ := ret[0].(*pmapi.AuthInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthInfo indicates an expected call of AuthInfo
func (mr *MockPMAPIProviderMockRecorder) AuthInfo(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthInfo", reflect.TypeOf((*MockPMAPIProvider)(nil).AuthInfo), arg0)
}
// AuthRefresh mocks base method
func (m *MockPMAPIProvider) AuthRefresh(arg0 string) (*pmapi.Auth, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthRefresh", arg0)
ret0, _ := ret[0].(*pmapi.Auth)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthRefresh indicates an expected call of AuthRefresh
func (mr *MockPMAPIProviderMockRecorder) AuthRefresh(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRefresh", reflect.TypeOf((*MockPMAPIProvider)(nil).AuthRefresh), arg0)
}
// CountMessages mocks base method
func (m *MockPMAPIProvider) CountMessages(arg0 string) ([]*pmapi.MessagesCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CountMessages", arg0)
ret0, _ := ret[0].([]*pmapi.MessagesCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CountMessages indicates an expected call of CountMessages
func (mr *MockPMAPIProviderMockRecorder) CountMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).CountMessages), arg0)
}
// CreateAttachment mocks base method
func (m *MockPMAPIProvider) CreateAttachment(arg0 *pmapi.Attachment, arg1, arg2 io.Reader) (*pmapi.Attachment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Attachment)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateAttachment indicates an expected call of CreateAttachment
func (mr *MockPMAPIProviderMockRecorder) CreateAttachment(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateAttachment), arg0, arg1, arg2)
}
// CreateDraft mocks base method
func (m *MockPMAPIProvider) CreateDraft(arg0 *pmapi.Message, arg1 string, arg2 int) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateDraft indicates an expected call of CreateDraft
func (mr *MockPMAPIProviderMockRecorder) CreateDraft(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateDraft), arg0, arg1, arg2)
}
// CreateLabel mocks base method
func (m *MockPMAPIProvider) CreateLabel(arg0 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLabel", arg0)
ret0, _ := ret[0].(*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateLabel indicates an expected call of CreateLabel
func (mr *MockPMAPIProviderMockRecorder) CreateLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateLabel), arg0)
}
// CurrentUser mocks base method
func (m *MockPMAPIProvider) CurrentUser() (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CurrentUser")
ret0, _ := ret[0].(*pmapi.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CurrentUser indicates an expected call of CurrentUser
func (mr *MockPMAPIProviderMockRecorder) CurrentUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockPMAPIProvider)(nil).CurrentUser))
}
// DecryptAndVerifyCards mocks base method
func (m *MockPMAPIProvider) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0)
ret0, _ := ret[0].([]pmapi.Card)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards
func (mr *MockPMAPIProviderMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockPMAPIProvider)(nil).DecryptAndVerifyCards), arg0)
}
// DeleteLabel mocks base method
func (m *MockPMAPIProvider) DeleteLabel(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteLabel", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteLabel indicates an expected call of DeleteLabel
func (mr *MockPMAPIProviderMockRecorder) DeleteLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).DeleteLabel), arg0)
}
// DeleteMessages mocks base method
func (m *MockPMAPIProvider) DeleteMessages(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteMessages", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteMessages indicates an expected call of DeleteMessages
func (mr *MockPMAPIProviderMockRecorder) DeleteMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).DeleteMessages), arg0)
}
// EmptyFolder mocks base method
func (m *MockPMAPIProvider) EmptyFolder(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// EmptyFolder indicates an expected call of EmptyFolder
func (mr *MockPMAPIProviderMockRecorder) EmptyFolder(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockPMAPIProvider)(nil).EmptyFolder), arg0, arg1)
}
// GetAttachment mocks base method
func (m *MockPMAPIProvider) GetAttachment(arg0 string) (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAttachment", arg0)
ret0, _ := ret[0].(io.ReadCloser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAttachment indicates an expected call of GetAttachment
func (mr *MockPMAPIProviderMockRecorder) GetAttachment(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockPMAPIProvider)(nil).GetAttachment), arg0)
}
// GetContactByID mocks base method
func (m *MockPMAPIProvider) GetContactByID(arg0 string) (pmapi.Contact, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactByID", arg0)
ret0, _ := ret[0].(pmapi.Contact)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContactByID indicates an expected call of GetContactByID
func (mr *MockPMAPIProviderMockRecorder) GetContactByID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockPMAPIProvider)(nil).GetContactByID), arg0)
}
// GetContactEmailByEmail mocks base method
func (m *MockPMAPIProvider) GetContactEmailByEmail(arg0 string, arg1, arg2 int) ([]pmapi.ContactEmail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2)
ret0, _ := ret[0].([]pmapi.ContactEmail)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail
func (mr *MockPMAPIProviderMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockPMAPIProvider)(nil).GetContactEmailByEmail), arg0, arg1, arg2)
}
// GetEvent mocks base method
func (m *MockPMAPIProvider) GetEvent(arg0 string) (*pmapi.Event, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetEvent", arg0)
ret0, _ := ret[0].(*pmapi.Event)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetEvent indicates an expected call of GetEvent
func (mr *MockPMAPIProviderMockRecorder) GetEvent(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockPMAPIProvider)(nil).GetEvent), arg0)
}
// GetMailSettings mocks base method
func (m *MockPMAPIProvider) GetMailSettings() (pmapi.MailSettings, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMailSettings")
ret0, _ := ret[0].(pmapi.MailSettings)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMailSettings indicates an expected call of GetMailSettings
func (mr *MockPMAPIProviderMockRecorder) GetMailSettings() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockPMAPIProvider)(nil).GetMailSettings))
}
// GetMessage mocks base method
func (m *MockPMAPIProvider) GetMessage(arg0 string) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMessage", arg0)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMessage indicates an expected call of GetMessage
func (mr *MockPMAPIProviderMockRecorder) GetMessage(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockPMAPIProvider)(nil).GetMessage), arg0)
}
// GetPublicKeysForEmail mocks base method
func (m *MockPMAPIProvider) GetPublicKeysForEmail(arg0 string) ([]pmapi.PublicKey, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0)
ret0, _ := ret[0].([]pmapi.PublicKey)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail
func (mr *MockPMAPIProviderMockRecorder) GetPublicKeysForEmail(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockPMAPIProvider)(nil).GetPublicKeysForEmail), arg0)
}
// Import mocks base method
func (m *MockPMAPIProvider) Import(arg0 []*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Import", arg0)
ret0, _ := ret[0].([]*pmapi.ImportMsgRes)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Import indicates an expected call of Import
func (mr *MockPMAPIProviderMockRecorder) Import(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockPMAPIProvider)(nil).Import), arg0)
}
// KeyRingForAddressID mocks base method
func (m *MockPMAPIProvider) KeyRingForAddressID(arg0 string) *crypto.KeyRing {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
ret0, _ := ret[0].(*crypto.KeyRing)
return ret0
}
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
func (mr *MockPMAPIProviderMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockPMAPIProvider)(nil).KeyRingForAddressID), arg0)
}
// LabelMessages mocks base method
func (m *MockPMAPIProvider) LabelMessages(arg0 []string, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// LabelMessages indicates an expected call of LabelMessages
func (mr *MockPMAPIProviderMockRecorder) LabelMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).LabelMessages), arg0, arg1)
}
// ListLabels mocks base method
func (m *MockPMAPIProvider) ListLabels() ([]*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListLabels")
ret0, _ := ret[0].([]*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListLabels indicates an expected call of ListLabels
func (mr *MockPMAPIProviderMockRecorder) ListLabels() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockPMAPIProvider)(nil).ListLabels))
}
// ListMessages mocks base method
func (m *MockPMAPIProvider) ListMessages(arg0 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListMessages", arg0)
ret0, _ := ret[0].([]*pmapi.Message)
ret1, _ := ret[1].(int)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListMessages indicates an expected call of ListMessages
func (mr *MockPMAPIProviderMockRecorder) ListMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).ListMessages), arg0)
}
// Logout mocks base method
func (m *MockPMAPIProvider) Logout() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout")
ret0, _ := ret[0].(error)
return ret0
}
// Logout indicates an expected call of Logout
func (mr *MockPMAPIProviderMockRecorder) Logout() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockPMAPIProvider)(nil).Logout))
}
// MarkMessagesRead mocks base method
func (m *MockPMAPIProvider) MarkMessagesRead(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesRead", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// MarkMessagesRead indicates an expected call of MarkMessagesRead
func (mr *MockPMAPIProviderMockRecorder) MarkMessagesRead(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockPMAPIProvider)(nil).MarkMessagesRead), arg0)
}
// MarkMessagesUnread mocks base method
func (m *MockPMAPIProvider) MarkMessagesUnread(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread
func (mr *MockPMAPIProviderMockRecorder) MarkMessagesUnread(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockPMAPIProvider)(nil).MarkMessagesUnread), arg0)
}
// ReportBugWithEmailClient mocks base method
func (m *MockPMAPIProvider) ReportBugWithEmailClient(arg0, arg1, arg2, arg3, arg4, arg5, arg6 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReportBugWithEmailClient", arg0, arg1, arg2, arg3, arg4, arg5, arg6)
ret0, _ := ret[0].(error)
return ret0
}
// ReportBugWithEmailClient indicates an expected call of ReportBugWithEmailClient
func (mr *MockPMAPIProviderMockRecorder) ReportBugWithEmailClient(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBugWithEmailClient", reflect.TypeOf((*MockPMAPIProvider)(nil).ReportBugWithEmailClient), arg0, arg1, arg2, arg3, arg4, arg5, arg6)
}
// SendMessage mocks base method
func (m *MockPMAPIProvider) SendMessage(arg0 string, arg1 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendMessage", arg0, arg1)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(*pmapi.Message)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// SendMessage indicates an expected call of SendMessage
func (mr *MockPMAPIProviderMockRecorder) SendMessage(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockPMAPIProvider)(nil).SendMessage), arg0, arg1)
}
// SendSimpleMetric mocks base method
func (m *MockPMAPIProvider) SendSimpleMetric(arg0, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SendSimpleMetric indicates an expected call of SendSimpleMetric
func (mr *MockPMAPIProviderMockRecorder) SendSimpleMetric(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockPMAPIProvider)(nil).SendSimpleMetric), arg0, arg1, arg2)
}
// SetAuths mocks base method
func (m *MockPMAPIProvider) SetAuths(arg0 chan<- *pmapi.Auth) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetAuths", arg0)
}
// SetAuths indicates an expected call of SetAuths
func (mr *MockPMAPIProviderMockRecorder) SetAuths(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAuths", reflect.TypeOf((*MockPMAPIProvider)(nil).SetAuths), arg0)
}
// UnlabelMessages mocks base method
func (m *MockPMAPIProvider) UnlabelMessages(arg0 []string, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UnlabelMessages indicates an expected call of UnlabelMessages
func (mr *MockPMAPIProviderMockRecorder) UnlabelMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).UnlabelMessages), arg0, arg1)
}
// Unlock mocks base method
func (m *MockPMAPIProvider) Unlock(arg0 string) (*crypto.KeyRing, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unlock", arg0)
ret0, _ := ret[0].(*crypto.KeyRing)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Unlock indicates an expected call of Unlock
func (mr *MockPMAPIProviderMockRecorder) Unlock(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockPMAPIProvider)(nil).Unlock), arg0)
}
// UnlockAddresses mocks base method
func (m *MockPMAPIProvider) UnlockAddresses(arg0 []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnlockAddresses", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UnlockAddresses indicates an expected call of UnlockAddresses
func (mr *MockPMAPIProviderMockRecorder) UnlockAddresses(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockAddresses", reflect.TypeOf((*MockPMAPIProvider)(nil).UnlockAddresses), arg0)
}
// UpdateLabel mocks base method
func (m *MockPMAPIProvider) UpdateLabel(arg0 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateLabel", arg0)
ret0, _ := ret[0].(*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateLabel indicates an expected call of UpdateLabel
func (mr *MockPMAPIProviderMockRecorder) UpdateLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).UpdateLabel), arg0)
}
// UpdateUser mocks base method
func (m *MockPMAPIProvider) UpdateUser() (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser")
ret0, _ := ret[0].(*pmapi.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateUser indicates an expected call of UpdateUser
func (mr *MockPMAPIProviderMockRecorder) UpdateUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockPMAPIProvider)(nil).UpdateUser))
}
// MockCredentialsStorer is a mock of CredentialsStorer interface
type MockCredentialsStorer struct {
ctrl *gomock.Controller
recorder *MockCredentialsStorerMockRecorder
}
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer
type MockCredentialsStorerMockRecorder struct {
mock *MockCredentialsStorer
}
// NewMockCredentialsStorer creates a new mock instance
func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer {
mock := &MockCredentialsStorer{ctrl: ctrl}
mock.recorder = &MockCredentialsStorerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
return m.recorder
}
// Add mocks base method
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*credentials.Credentials)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Add indicates an expected call of Add
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4)
}
// Delete mocks base method
func (m *MockCredentialsStorer) Delete(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete
func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0)
}
// Get mocks base method
func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(*credentials.Credentials)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0)
}
// List mocks base method
func (m *MockCredentialsStorer) List() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List))
}
// Logout mocks base method
func (m *MockCredentialsStorer) Logout(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Logout indicates an expected call of Logout
func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0)
}
// SwitchAddressMode mocks base method
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SwitchAddressMode indicates an expected call of SwitchAddressMode
func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0)
}
// UpdateEmails mocks base method
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateEmails indicates an expected call of UpdateEmails
func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1)
}
// UpdateToken mocks base method
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateToken indicates an expected call of UpdateToken
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1)
}

View File

@ -1,26 +1,26 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Code generated by ./release-notes.sh at Thu 21 May 2020 07:59:59 AM CEST. DO NOT EDIT.
// Code generated by ./release-notes.sh at 'Fri Jan 22 11:01:06 AM CET 2021'. DO NOT EDIT.
package bridge
const ReleaseNotes = `
`
const ReleaseFixedBugs = `• Fixed ignored import to Sent folder
const ReleaseFixedBugs = `• Fixed sending error caused by inconsistent use of upper and lower case in senders email address
`

View File

@ -0,0 +1,87 @@
// 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 bridge
import (
"fmt"
"path/filepath"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/internal/store/cache"
"github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/message"
)
type storeFactory struct {
cacheProvider CacheProvider
sentryReporter *sentry.Reporter
panicHandler users.PanicHandler
eventListener listener.Listener
events *store.Events
cache cache.Cache
builder *message.Builder
}
func newStoreFactory(
cacheProvider CacheProvider,
sentryReporter *sentry.Reporter,
panicHandler users.PanicHandler,
eventListener listener.Listener,
cache cache.Cache,
builder *message.Builder,
) *storeFactory {
return &storeFactory{
cacheProvider: cacheProvider,
sentryReporter: sentryReporter,
panicHandler: panicHandler,
eventListener: eventListener,
events: store.NewEvents(cacheProvider.GetIMAPCachePath()),
cache: cache,
builder: builder,
}
}
// New creates new store for given user.
func (f *storeFactory) New(user store.BridgeUser) (*store.Store, error) {
return store.New(
f.sentryReporter,
f.panicHandler,
user,
f.eventListener,
f.cache,
f.builder,
getUserStorePath(f.cacheProvider.GetDBDir(), user.ID()),
f.events,
)
}
// Remove removes all store files for given user.
func (f *storeFactory) Remove(userID string) error {
return store.RemoveStore(
f.events,
getUserStorePath(f.cacheProvider.GetDBDir(), userID),
userID,
)
}
// getUserStorePath returns the file path of the store database for the given userID.
func getUserStorePath(storeDir string, userID string) (path string) {
return filepath.Join(storeDir, fmt.Sprintf("mailbox-%v.db", userID))
}

View File

@ -1,105 +1,54 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"io"
"github.com/Masterminds/semver/v3"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi" // mockgen needs this to be given an explicit import name
"github.com/ProtonMail/proton-bridge/internal/updater"
)
type Configer interface {
ClearData() error
GetDBDir() string
type Locator interface {
Clear() error
ClearUpdates() error
ProvideLogsPath() (string, error)
}
type CacheProvider interface {
GetIMAPCachePath() string
GetAPIConfig() *pmapi.ClientConfig
GetDBDir() string
GetDefaultMessageCacheDir() string
}
type PreferenceProvider interface {
type SettingsProvider interface {
Get(key string) string
GetBool(key string) bool
GetInt(key string) int
Set(key string, value string)
GetBool(key string) bool
SetBool(key string, val bool)
GetInt(key string) int
}
type PanicHandler interface {
HandlePanic()
type Updater interface {
Check() (updater.VersionInfo, error)
IsDowngrade(updater.VersionInfo) bool
InstallUpdate(updater.VersionInfo) error
}
type PMAPIProviderFactory func(string) PMAPIProvider
type PMAPIProvider interface {
SetAuths(auths chan<- *pmapi.Auth)
Auth(username, password string, info *pmapi.AuthInfo) (*pmapi.Auth, error)
AuthInfo(username string) (*pmapi.AuthInfo, error)
AuthRefresh(token string) (*pmapi.Auth, error)
Unlock(mailboxPassword string) (kr *pmcrypto.KeyRing, err error)
UnlockAddresses(passphrase []byte) error
CurrentUser() (*pmapi.User, error)
UpdateUser() (*pmapi.User, error)
Addresses() pmapi.AddressList
Logout() error
GetEvent(eventID string) (*pmapi.Event, error)
CountMessages(addressID string) ([]*pmapi.MessagesCount, error)
ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Message, int, error)
GetMessage(apiID string) (*pmapi.Message, error)
Import([]*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error)
DeleteMessages(apiIDs []string) error
LabelMessages(apiIDs []string, labelID string) error
UnlabelMessages(apiIDs []string, labelID string) error
MarkMessagesRead(apiIDs []string) error
MarkMessagesUnread(apiIDs []string) error
ListLabels() ([]*pmapi.Label, error)
CreateLabel(label *pmapi.Label) (*pmapi.Label, error)
UpdateLabel(label *pmapi.Label) (*pmapi.Label, error)
DeleteLabel(labelID string) error
EmptyFolder(labelID string, addressID string) error
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
SendSimpleMetric(category, action, label string) error
Auth2FA(twoFactorCode string, auth *pmapi.Auth) (*pmapi.Auth2FA, error)
GetMailSettings() (pmapi.MailSettings, error)
GetContactEmailByEmail(string, int, int) ([]pmapi.ContactEmail, error)
GetContactByID(string) (pmapi.Contact, error)
DecryptAndVerifyCards([]pmapi.Card) ([]pmapi.Card, error)
GetPublicKeysForEmail(string) ([]pmapi.PublicKey, bool, error)
SendMessage(string, *pmapi.SendMessageReq) (sent, parent *pmapi.Message, err error)
CreateDraft(m *pmapi.Message, parent string, action int) (created *pmapi.Message, err error)
CreateAttachment(att *pmapi.Attachment, r io.Reader, sig io.Reader) (created *pmapi.Attachment, err error)
KeyRingForAddressID(string) (kr *pmcrypto.KeyRing)
GetAttachment(id string) (att io.ReadCloser, err error)
}
type CredentialsStorer interface {
List() (userIDs []string, err error)
Add(userID, userName, apiToken, mailboxPassword string, emails []string) (*credentials.Credentials, error)
Get(userID string) (*credentials.Credentials, error)
SwitchAddressMode(userID string) error
UpdateEmails(userID string, emails []string) error
UpdateToken(userID, apiToken string) error
Logout(userID string) error
Delete(userID string) error
type Versioner interface {
RemoveOtherVersions(*semver.Version) error
}

View File

@ -1,621 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
logrus "github.com/sirupsen/logrus"
)
// ErrLoggedOutUser is sent to IMAP and SMTP if user exists, password is OK but user is logged out from bridge.
var ErrLoggedOutUser = errors.New("bridge account is logged out, use bridge to login again")
// User is a struct on top of API client and credentials store.
type User struct {
log *logrus.Entry
panicHandler PanicHandler
listener listener.Listener
apiClient PMAPIProvider
credStorer CredentialsStorer
imapUpdatesChannel chan interface{}
store *store.Store
storeCache *store.Cache
storePath string
userID string
creds *credentials.Credentials
lock sync.RWMutex
authChannel chan *pmapi.Auth
hasAPIAuth bool
unlockingKeyringLock sync.Mutex
wasKeyringUnlocked bool
}
// newUser creates a new bridge user.
func newUser(
panicHandler PanicHandler,
userID string,
eventListener listener.Listener,
credStorer CredentialsStorer,
apiClient PMAPIProvider,
storeCache *store.Cache,
storeDir string,
) (u *User, err error) {
log := log.WithField("user", userID)
log.Debug("Creating or loading user")
creds, err := credStorer.Get(userID)
if err != nil {
return nil, errors.Wrap(err, "failed to load user credentials")
}
u = &User{
log: log,
panicHandler: panicHandler,
listener: eventListener,
credStorer: credStorer,
apiClient: apiClient,
storeCache: storeCache,
storePath: getUserStorePath(storeDir, userID),
userID: userID,
creds: creds,
}
return
}
// init initialises a bridge user. This includes reloading its credentials from the credentials store
// (such as when logging out and back in, you need to reload the credentials because the new credentials will
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
// something in the store changed).
func (u *User) init(idleUpdates chan interface{}, apiClient PMAPIProvider) (err error) {
// If this is an existing user, we still need a new api client to get a new refresh token.
// If it's a new user, doesn't matter really; this is basically a noop in this case.
u.apiClient = apiClient
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
// Reload the user's credentials (if they log out and back in we need the new
// version with the apitoken and mailbox password).
creds, err := u.credStorer.Get(u.userID)
if err != nil {
return errors.Wrap(err, "failed to load user credentials")
}
u.creds = creds
// Set up the auth channel on which auths from the api client are sent.
u.authChannel = make(chan *pmapi.Auth)
u.apiClient.SetAuths(u.authChannel)
u.hasAPIAuth = false
go func() {
defer u.panicHandler.HandlePanic()
u.watchAPIClientAuths()
}()
// Try to authorise the user if they aren't already authorised.
// Note: we still allow users to set up bridge if the internet is off.
if authErr := u.authorizeIfNecessary(false); authErr != nil {
switch errors.Cause(authErr) {
case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser:
u.log.WithError(authErr).Warn("Could not authorize user")
default:
if logoutErr := u.logout(); logoutErr != nil {
u.log.WithError(logoutErr).Warn("Could not logout user")
}
return errors.Wrap(authErr, "failed to authorize user")
}
}
// Logged-out user keeps store running to access offline data.
// Therefore it is necessary to close it before re-init.
if u.store != nil {
if err := u.store.Close(); err != nil {
log.WithError(err).Error("Not able to close store")
}
u.store = nil
}
store, err := store.New(u.panicHandler, u, u.apiClient, u.listener, u.storePath, u.storeCache)
if err != nil {
return errors.Wrap(err, "failed to create store")
}
u.store = store
// Save the imap updates channel here so it can be set later when imap connects.
u.imapUpdatesChannel = idleUpdates
return err
}
func (u *User) SetIMAPIdleUpdateChannel() {
if u.store == nil {
return
}
u.store.SetIMAPUpdateChannel(u.imapUpdatesChannel)
}
// authorizeIfNecessary checks whether user is logged in and is connected to api auth channel.
// If user is not already connected to the api auth channel (for example there was no internet during start),
// it tries to connect it. See `connectToAuthChannel` for more info.
func (u *User) authorizeIfNecessary(emitEvent bool) (err error) {
// If user is connected and has an auth channel, then perfect, nothing to do here.
if u.creds.IsConnected() && u.HasAPIAuth() {
// The keyring unlock is triggered here to resolve state where apiClient
// is authenticated (we have auth token) but it was not possible to download
// and unlock the keys (internet not reachable).
return u.unlockIfNecessary()
}
if !u.creds.IsConnected() {
err = ErrLoggedOutUser
} else if err = u.authorizeAndUnlock(); err != nil {
u.log.WithError(err).Error("Could not authorize and unlock user")
switch errors.Cause(err) {
case pmapi.ErrUpgradeApplication:
u.listener.Emit(events.UpgradeApplicationEvent, "")
case pmapi.ErrAPINotReachable:
u.listener.Emit(events.InternetOffEvent, "")
default:
if errLogout := u.credStorer.Logout(u.userID); errLogout != nil {
u.log.WithField("err", errLogout).Error("Could not log user out from credentials store")
}
}
}
if emitEvent && err != nil &&
errors.Cause(err) != pmapi.ErrUpgradeApplication &&
errors.Cause(err) != pmapi.ErrAPINotReachable {
u.listener.Emit(events.LogoutEvent, u.userID)
}
return err
}
// unlockIfNecessary will not trigger keyring unlocking if it was already successfully unlocked.
func (u *User) unlockIfNecessary() error {
u.unlockingKeyringLock.Lock()
defer u.unlockingKeyringLock.Unlock()
if u.wasKeyringUnlocked {
return nil
}
if _, err := u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
u.wasKeyringUnlocked = true
return nil
}
// authorizeAndUnlock tries to authorize the user with the API using the the user's APIToken.
// If that succeeds, it tries to unlock the user's keys and addresses.
func (u *User) authorizeAndUnlock() (err error) {
if u.creds.APIToken == "" {
u.log.Warn("Could not connect to API auth channel, have no API token")
return nil
}
auth, err := u.apiClient.AuthRefresh(u.creds.APIToken)
if err != nil {
return errors.Wrap(err, "failed to refresh API auth")
}
u.authChannel <- auth
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err = u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
return nil
}
// See `connectToAPIClientAuthChannel` for more info.
func (u *User) watchAPIClientAuths() {
for auth := range u.authChannel {
if auth != nil {
newRefreshToken := auth.UID() + ":" + auth.RefreshToken
u.updateAPIToken(newRefreshToken)
u.hasAPIAuth = true
} else if err := u.logout(); err != nil {
u.log.WithError(err).Error("Cannot logout user after receiving empty auth from API")
}
}
}
// updateAPIToken is helper for updating the token in keychain. It's not supposed to be
// called directly from other parts of the code--only from `watchAPIClientAuths`.
func (u *User) updateAPIToken(newRefreshToken string) {
u.lock.Lock()
defer u.lock.Unlock()
u.log.Info("Saving refresh token")
if err := u.credStorer.UpdateToken(u.userID, newRefreshToken); err != nil {
u.log.WithError(err).Error("Cannot update refresh token in credentials store")
} else {
u.refreshFromCredentials()
}
}
// clearStore removes the database.
func (u *User) clearStore() error {
u.log.Trace("Clearing user store")
if u.store != nil {
if err := u.store.Remove(); err != nil {
return errors.Wrap(err, "failed to remove store")
}
} else {
u.log.Warn("Store is not initialized: cleaning up store files manually")
if err := store.RemoveStore(u.storeCache, u.storePath, u.userID); err != nil {
return errors.Wrap(err, "failed to remove store manually")
}
}
return nil
}
// closeStore just closes the store without deleting it.
func (u *User) closeStore() error {
u.log.Trace("Closing user store")
if u.store != nil {
if err := u.store.Close(); err != nil {
return errors.Wrap(err, "failed to close store")
}
}
return nil
}
// getUserStorePath returns the file path of the store database for the given userID.
func getUserStorePath(storeDir string, userID string) (path string) {
fileName := fmt.Sprintf("mailbox-%v.db", userID)
return filepath.Join(storeDir, fileName)
}
// GetTemporaryPMAPIClient returns an authorised PMAPI client.
// Do not use! It's only for backward compatibility of old SMTP and IMAP implementations.
// After proper refactor of SMTP and IMAP remove this method.
func (u *User) GetTemporaryPMAPIClient() PMAPIProvider {
return u.apiClient
}
// ID returns the user's userID.
func (u *User) ID() string {
return u.userID
}
// Username returns the user's username as found in the user's credentials.
func (u *User) Username() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.Name
}
// IsConnected returns whether user is logged in.
func (u *User) IsConnected() bool {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.IsConnected()
}
// IsCombinedAddressMode returns whether user is set in combined or split mode.
// Combined mode is the default mode and is what users typically need.
// Split mode is mostly for outlook as it cannot handle sending e-mails from an
// address other than the primary one.
func (u *User) IsCombinedAddressMode() bool {
if u.store != nil {
return u.store.IsCombinedMode()
}
return u.creds.IsCombinedAddressMode
}
// GetPrimaryAddress returns the user's original address (which is
// not necessarily the same as the primary address, because a primary address
// might be an alias and be in position one).
func (u *User) GetPrimaryAddress() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.EmailList()[0]
}
// GetStoreAddresses returns all addresses used by the store (so in combined mode,
// that's just the original address, but in split mode, that's all active addresses).
func (u *User) GetStoreAddresses() []string {
u.lock.RLock()
defer u.lock.RUnlock()
if u.IsCombinedAddressMode() {
return u.creds.EmailList()[:1]
}
return u.creds.EmailList()
}
// getStoreAddresses returns a user's used addresses (with the original address in first place).
func (u *User) getStoreAddresses() []string { // nolint[unused]
addrInfo, err := u.store.GetAddressInfo()
if err != nil {
u.log.WithError(err).Error("Failed getting address info from store")
return nil
}
addresses := []string{}
for _, addr := range addrInfo {
addresses = append(addresses, addr.Address)
}
if u.IsCombinedAddressMode() {
return addresses[:1]
}
return addresses
}
// GetAddresses returns list of all addresses.
func (u *User) GetAddresses() []string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.EmailList()
}
// GetAddressID returns the API ID of the given address.
func (u *User) GetAddressID(address string) (id string, err error) {
u.lock.RLock()
defer u.lock.RUnlock()
address = strings.ToLower(address)
if u.store == nil {
err = errors.New("store is not initialised")
return
}
return u.store.GetAddressID(address)
}
// GetBridgePassword returns bridge password. This is not a password of the PM
// account, but generated password for local purposes to not use a PM account
// in the clients (such as Thunderbird).
func (u *User) GetBridgePassword() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.BridgePassword
}
// CheckBridgeLogin checks whether the user is logged in and the bridge
// password is correct.
func (u *User) CheckBridgeLogin(password string) error {
if isApplicationOutdated {
u.listener.Emit(events.UpgradeApplicationEvent, "")
return pmapi.ErrUpgradeApplication
}
u.lock.RLock()
defer u.lock.RUnlock()
// True here because users should be notified by popup of auth failure.
if err := u.authorizeIfNecessary(true); err != nil {
u.log.WithError(err).Error("Failed to authorize user")
return err
}
return u.creds.CheckPassword(password)
}
// UpdateUser updates user details from API and saves to the credentials.
func (u *User) UpdateUser() error {
u.lock.Lock()
defer u.lock.Unlock()
if err := u.authorizeIfNecessary(true); err != nil {
return errors.Wrap(err, "cannot update user")
}
_, err := u.apiClient.UpdateUser()
if err != nil {
return err
}
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return err
}
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return err
}
emails := u.apiClient.Addresses().ActiveEmails()
if err := u.credStorer.UpdateEmails(u.userID, emails); err != nil {
return err
}
u.refreshFromCredentials()
return nil
}
// SwitchAddressMode changes mode from combined to split and vice versa. The mode to switch to is determined by the
// state of the user's credentials in the credentials store. See `IsCombinedAddressMode` for more details.
func (u *User) SwitchAddressMode() (err error) {
u.log.Trace("Switching user address mode")
u.lock.Lock()
defer u.lock.Unlock()
u.closeAllConnections()
if u.store == nil {
err = errors.New("store is not initialised")
return
}
newAddressModeState := !u.IsCombinedAddressMode()
if err = u.store.UseCombinedMode(newAddressModeState); err != nil {
u.log.WithError(err).Error("Could not switch store address mode")
return
}
if u.creds.IsCombinedAddressMode != newAddressModeState {
if err = u.credStorer.SwitchAddressMode(u.userID); err != nil {
u.log.WithError(err).Error("Could not switch credentials store address mode")
return
}
}
u.refreshFromCredentials()
return err
}
// logout is the same as Logout, but for internal purposes (logged out from
// the server) which emits LogoutEvent to notify other parts of the Bridge.
func (u *User) logout() error {
u.lock.Lock()
wasConnected := u.creds.IsConnected()
u.lock.Unlock()
err := u.Logout()
if wasConnected {
u.listener.Emit(events.LogoutEvent, u.userID)
u.listener.Emit(events.UserRefreshEvent, u.userID)
}
return err
}
// Logout logs out the user from pmapi, the credentials store, the mail store, and tries to remove as much
// sensitive data as possible.
func (u *User) Logout() (err error) {
u.lock.Lock()
defer u.lock.Unlock()
u.log.Debug("Logging out user")
if !u.creds.IsConnected() {
return
}
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
if err = u.apiClient.Logout(); err != nil {
u.log.WithError(err).Warn("Could not log user out from API client")
}
u.apiClient.SetAuths(nil)
// Logout needs to stop auth channel so when user logs back in
// it can register again with new client.
// Note: be careful to not close channel twice.
if u.authChannel != nil {
close(u.authChannel)
u.authChannel = nil
}
if err = u.credStorer.Logout(u.userID); err != nil {
u.log.WithError(err).Warn("Could not log user out from credentials store")
if err = u.credStorer.Delete(u.userID); err != nil {
u.log.WithError(err).Error("Could not delete user from credentials store")
}
}
u.refreshFromCredentials()
// Do not close whole store, just event loop. Some information might be needed offline (e.g. addressID)
u.closeEventLoop()
u.closeAllConnections()
runtime.GC()
return err
}
func (u *User) refreshFromCredentials() {
if credentials, err := u.credStorer.Get(u.userID); err != nil {
log.Error("Cannot update credentials: ", err)
} else {
u.creds = credentials
}
}
func (u *User) closeEventLoop() {
if u.store == nil {
return
}
u.store.CloseEventLoop()
}
// closeAllConnections calls CloseConnection for all users addresses.
func (u *User) closeAllConnections() {
for _, address := range u.creds.EmailList() {
u.CloseConnection(address)
}
if u.store != nil {
u.store.SetIMAPUpdateChannel(nil)
}
}
// CloseConnection emits closeConnection event on `address` which should close all active connection.
func (u *User) CloseConnection(address string) {
u.listener.Emit(events.CloseConnectionEvent, address)
}
func (u *User) GetStore() *store.Store {
return u.store
}
func (u *User) HasAPIAuth() bool {
return u.hasAPIAuth
}

View File

@ -1,208 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestUpdateUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().UpdateUser().Return(nil, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
assert.NoError(t, user.UpdateUser())
waitForEvents()
}
func TestUserSwitchAddressMode(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
assert.True(t, user.store.IsCombinedMode())
assert.True(t, user.creds.IsCombinedAddressMode)
assert.True(t, user.IsCombinedAddressMode())
waitForEvents()
gomock.InOrder(
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsSplit, nil),
)
assert.NoError(t, user.SwitchAddressMode())
assert.False(t, user.store.IsCombinedMode())
assert.False(t, user.creds.IsCombinedAddressMode)
assert.False(t, user.IsCombinedAddressMode())
gomock.InOrder(
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me"),
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me"),
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "alsouser@pm.me"),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
assert.NoError(t, user.SwitchAddressMode())
assert.True(t, user.store.IsCombinedMode())
assert.True(t, user.creds.IsCombinedAddressMode)
assert.True(t, user.IsCombinedAddressMode())
waitForEvents()
}
func TestLogoutUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := user.Logout()
waitForEvents()
assert.NoError(t, err)
}
func TestLogoutUserFailsLogout(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed"))
m.credentialsStore.EXPECT().Delete("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := user.Logout()
waitForEvents()
assert.NoError(t, err)
}
func TestCheckBridgeLogin(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
waitForEvents()
assert.NoError(t, err)
}
func TestCheckBridgeLoginUpgradeApplication(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "")
isApplicationOutdated = true
err := user.CheckBridgeLogin("any-pass")
waitForEvents()
isApplicationOutdated = false
assert.Equal(t, pmapi.ErrUpgradeApplication, err)
}
func TestCheckBridgeLoginLoggedOut(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
_ = user.init(nil, m.pmapiClient)
defer cleanUpUserData(user)
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
err := user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
waitForEvents()
assert.Equal(t, "bridge account is logged out, use bridge to login again", err.Error())
}
func TestCheckBridgeLoginBadPassword(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
err := user.CheckBridgeLogin("wrong!")
waitForEvents()
assert.Equal(t, "backend/credentials: incorrect password", err.Error())
}

View File

@ -1,188 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"errors"
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
a "github.com/stretchr/testify/assert"
)
func TestNewUserNoCredentialsStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(nil, errors.New("fail"))
_, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
a.Error(t, err)
}
func TestNewUserBridgeOutdated(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil).AnyTimes()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, pmapi.ErrUpgradeApplication).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "").AnyTimes()
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrUpgradeApplication)
m.pmapiClient.EXPECT().Addresses().Return(nil)
checkNewUser(m)
}
func TestNewUserNoInternetConnection(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, pmapi.ErrAPINotReachable).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.InternetOffEvent, "").AnyTimes()
m.pmapiClient.EXPECT().Addresses().Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrAPINotReachable)
m.pmapiClient.EXPECT().GetEvent("").Return(nil, pmapi.ErrAPINotReachable).AnyTimes()
checkNewUser(m)
}
func TestNewUserAuthRefreshFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, errors.New("bad token")).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUserUnlockFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, errors.New("bad password"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUserUnlockAddressesFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(errors.New("bad password"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
checkNewUser(m)
}
func checkNewUser(m mocks) {
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
defer cleanUpUserData(user)
_ = user.init(nil, m.pmapiClient)
waitForEvents()
a.Equal(m.t, testCredentials, user.creds)
}
func checkNewUserDisconnected(m mocks) {
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
defer cleanUpUserData(user)
_ = user.init(nil, m.pmapiClient)
waitForEvents()
a.Equal(m.t, testCredentialsDisconnected, user.creds)
}
func _TestUserEventRefreshUpdatesAddresses(t *testing.T) { // nolint[funlen]
a.Fail(t, "not implemented")
}

View File

@ -1,113 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"testing"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testNewUser(m mocks) *User {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
// Expectations for initial sync (when loading existing user from credentials store).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).AnyTimes()
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
assert.NoError(m.t, err)
err = user.init(nil, m.pmapiClient)
assert.NoError(m.t, err)
return user
}
func testNewUserForLogout(m mocks) *User {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
// These may or may not be hit depending on how fast the log out happens.
m.pmapiClient.EXPECT().SetAuths(nil).AnyTimes()
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil).AnyTimes()
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}).AnyTimes()
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).AnyTimes()
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
assert.NoError(m.t, err)
err = user.init(nil, m.pmapiClient)
assert.NoError(m.t, err)
return user
}
func cleanUpUserData(u *User) {
_ = u.clearStore()
}
func _TestNeverLongStorePath(t *testing.T) { // nolint[unused]
assert.Fail(t, "not implemented")
}
func TestClearStoreWithStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
require.Nil(t, user.store.Close())
user.store = nil
assert.Nil(t, user.clearStore())
}
func TestClearStoreWithoutStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
assert.NotNil(t, user.store)
assert.Nil(t, user.clearStore())
}

View File

@ -1,41 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"fmt"
"runtime"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
// UpdateCurrentUserAgent updates user agent on pmapi so each request has this
// information in headers for statistic purposes.
func UpdateCurrentUserAgent(bridgeVersion, os, clientName, clientVersion string) {
if os == "" {
os = runtime.GOOS
}
mailClient := "unknown client"
if clientName != "" {
mailClient = clientName
if clientVersion != "" {
mailClient += "/" + clientVersion
}
}
pmapi.CurrentUserAgent = fmt.Sprintf("Bridge/%s (%s; %s)", bridgeVersion, os, mailClient)
}

View File

@ -1,51 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge
import (
"runtime"
"testing"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/stretchr/testify/assert"
)
func TestUpdateCurrentUserAgentGOOS(t *testing.T) {
UpdateCurrentUserAgent("ver", "", "", "")
assert.Equal(t, "Bridge/ver ("+runtime.GOOS+"; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentOS(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "", "")
assert.Equal(t, "Bridge/ver (os; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientVer(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "", "cver")
assert.Equal(t, "Bridge/ver (os; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientName(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "mail", "")
assert.Equal(t, "Bridge/ver (os; mail)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientNameAndVersion(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "mail", "cver")
assert.Equal(t, "Bridge/ver (os; mail/cver)", pmapi.CurrentUserAgent)
}

70
internal/config/cache/cache.go vendored Normal file
View File

@ -0,0 +1,70 @@
// 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 cache provides access to contents inside a cache directory.
package cache
import (
"os"
"path/filepath"
"github.com/ProtonMail/proton-bridge/pkg/files"
)
type Cache struct {
dir, version string
}
func New(dir, version string) (*Cache, error) {
if err := os.MkdirAll(filepath.Join(dir, version), 0700); err != nil {
return nil, err
}
return &Cache{
dir: dir,
version: version,
}, nil
}
// GetDBDir returns folder for db files.
func (c *Cache) GetDBDir() string {
return c.getCurrentCacheDir()
}
// GetDefaultMessageCacheDir returns folder for cached messages files.
func (c *Cache) GetDefaultMessageCacheDir() string {
return filepath.Join(c.getCurrentCacheDir(), "messages")
}
// GetIMAPCachePath returns path to file with IMAP status.
func (c *Cache) GetIMAPCachePath() string {
return filepath.Join(c.getCurrentCacheDir(), "user_info.json")
}
// GetTransferDir returns folder for import-export rules files.
func (c *Cache) GetTransferDir() string {
return c.getCurrentCacheDir()
}
// RemoveOldVersions removes any cache dirs that are not the current version.
func (c *Cache) RemoveOldVersions() error {
return files.Remove(c.dir).Except(c.getCurrentCacheDir()).Do()
}
func (c *Cache) getCurrentCacheDir() string {
return filepath.Join(c.dir, c.version)
}

70
internal/config/cache/cache_test.go vendored Normal file
View File

@ -0,0 +1,70 @@
// 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 cache
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRemoveOldVersions(t *testing.T) {
dir, err := ioutil.TempDir("", "test-cache")
require.NoError(t, err)
cache, err := New(dir, "c4")
require.NoError(t, err)
createFilesInDir(t, dir,
"unexpected1.txt",
"c1/unexpected1.txt",
"c2/unexpected2.txt",
"c3/unexpected3.txt",
"something.txt",
)
require.DirExists(t, filepath.Join(dir, "c4"))
require.FileExists(t, filepath.Join(dir, "unexpected1.txt"))
require.FileExists(t, filepath.Join(dir, "c1", "unexpected1.txt"))
require.FileExists(t, filepath.Join(dir, "c2", "unexpected2.txt"))
require.FileExists(t, filepath.Join(dir, "c3", "unexpected3.txt"))
require.FileExists(t, filepath.Join(dir, "something.txt"))
assert.NoError(t, cache.RemoveOldVersions())
assert.DirExists(t, filepath.Join(dir, "c4"))
assert.NoFileExists(t, filepath.Join(dir, "unexpected1.txt"))
assert.NoFileExists(t, filepath.Join(dir, "c1", "unexpected1.txt"))
assert.NoFileExists(t, filepath.Join(dir, "c2", "unexpected2.txt"))
assert.NoFileExists(t, filepath.Join(dir, "c3", "unexpected3.txt"))
assert.NoFileExists(t, filepath.Join(dir, "something.txt"))
}
func createFilesInDir(t *testing.T, dir string, files ...string) {
for _, target := range files {
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, target)), 0700))
f, err := os.Create(filepath.Join(dir, target))
require.NoError(t, err)
require.NoError(t, f.Close())
}
}

View File

@ -0,0 +1,152 @@
// 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 settings
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"sync"
"github.com/sirupsen/logrus"
)
type keyValueStore struct {
cache map[string]string
path string
lock *sync.RWMutex
}
// newKeyValueStore returns loaded preferences.
func newKeyValueStore(path string) *keyValueStore {
p := &keyValueStore{
path: path,
lock: &sync.RWMutex{},
}
if err := p.load(); err != nil {
logrus.WithError(err).Warn("Cannot load preferences file, creating new one")
}
return p
}
func (p *keyValueStore) load() error {
if p.cache != nil {
return nil
}
p.lock.Lock()
defer p.lock.Unlock()
p.cache = map[string]string{}
f, err := os.Open(p.path)
if err != nil {
return err
}
defer f.Close() //nolint:errcheck,gosec
return json.NewDecoder(f).Decode(&p.cache)
}
func (p *keyValueStore) save() error {
if p.cache == nil {
return errors.New("cannot save preferences: cache is nil")
}
p.lock.Lock()
defer p.lock.Unlock()
b, err := json.MarshalIndent(p.cache, "", "\t")
if err != nil {
return err
}
return ioutil.WriteFile(p.path, b, 0600)
}
func (p *keyValueStore) setDefault(key, value string) {
if p.Get(key) == "" {
p.Set(key, value)
}
}
func (p *keyValueStore) Get(key string) string {
p.lock.RLock()
defer p.lock.RUnlock()
return p.cache[key]
}
func (p *keyValueStore) GetBool(key string) bool {
return p.Get(key) == "true"
}
func (p *keyValueStore) GetInt(key string) int {
if p.Get(key) == "" {
return 0
}
value, err := strconv.Atoi(p.Get(key))
if err != nil {
logrus.WithError(err).Error("Cannot parse int")
}
return value
}
func (p *keyValueStore) GetFloat64(key string) float64 {
if p.Get(key) == "" {
return 0
}
value, err := strconv.ParseFloat(p.Get(key), 64)
if err != nil {
logrus.WithError(err).Error("Cannot parse float64")
}
return value
}
func (p *keyValueStore) Set(key, value string) {
p.lock.Lock()
p.cache[key] = value
p.lock.Unlock()
if err := p.save(); err != nil {
logrus.WithError(err).Warn("Cannot save preferences")
}
}
func (p *keyValueStore) SetBool(key string, value bool) {
if value {
p.Set(key, "true")
} else {
p.Set(key, "false")
}
}
func (p *keyValueStore) SetInt(key string, value int) {
p.Set(key, strconv.Itoa(value))
}
func (p *keyValueStore) SetFloat64(key string, value float64) {
p.Set(key, fmt.Sprintf("%v", value))
}

View File

@ -0,0 +1,142 @@
// 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 settings
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestLoadNoKeyValueStore(t *testing.T) {
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
r.Equal("", pref.Get("key"))
}
func TestLoadBadKeyValueStore(t *testing.T) {
r := require.New(t)
path, clean := newTmpFile(r)
defer clean()
r.NoError(ioutil.WriteFile(path, []byte("{\"key\":\"MISSING_QUOTES"), 0700))
pref := newKeyValueStore(path)
r.Equal("", pref.Get("key"))
}
func TestKeyValueStor(t *testing.T) {
r := require.New(t)
pref, clean := newTestKeyValueStore(r)
defer clean()
r.Equal("value", pref.Get("str"))
r.Equal("42", pref.Get("int"))
r.Equal("true", pref.Get("bool"))
r.Equal("t", pref.Get("falseBool"))
}
func TestKeyValueStoreGetInt(t *testing.T) {
r := require.New(t)
pref, clean := newTestKeyValueStore(r)
defer clean()
r.Equal(0, pref.GetInt("str"))
r.Equal(42, pref.GetInt("int"))
r.Equal(0, pref.GetInt("bool"))
r.Equal(0, pref.GetInt("falseBool"))
}
func TestKeyValueStoreGetBool(t *testing.T) {
r := require.New(t)
pref, clean := newTestKeyValueStore(r)
defer clean()
r.Equal(false, pref.GetBool("str"))
r.Equal(false, pref.GetBool("int"))
r.Equal(true, pref.GetBool("bool"))
r.Equal(false, pref.GetBool("falseBool"))
}
func TestKeyValueStoreSetDefault(t *testing.T) {
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.setDefault("key", "value")
pref.setDefault("key", "othervalue")
r.Equal("value", pref.Get("key"))
}
func TestKeyValueStoreSet(t *testing.T) {
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.Set("str", "value")
checkSavedKeyValueStore(r, pref.path, "{\n\t\"str\": \"value\"\n}")
}
func TestKeyValueStoreSetInt(t *testing.T) {
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.SetInt("int", 42)
checkSavedKeyValueStore(r, pref.path, "{\n\t\"int\": \"42\"\n}")
}
func TestKeyValueStoreSetBool(t *testing.T) {
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.SetBool("trueBool", true)
pref.SetBool("falseBool", false)
checkSavedKeyValueStore(r, pref.path, "{\n\t\"falseBool\": \"false\",\n\t\"trueBool\": \"true\"\n}")
}
func newTmpFile(r *require.Assertions) (path string, clean func()) {
tmpfile, err := ioutil.TempFile("", "pref.*.json")
r.NoError(err)
defer r.NoError(tmpfile.Close())
return tmpfile.Name(), func() {
r.NoError(os.Remove(tmpfile.Name()))
}
}
func newTestEmptyKeyValueStore(r *require.Assertions) (*keyValueStore, func()) {
path, clean := newTmpFile(r)
return newKeyValueStore(path), clean
}
func newTestKeyValueStore(r *require.Assertions) (*keyValueStore, func()) {
path, clean := newTmpFile(r)
r.NoError(ioutil.WriteFile(path, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
return newKeyValueStore(path), clean
}
func checkSavedKeyValueStore(r *require.Assertions, path, expected string) {
data, err := ioutil.ReadFile(path)
r.NoError(err)
r.Equal(expected, string(data))
}

View File

@ -0,0 +1,113 @@
// 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 settings provides access to persistent user settings.
package settings
import (
"fmt"
"math/rand"
"path/filepath"
"time"
)
// Keys of preferences in JSON file.
const (
FirstStartKey = "first_time_start"
FirstStartGUIKey = "first_time_start_gui"
LastHeartbeatKey = "last_heartbeat"
APIPortKey = "user_port_api"
IMAPPortKey = "user_port_imap"
SMTPPortKey = "user_port_smtp"
SMTPSSLKey = "user_ssl_smtp"
AllowProxyKey = "allow_proxy"
AutostartKey = "autostart"
AutoUpdateKey = "autoupdate"
CookiesKey = "cookies"
ReportOutgoingNoEncKey = "report_outgoing_email_without_encryption"
LastVersionKey = "last_used_version"
UpdateChannelKey = "update_channel"
RolloutKey = "rollout"
PreferredKeychainKey = "preferred_keychain"
CacheEnabledKey = "cache_enabled"
CacheCompressionKey = "cache_compression"
CacheLocationKey = "cache_location"
CacheMinFreeAbsKey = "cache_min_free_abs"
CacheMinFreeRatKey = "cache_min_free_rat"
CacheConcurrencyRead = "cache_concurrent_read"
CacheConcurrencyWrite = "cache_concurrent_write"
IMAPWorkers = "imap_workers"
FetchWorkers = "fetch_workers"
AttachmentWorkers = "attachment_workers"
ColorScheme = "color_scheme"
RebrandingMigrationKey = "rebranding_migrated"
)
type Settings struct {
*keyValueStore
settingsPath string
}
func New(settingsPath string) *Settings {
s := &Settings{
keyValueStore: newKeyValueStore(filepath.Join(settingsPath, "prefs.json")),
settingsPath: settingsPath,
}
s.setDefaultValues()
return s
}
const (
DefaultIMAPPort = "1143"
DefaultSMTPPort = "1025"
DefaultAPIPort = "1042"
)
func (s *Settings) setDefaultValues() {
s.setDefault(FirstStartKey, "true")
s.setDefault(FirstStartGUIKey, "true")
s.setDefault(LastHeartbeatKey, fmt.Sprintf("%v", time.Now().YearDay()))
s.setDefault(AllowProxyKey, "true")
s.setDefault(AutostartKey, "true")
s.setDefault(AutoUpdateKey, "true")
s.setDefault(ReportOutgoingNoEncKey, "false")
s.setDefault(LastVersionKey, "")
s.setDefault(UpdateChannelKey, "")
s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64())) //nolint:gosec // G404 It is OK to use weak random number generator here
s.setDefault(PreferredKeychainKey, "")
s.setDefault(CacheEnabledKey, "true")
s.setDefault(CacheCompressionKey, "true")
s.setDefault(CacheLocationKey, "")
s.setDefault(CacheMinFreeAbsKey, "250000000")
s.setDefault(CacheMinFreeRatKey, "")
s.setDefault(CacheConcurrencyRead, "16")
s.setDefault(CacheConcurrencyWrite, "16")
s.setDefault(IMAPWorkers, "16")
s.setDefault(FetchWorkers, "16")
s.setDefault(AttachmentWorkers, "16")
s.setDefault(ColorScheme, "")
s.setDefault(APIPortKey, DefaultAPIPort)
s.setDefault(IMAPPortKey, DefaultIMAPPort)
s.setDefault(SMTPPortKey, DefaultSMTPPort)
// By default, stick to STARTTLS. If the user uses catalina+applemail they'll have to change to SSL.
s.setDefault(SMTPSSLKey, "false")
}

View File

@ -0,0 +1,53 @@
// 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 tls
import "os/exec"
func addTrustedCert(certPath string) error {
return exec.Command( //nolint:gosec
"/usr/bin/security",
"execute-with-privileges",
"/usr/bin/security",
"add-trusted-cert",
"-d",
"-r", "trustRoot",
"-p", "ssl",
"-k", "/Library/Keychains/System.keychain",
certPath,
).Run()
}
func removeTrustedCert(certPath string) error {
return exec.Command( //nolint:gosec
"/usr/bin/security",
"execute-with-privileges",
"/usr/bin/security",
"remove-trusted-cert",
"-d",
certPath,
).Run()
}
func (t *TLS) InstallCerts() error {
return addTrustedCert(t.getTLSCertPath())
}
func (t *TLS) UninstallCerts() error {
return removeTrustedCert(t.getTLSCertPath())
}

View File

@ -0,0 +1,26 @@
// 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 tls
func (t *TLS) InstallCerts() error {
return nil // Linux doesn't have a root cert store.
}
func (t *TLS) UninstallCerts() error {
return nil // Linux doesn't have a root cert store.
}

View File

@ -0,0 +1,26 @@
// 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 tls
func (t *TLS) InstallCerts() error {
return nil // NOTE(GODT-986): Install certs to root cert store?
}
func (t *TLS) UninstallCerts() error {
return nil // NOTE(GODT-986): Uninstall certs from root cert store?
}

155
internal/config/tls/tls.go Normal file
View File

@ -0,0 +1,155 @@
// 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 tls
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"path/filepath"
"time"
"github.com/pkg/errors"
)
type TLS struct {
settingsPath string
}
func New(settingsPath string) *TLS {
return &TLS{
settingsPath: settingsPath,
}
}
// NewTLSTemplate creates a new TLS template certificate with a random serial number.
func NewTLSTemplate() (*x509.Certificate, error) {
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, errors.Wrap(err, "failed to generate serial number")
}
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Country: []string{"CH"},
Organization: []string{"Proton AG"},
OrganizationalUnit: []string{"Proton Mail"},
CommonName: "127.0.0.1",
},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
NotBefore: time.Now(),
NotAfter: time.Now().Add(20 * 365 * 24 * time.Hour),
}, nil
}
var ErrTLSCertExpiresSoon = fmt.Errorf("TLS certificate will expire soon")
// getTLSCertPath returns path to certificate; used for TLS servers (IMAP, SMTP).
func (t *TLS) getTLSCertPath() string {
return filepath.Join(t.settingsPath, "cert.pem")
}
// getTLSKeyPath returns path to private key; used for TLS servers (IMAP, SMTP).
func (t *TLS) getTLSKeyPath() string {
return filepath.Join(t.settingsPath, "key.pem")
}
// HasCerts returns whether TLS certs have been generated.
func (t *TLS) HasCerts() bool {
if _, err := os.Stat(t.getTLSCertPath()); err != nil {
return false
}
if _, err := os.Stat(t.getTLSKeyPath()); err != nil {
return false
}
return true
}
// GenerateCerts generates certs from the given template.
func (t *TLS) GenerateCerts(template *x509.Certificate) error {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return errors.Wrap(err, "failed to generate private key")
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
return errors.Wrap(err, "failed to create certificate")
}
certOut, err := os.Create(t.getTLSCertPath())
if err != nil {
return err
}
defer certOut.Close() //nolint:errcheck,gosec
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return err
}
keyOut, err := os.OpenFile(t.getTLSKeyPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer keyOut.Close() //nolint:errcheck,gosec
return pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
}
// GetConfig tries to load TLS config or generate new one which is then returned.
func (t *TLS) GetConfig() (*tls.Config, error) {
c, err := tls.LoadX509KeyPair(t.getTLSCertPath(), t.getTLSKeyPath())
if err != nil {
return nil, errors.Wrap(err, "failed to load keypair")
}
c.Leaf, err = x509.ParseCertificate(c.Certificate[0])
if err != nil {
return nil, errors.Wrap(err, "failed to parse certificate")
}
if time.Now().Add(31 * 24 * time.Hour).After(c.Leaf.NotAfter) {
return nil, ErrTLSCertExpiresSoon
}
caCertPool := x509.NewCertPool()
caCertPool.AddCert(c.Leaf)
//nolint:gosec // We need to support older TLS versions for AppleMail and Outlook
return &tls.Config{
Certificates: []tls.Certificate{c},
ServerName: "127.0.0.1",
ClientAuth: tls.VerifyClientCertIfGiven,
RootCAs: caCertPool,
ClientCAs: caCertPool,
}, nil
}

View File

@ -0,0 +1,77 @@
// 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 tls
import (
"io/ioutil"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestGetOldConfig(t *testing.T) {
dir, err := ioutil.TempDir("", "test-tls")
require.NoError(t, err)
// Create new tls object.
tls := New(dir)
// Create new TLS template.
tlsTemplate, err := NewTLSTemplate()
require.NoError(t, err)
// Make the template be an old key.
tlsTemplate.NotBefore = time.Now().Add(-365 * 24 * time.Hour)
tlsTemplate.NotAfter = time.Now()
// Generate the certs from the template.
require.NoError(t, tls.GenerateCerts(tlsTemplate))
// Generate the config from the certs -- it's going to expire soon so we don't want to use it.
_, err = tls.GetConfig()
require.Equal(t, err, ErrTLSCertExpiresSoon)
}
func TestGetValidConfig(t *testing.T) {
dir, err := ioutil.TempDir("", "test-tls")
require.NoError(t, err)
// Create new tls object.
tls := New(dir)
// Create new TLS template.
tlsTemplate, err := NewTLSTemplate()
require.NoError(t, err)
// Make the template be a new key.
tlsTemplate.NotBefore = time.Now()
tlsTemplate.NotAfter = time.Now().Add(2 * 365 * 24 * time.Hour)
// Generate the certs from the template.
require.NoError(t, tls.GenerateCerts(tlsTemplate))
// Generate the config from the certs -- it's not going to expire soon so we want to use it.
config, err := tls.GetConfig()
require.NoError(t, err)
require.Equal(t, len(config.Certificates), 1)
// Check the cert is valid.
now, notValidAfter := time.Now(), config.Certificates[0].Leaf.NotAfter
require.False(t, now.After(notValidAfter), "new certificate expected to be valid at %v but have valid until %v", now, notValidAfter)
}

View File

@ -0,0 +1,61 @@
// 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 useragent
import (
"os/exec"
"runtime"
"strings"
"github.com/Masterminds/semver/v3"
)
// IsCatalinaOrNewer checks whether the host is MacOS Catalina 10.15.x or higher.
func IsCatalinaOrNewer() bool {
return isThisDarwinNewerOrEqual(getMinCatalina())
}
// IsBigSurOrNewer checks whether the host is MacOS BigSur 10.16.x or higher.
func IsBigSurOrNewer() bool {
return isThisDarwinNewerOrEqual(getMinBigSur())
}
func getMinCatalina() *semver.Version { return semver.MustParse("10.15.0") }
func getMinBigSur() *semver.Version { return semver.MustParse("10.16.0") }
func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool {
if runtime.GOOS != "darwin" {
return false
}
rawVersion, err := exec.Command("sw_vers", "-productVersion").Output()
if err != nil {
return false
}
return isVersionEqualOrNewer(minVersion, strings.TrimSpace(string(rawVersion)))
}
// isVersionEqualOrNewer is separated to be able to run test on other than darwin.
func isVersionEqualOrNewer(minVersion *semver.Version, rawVersion string) bool {
semVersion, err := semver.NewVersion(rawVersion)
if err != nil {
return false
}
return semVersion.GreaterThan(minVersion) || semVersion.Equal(minVersion)
}

View File

@ -0,0 +1,64 @@
// 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 useragent
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsVersionCatalinaOrNewer(t *testing.T) {
testData := map[struct{ version string }]bool{
{""}: false,
{"9.0.0"}: false,
{"9.15.0"}: false,
{"10.13.0"}: false,
{"10.14.0"}: false,
{"10.14.99"}: false,
{"10.15.0"}: true,
{"10.16.0"}: true,
{"11.0.0"}: true,
{"11.1"}: true,
}
for args, exp := range testData {
got := isVersionEqualOrNewer(getMinCatalina(), args.version)
assert.Equal(t, exp, got, "version %v", args.version)
}
}
func TestIsVersionBigSurOrNewer(t *testing.T) {
testData := map[struct{ version string }]bool{
{""}: false,
{"9.0.0"}: false,
{"9.15.0"}: false,
{"10.13.0"}: false,
{"10.14.0"}: false,
{"10.14.99"}: false,
{"10.15.0"}: false,
{"10.16.0"}: true,
{"11.0.0"}: true,
{"11.1"}: true,
}
for args, exp := range testData {
got := isVersionEqualOrNewer(getMinBigSur(), args.version)
assert.Equal(t, exp, got, "version %v", args.version)
}
}

View File

@ -0,0 +1,59 @@
// 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 useragent
import (
"fmt"
"regexp"
"runtime"
)
type UserAgent struct {
client, platform string
}
func New() *UserAgent {
return &UserAgent{
client: "",
platform: runtime.GOOS,
}
}
func (ua *UserAgent) SetClient(name, version string) {
ua.client = fmt.Sprintf("%v/%v", name, regexp.MustCompile(`(.*) \((.*)\)`).ReplaceAllString(version, "$1-$2"))
}
func (ua *UserAgent) HasClient() bool {
return ua.client != ""
}
func (ua *UserAgent) SetPlatform(platform string) {
ua.platform = platform
}
func (ua *UserAgent) String() string {
var client string
if ua.client != "" {
client = ua.client
} else {
client = "NoClient/0.0.1"
}
return fmt.Sprintf("%v (%v)", client, ua.platform)
}

View File

@ -0,0 +1,86 @@
// 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 useragent
import (
"fmt"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUserAgent(t *testing.T) {
tests := []struct {
name, version, platform string
want string
}{
// No name/version, no platform.
{
want: fmt.Sprintf("NoClient/0.0.1 (%v)", runtime.GOOS),
},
// No name/version, with platform.
{
platform: "macOS 10.15",
want: "NoClient/0.0.1 (macOS 10.15)",
},
// With name/version, with platform.
{
name: "Mac OS X Mail",
version: "1.0.0",
platform: "macOS 10.15",
want: "Mac OS X Mail/1.0.0 (macOS 10.15)",
},
// With name/version, with platform.
{
name: "Mac OS X Mail",
version: "13.4 (3608.120.23.2.4)",
platform: "macOS 10.15",
want: "Mac OS X Mail/13.4-3608.120.23.2.4 (macOS 10.15)",
},
// With name/version, with platform.
{
name: "Thunderbird",
version: "78.6.1",
platform: "Windows 10 (10.0)",
want: "Thunderbird/78.6.1 (Windows 10 (10.0))",
},
}
for _, test := range tests {
test := test
t.Run(test.want, func(t *testing.T) {
ua := New()
if test.name != "" && test.version != "" {
ua.SetClient(test.name, test.version)
}
if test.platform != "" {
ua.SetPlatform(test.platform)
}
assert.Equal(t, test.want, ua.String())
})
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 constants contains variables that are set via ldflags during build.
package constants
import "fmt"
const VendorName = "protonmail"
//nolint:gochecknoglobals
var (
// Version of the build.
Version = ""
// Revision is current hash of the build.
Revision = ""
// BuildTime stamp of the build.
BuildTime = ""
// DSNSentry client keys to be able to report crashes to Sentry.
DSNSentry = ""
// BuildVersion is derived from LongVersion and BuildTime.
BuildVersion = fmt.Sprintf("%v (%v) %v", Version, Revision, BuildTime)
)

View File

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

View File

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

143
internal/cookies/jar.go Normal file
View File

@ -0,0 +1,143 @@
// 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 cookies implements a persistent cookie jar which satisfies the http.CookieJar interface.
package cookies
import (
"encoding/json"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
)
type cookiesByHost map[string][]*http.Cookie
// Jar implements http.CookieJar by wrapping the standard library's cookiejar.Jar.
// The jar uses a pantry to load cookies at startup and save cookies when set.
type Jar struct {
jar *cookiejar.Jar
settings *settings.Settings
cookies cookiesByHost
locker sync.Locker
}
func NewCookieJar(s *settings.Settings) (*Jar, error) {
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
cookiesByHost, err := loadCookies(s)
if err != nil {
return nil, err
}
for host, cookies := range cookiesByHost {
url, err := url.Parse(host)
if err != nil {
continue
}
jar.SetCookies(url, cookies)
}
return &Jar{
jar: jar,
settings: s,
cookies: cookiesByHost,
locker: &sync.Mutex{},
}, nil
}
func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
j.locker.Lock()
defer j.locker.Unlock()
j.jar.SetCookies(u, cookies)
for _, cookie := range cookies {
if cookie.MaxAge > 0 {
cookie.Expires = time.Now().Add(time.Duration(cookie.MaxAge) * time.Second)
}
}
j.cookies[fmt.Sprintf("%v://%v", u.Scheme, u.Host)] = cookies
}
func (j *Jar) Cookies(u *url.URL) []*http.Cookie {
j.locker.Lock()
defer j.locker.Unlock()
return j.jar.Cookies(u)
}
// PersistCookies persists the cookies to disk.
func (j *Jar) PersistCookies() error {
j.locker.Lock()
defer j.locker.Unlock()
rawCookies, err := json.Marshal(j.cookies)
if err != nil {
return err
}
j.settings.Set(settings.CookiesKey, string(rawCookies))
return nil
}
// loadCookies loads all non-expired cookies from disk.
func loadCookies(s *settings.Settings) (cookiesByHost, error) {
rawCookies := s.Get(settings.CookiesKey)
if rawCookies == "" {
return make(cookiesByHost), nil
}
var cookiesByHost cookiesByHost
if err := json.Unmarshal([]byte(rawCookies), &cookiesByHost); err != nil {
return nil, err
}
for host, cookies := range cookiesByHost {
if validCookies := discardExpiredCookies(cookies); len(validCookies) > 0 {
cookiesByHost[host] = validCookies
}
}
return cookiesByHost, nil
}
// discardExpiredCookies returns all the given cookies which aren't expired.
func discardExpiredCookies(cookies []*http.Cookie) []*http.Cookie {
var validCookies []*http.Cookie
for _, cookie := range cookies {
if cookie.Expires.After(time.Now()) {
validCookies = append(validCookies, cookie)
}
}
return validCookies
}

View File

@ -0,0 +1,179 @@
// 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 cookies
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestJarGetSet(t *testing.T) {
ts := getTestServer(t, []testCookie{
{"TestName1", "TestValue1", 3600},
{"TestName2", "TestValue2", 3600},
{"TestName3", "TestValue3", 3600},
})
defer ts.Close()
client, _ := getClientWithJar(t, newFakeSettings())
// Hit a server that sets some cookies.
setRes, err := client.Get(ts.URL + "/set")
if err != nil {
t.FailNow()
}
require.NoError(t, setRes.Body.Close())
// Hit a server that checks the cookies are there.
getRes, err := client.Get(ts.URL + "/get")
if err != nil {
t.FailNow()
}
require.NoError(t, getRes.Body.Close())
}
func TestJarLoad(t *testing.T) {
ts := getTestServer(t, []testCookie{
{"TestName1", "TestValue1", 3600},
{"TestName2", "TestValue2", 3600},
{"TestName3", "TestValue3", 3600},
})
defer ts.Close()
// This will be our "persistent storage" from which the cookie jar should load cookies.
s := newFakeSettings()
// This client saves cookies to persistent storage.
oldClient, jar := getClientWithJar(t, s)
// Hit a server that sets some cookies.
setRes, err := oldClient.Get(ts.URL + "/set")
if err != nil {
t.FailNow()
}
require.NoError(t, setRes.Body.Close())
// Save the cookies.
require.NoError(t, jar.PersistCookies())
// This client loads cookies from persistent storage.
newClient, _ := getClientWithJar(t, s)
// Hit a server that checks the cookies are there.
getRes, err := newClient.Get(ts.URL + "/get")
if err != nil {
t.FailNow()
}
require.NoError(t, getRes.Body.Close())
}
func TestJarExpiry(t *testing.T) {
ts := getTestServer(t, []testCookie{
{"TestName1", "TestValue1", 3600},
{"TestName2", "TestValue2", 1},
{"TestName3", "TestValue3", 3600},
})
defer ts.Close()
// This will be our "persistent storage" from which the cookie jar should load cookies.
s := newFakeSettings()
// This client saves cookies to persistent storage.
oldClient, jar1 := getClientWithJar(t, s)
// Hit a server that sets some cookies.
setRes, err := oldClient.Get(ts.URL + "/set")
if err != nil {
t.FailNow()
}
require.NoError(t, setRes.Body.Close())
// Save the cookies.
require.NoError(t, jar1.PersistCookies())
// Wait until the second cookie expires.
time.Sleep(2 * time.Second)
// Load a client, which will clear out expired cookies.
_, jar2 := getClientWithJar(t, s)
// Save the cookies (expired ones were cleared out).
require.NoError(t, jar2.PersistCookies())
assert.Contains(t, s.Get(settings.CookiesKey), "TestName1")
assert.NotContains(t, s.Get(settings.CookiesKey), "TestName2")
assert.Contains(t, s.Get(settings.CookiesKey), "TestName3")
}
type testCookie struct {
name, value string
maxAge int
}
func getClientWithJar(t *testing.T, s *settings.Settings) (*http.Client, *Jar) {
jar, err := NewCookieJar(s)
require.NoError(t, err)
return &http.Client{Jar: jar}, jar
}
func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/set", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, cookie := range wantCookies {
http.SetCookie(w, &http.Cookie{
Name: cookie.name,
Value: cookie.value,
MaxAge: cookie.maxAge,
})
}
w.WriteHeader(http.StatusOK)
}))
mux.HandleFunc("/get", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Len(t, r.Cookies(), len(wantCookies))
for k, v := range r.Cookies() {
assert.Equal(t, wantCookies[k].name, v.Name)
assert.Equal(t, wantCookies[k].value, v.Value)
}
w.WriteHeader(http.StatusOK)
}))
return httptest.NewServer(mux)
}
// newFakeSettings creates a temporary folder for files.
func newFakeSettings() *settings.Settings {
dir, err := ioutil.TempDir("", "test-settings")
if err != nil {
panic(err)
}
return settings.New(dir)
}

42
internal/crash/actions.go Normal file
View File

@ -0,0 +1,42 @@
// 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 crash
import (
"fmt"
"github.com/0xAX/notificator"
)
// ShowErrorNotification shows a system notification that the app with the given appName has crashed.
// NOTE: Icons shouldn't be hardcoded.
func ShowErrorNotification(appName string) RecoveryAction {
return func(r interface{}) error {
notify := notificator.New(notificator.Options{
DefaultIcon: "../frontend/ui/icon/icon.png",
AppName: appName,
})
return notify.Push(
"Fatal Error",
fmt.Sprintf("%v has encountered a fatal error.", appName),
"/frontend/icon/icon.png",
notificator.UR_CRITICAL,
)
}
}

54
internal/crash/handler.go Normal file
View File

@ -0,0 +1,54 @@
// 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 crash implements a crash handler with configurable recovery actions.
package crash
import (
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/sirupsen/logrus"
)
type RecoveryAction func(interface{}) error
type Handler struct {
actions []RecoveryAction
}
func NewHandler(actions ...RecoveryAction) *Handler {
return &Handler{actions: actions}
}
func (h *Handler) AddRecoveryAction(action RecoveryAction) *Handler {
h.actions = append(h.actions, action)
return h
}
func (h *Handler) HandlePanic() {
sentry.SkipDuringUnwind()
r := recover()
if r == nil {
return
}
for _, action := range h.actions {
if err := action(r); err != nil {
logrus.WithError(err).Error("Failed to execute recovery action")
}
}
}

View File

@ -0,0 +1,58 @@
// 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 crash
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHandler(t *testing.T) {
var s string
h := NewHandler(
func(r interface{}) error {
s += fmt.Sprintf("1: %v\n", r)
return nil
},
func(r interface{}) error {
s += fmt.Sprintf("2: %v\n", r)
return nil
},
)
h.
AddRecoveryAction(func(r interface{}) error {
s += fmt.Sprintf("3: %v\n", r)
return nil
}).
AddRecoveryAction(func(r interface{}) error {
s += fmt.Sprintf("4: %v\n", r)
return nil
})
defer func() {
assert.Equal(t, "1: thing\n2: thing\n3: thing\n4: thing\n", s)
}()
defer h.HandlePanic()
panic("thing")
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package events provides names of events used by the event listener in bridge.
package events
@ -27,19 +27,22 @@ import (
// Constants of events used by the event listener in bridge.
const (
ErrorEvent = "error"
CredentialsErrorEvent = "credentialsError"
CloseConnectionEvent = "closeConnection"
LogoutEvent = "logout"
AddressChangedEvent = "addressChanged"
AddressChangedLogoutEvent = "addressChangedLogout"
UserRefreshEvent = "userRefresh"
RestartBridgeEvent = "restartBridge"
InternetOffEvent = "internetOff"
InternetOnEvent = "internetOn"
InternetConnChangedEvent = "internetChanged"
InternetOff = "internetOff"
InternetOn = "internetOn"
SecondInstanceEvent = "secondInstance"
OutgoingNoEncEvent = "outgoingNoEncryption"
NoActiveKeyForRecipientEvent = "noActiveKeyForRecipient"
UpgradeApplicationEvent = "upgradeApplication"
TLSCertIssue = "tlsCertPinningIssue"
UserChangeDone = "QMLUserChangedDone"
// LogoutEventTimeout is the minimum time to permit between logout events being sent.
LogoutEventTimeout = 3 * time.Minute
@ -48,6 +51,11 @@ const (
// SetupEvents specific to event type and data.
func SetupEvents(listener listener.Listener) {
listener.SetLimit(LogoutEvent, LogoutEventTimeout)
listener.SetBuffer(TLSCertIssue)
listener.SetBuffer(ErrorEvent)
listener.SetBuffer(CredentialsErrorEvent)
listener.SetBuffer(InternetConnChangedEvent)
listener.SetBuffer(UpgradeApplicationEvent)
listener.SetBuffer(TLSCertIssue)
listener.SetBuffer(UserRefreshEvent)
listener.Book(UserChangeDone)
}

11
internal/frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Auto generated
moc.cpp
moc.go
moc.h
moc_cgo_*.go
moc_moc.h
rcc.cpp
rcc.qrc
rcc_cgo_*.go
*.qmlc

View File

@ -0,0 +1,14 @@
FILES=$(shell find . -iname 'rcc.qrc')
FILES+=$(shell find . -iname 'rcc.cpp')
FILES+=$(shell find . -iname 'rcc_cgo*.go')
FILES+=$(shell find . -iname 'moc.go')
FILES+=$(shell find . -iname 'moc.cpp')
FILES+=$(shell find . -iname 'moc.h')
FILES+=$(shell find . -iname 'moc_cgo*.go')
FILES+=$(shell find ./qml -iname '*.qmlc')
clean:
rm -f ${FILES}

View File

@ -1,108 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build darwin
package autoconfig
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
mobileconfig "github.com/ProtonMail/go-apple-mobileconfig"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
)
func init() { //nolint[gochecknoinit]
available = append(available, &appleMail{})
}
type appleMail struct{}
func (c *appleMail) Name() string {
return "Apple Mail"
}
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.BridgeUser, addressIndex int) error { //nolint[funlen]
var addresses string
var displayName string
if user.IsCombinedAddressMode() {
displayName = user.GetPrimaryAddress()
addresses = strings.Join(user.GetAddresses(), ",")
} else {
for idx, address := range user.GetAddresses() {
if idx == addressIndex {
displayName = address
break
}
}
addresses = displayName
}
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
mc := &mobileconfig.Config{
EmailAddress: addresses,
DisplayName: displayName,
Identifier: "protonmail " + displayName + timestamp,
Imap: &mobileconfig.Imap{
Hostname: bridge.Host,
Port: imapPort,
Tls: imapSSL,
Username: displayName,
Password: user.GetBridgePassword(),
},
Smtp: &mobileconfig.Smtp{
Hostname: bridge.Host,
Port: smtpPort,
Tls: smtpSSL,
Username: displayName,
},
}
dir, err := ioutil.TempDir("", "protonmail-autoconfig")
if err != nil {
return err
}
// Make sure the temporary file is deleted.
go (func() {
<-time.After(10 * time.Minute)
_ = os.RemoveAll(dir)
})()
// Make sure the file is only readable for the current user.
f, err := os.OpenFile(filepath.Join(dir, "protonmail.mobileconfig"), os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return err
}
if err := mc.WriteTo(f); err != nil {
_ = f.Close()
return err
}
_ = f.Close()
return exec.Command("open", f.Name()).Run() // nolint[gosec]
}

View File

@ -1,33 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package autoconfig provides automatic config of IMAP and SMTP.
// For now only for Apple Mail.
package autoconfig
import "github.com/ProtonMail/proton-bridge/internal/frontend/types"
type AutoConfig interface {
Name() string
Configure(imapPort int, smtpPort int, imapSSl, smtpSSL bool, user types.BridgeUser, addressIndex int) error
}
var available []AutoConfig //nolint[gochecknoglobals]
func Available() []AutoConfig {
return available
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cli
@ -55,7 +55,7 @@ func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ish
}
}
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.BridgeUser {
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.User {
user := f.getUserByIndexOrName("")
if user != nil {
return user
@ -76,7 +76,7 @@ func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.BridgeUser {
return user
}
func (f *frontendCLI) getUserByIndexOrName(arg string) types.BridgeUser {
func (f *frontendCLI) getUserByIndexOrName(arg string) types.User {
users := f.bridge.GetUsers()
numberOfAccounts := len(users)
if numberOfAccounts == 0 {

View File

@ -1,34 +1,35 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cli
import (
"context"
"strings"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) listAccounts(c *ishell.Context) {
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
f.Printf(bold(strings.Replace(spacing, "d", "s", -1)), "#", "account", "status", "address mode")
f.Printf(bold(strings.ReplaceAll(spacing, "d", "s")), "#", "account", "status", "address mode")
for idx, user := range f.bridge.GetUsers() {
connected := "disconnected"
if user.IsConnected() {
@ -63,23 +64,23 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
}
}
func (f *frontendCLI) showAccountAddressInfo(user types.BridgeUser, address string) {
func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
smtpSecurity := "STARTTLS"
if f.preferences.GetBool(preferences.SMTPSSLKey) {
if f.settings.GetBool(settings.SMTPSSLKey) {
smtpSecurity = "SSL"
}
f.Println(bold("Configuration for " + address))
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
bridge.Host,
f.preferences.GetInt(preferences.IMAPPortKey),
f.settings.GetInt(settings.IMAPPortKey),
address,
user.GetBridgePassword(),
"STARTTLS",
)
f.Println("")
f.Printf("SMTP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
bridge.Host,
f.preferences.GetInt(preferences.SMTPPortKey),
f.settings.GetInt(settings.SMTPPortKey),
address,
user.GetBridgePassword(),
smtpSecurity,
@ -87,7 +88,7 @@ func (f *frontendCLI) showAccountAddressInfo(user types.BridgeUser, address stri
f.Println("")
}
func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
f.ShowPrompt(false)
defer f.ShowPrompt(true)
@ -114,7 +115,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
}
f.Println("Authenticating ... ")
client, auth, err := f.bridge.Login(loginName, password)
client, auth, err := f.bridge.Login(loginName, []byte(password))
if err != nil {
f.processAPIError(err)
return
@ -126,7 +127,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
return
}
_, err = client.Auth2FA(twoFactor, auth)
err = client.Auth2FA(context.Background(), twoFactor)
if err != nil {
f.processAPIError(err)
return
@ -142,7 +143,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
}
f.Println("Adding account ...")
user, err := f.bridge.FinishLogin(client, auth, mailboxPassword)
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
if err != nil {
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
f.Println("Adding account was unsuccessful:", err)
@ -191,14 +192,34 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
if !f.yesNoQuestion("Do you really want remove all accounts") {
return
}
for _, user := range f.bridge.GetUsers() {
if err := f.bridge.DeleteUser(user.ID(), false); err != nil {
f.printAndLogError("Cannot delete account ", user.Username(), ": ", err)
}
}
c.Println("Keychain cleared")
}
func (f *frontendCLI) deleteEverything(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
if !f.yesNoQuestion("Do you really want remove everything") {
return
}
f.bridge.FactoryReset()
c.Println("Everything cleared")
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
f.restarter.SetToRestart()
f.Stop()
}
func (f *frontendCLI) changeMode(c *ishell.Context) {
user := f.askUserByIndexOrName(c)
if user == nil {

View File

@ -1,68 +1,72 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package cli provides CLI interface of the Bridge.
package cli
import (
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/abiosoft/ishell"
"github.com/sirupsen/logrus"
)
var (
log = config.GetLogEntry("frontend/cli") //nolint[gochecknoglobals]
log = logrus.WithField("pkg", "frontend/cli") //nolint:gochecknoglobals
)
type frontendCLI struct {
*ishell.Shell
config *config.Config
preferences *config.Preferences
locations *locations.Locations
settings *settings.Settings
eventListener listener.Listener
updates types.Updater
updater types.Updater
bridge types.Bridger
appRestart bool
restarter types.Restarter
}
// New returns a new CLI frontend configured with the given options.
func New( //nolint[funlen]
func New( //nolint:funlen
panicHandler types.PanicHandler,
config *config.Config,
preferences *config.Preferences,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updates types.Updater,
updater types.Updater,
bridge types.Bridger,
) *frontendCLI { //nolint[golint]
restarter types.Restarter,
) *frontendCLI { //nolint:revive
fe := &frontendCLI{
Shell: ishell.New(),
config: config,
preferences: preferences,
locations: locations,
settings: settings,
eventListener: eventListener,
updates: updates,
updater: updater,
bridge: bridge,
appRestart: false,
restarter: restarter,
}
// Clear commands.
@ -76,10 +80,15 @@ func New( //nolint[funlen]
Func: fe.deleteCache,
})
clearCmd.AddCmd(&ishell.Cmd{Name: "accounts",
Help: "remove all accounts from keychain. (aliases: k, keychain)",
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
Aliases: []string{"a", "k", "keychain"},
Func: fe.deleteAccounts,
})
clearCmd.AddCmd(&ishell.Cmd{Name: "everything",
Help: "remove everything",
Aliases: []string{"a", "k", "keychain"},
Func: fe.deleteEverything,
})
fe.AddCmd(clearCmd)
// Change commands.
@ -98,10 +107,6 @@ func New( //nolint[funlen]
Aliases: []string{"p"},
Func: fe.changePort,
})
changeCmd.AddCmd(&ishell.Cmd{Name: "proxy",
Help: "allow or disallow bridge to securely connect to proton via a third party when it is being blocked",
Func: fe.toggleAllowProxy,
})
changeCmd.AddCmd(&ishell.Cmd{Name: "smtp-security",
Help: "change port numbers of IMAP and SMTP servers.(alias: ssl, starttls)",
Aliases: []string{"ssl", "starttls"},
@ -109,19 +114,71 @@ func New( //nolint[funlen]
})
fe.AddCmd(changeCmd)
// Check commands.
checkCmd := &ishell.Cmd{Name: "check", Help: "check internet connection or new version."}
checkCmd.AddCmd(&ishell.Cmd{Name: "updates",
Help: "check for Bridge updates. (aliases: u, v, version)",
Aliases: []string{"u", "version", "v"},
Func: fe.checkUpdates,
// DoH commands.
dohCmd := &ishell.Cmd{Name: "proxy",
Help: "allow or disallow bridge to securely connect to proton via a third party when it is being blocked",
}
dohCmd.AddCmd(&ishell.Cmd{Name: "allow",
Help: "allow bridge to securely connect to proton via a third party when it is being blocked",
Func: fe.allowProxy,
})
checkCmd.AddCmd(&ishell.Cmd{Name: "internet",
Help: "check internet connection. (aliases: i, conn, connection)",
Aliases: []string{"i", "con", "connection"},
Func: fe.checkInternetConnection,
dohCmd.AddCmd(&ishell.Cmd{Name: "disallow",
Help: "disallow bridge to securely connect to proton via a third party when it is being blocked",
Func: fe.disallowProxy,
})
fe.AddCmd(checkCmd)
fe.AddCmd(dohCmd)
// Cache-On-Disk commands.
codCmd := &ishell.Cmd{Name: "local-cache",
Help: "manage the local encrypted message cache",
}
codCmd.AddCmd(&ishell.Cmd{Name: "enable",
Help: "enable the local cache",
Func: fe.enableCacheOnDisk,
})
codCmd.AddCmd(&ishell.Cmd{Name: "disable",
Help: "disable the local cache",
Func: fe.disableCacheOnDisk,
})
codCmd.AddCmd(&ishell.Cmd{Name: "change-location",
Help: "change the location of the local cache",
Func: fe.setCacheOnDiskLocation,
})
fe.AddCmd(codCmd)
// Updates commands.
updatesCmd := &ishell.Cmd{Name: "updates",
Help: "manage bridge updates",
}
updatesCmd.AddCmd(&ishell.Cmd{Name: "check",
Help: "check for Bridge updates",
Func: fe.checkUpdates,
})
autoUpdatesCmd := &ishell.Cmd{Name: "autoupdates",
Help: "manage bridge updates",
}
updatesCmd.AddCmd(autoUpdatesCmd)
autoUpdatesCmd.AddCmd(&ishell.Cmd{Name: "enable",
Help: "automatically keep bridge up to date",
Func: fe.enableAutoUpdates,
})
autoUpdatesCmd.AddCmd(&ishell.Cmd{Name: "disable",
Help: "require bridge to be manually updated",
Func: fe.disableAutoUpdates,
})
updatesChannelCmd := &ishell.Cmd{Name: "channel",
Help: "switch updates channel",
}
updatesCmd.AddCmd(updatesChannelCmd)
updatesChannelCmd.AddCmd(&ishell.Cmd{Name: "early",
Help: "switch to the early-access updates channel",
Func: fe.selectEarlyChannel,
})
updatesChannelCmd.AddCmd(&ishell.Cmd{Name: "stable",
Help: "switch to the stable updates channel",
Func: fe.selectStableChannel,
})
fe.AddCmd(updatesCmd)
// Print info commands.
fe.AddCmd(&ishell.Cmd{Name: "log-dir",
@ -134,11 +191,7 @@ func New( //nolint[funlen]
Aliases: []string{"man"},
Func: fe.printManual,
})
fe.AddCmd(&ishell.Cmd{Name: "release-notes",
Help: "print release notes. (aliases: notes, fixed-bugs, bugs, ver, version)",
Aliases: []string{"notes", "fixed-bugs", "bugs", "ver", "version"},
Func: fe.printLocalReleaseNotes,
})
fe.AddCmd(&ishell.Cmd{Name: "credits",
Help: "print used resources.",
Func: fe.printCredits,
@ -185,27 +238,30 @@ func New( //nolint[funlen]
defer panicHandler.HandlePanic()
fe.watchEvents()
}()
fe.eventListener.RetryEmit(events.TLSCertIssue)
fe.eventListener.RetryEmit(events.ErrorEvent)
return fe
}
func (f *frontendCLI) watchEvents() {
errorCh := f.getEventChannel(events.ErrorEvent)
internetOffCh := f.getEventChannel(events.InternetOffEvent)
internetOnCh := f.getEventChannel(events.InternetOnEvent)
addressChangedCh := f.getEventChannel(events.AddressChangedEvent)
addressChangedLogoutCh := f.getEventChannel(events.AddressChangedLogoutEvent)
logoutCh := f.getEventChannel(events.LogoutEvent)
certIssue := f.getEventChannel(events.TLSCertIssue)
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
for {
select {
case errorDetails := <-errorCh:
f.Println("Bridge failed:", errorDetails)
case <-internetOffCh:
f.notifyInternetOff()
case <-internetOnCh:
f.notifyInternetOn()
case <-credentialsErrorCh:
f.notifyCredentialsError()
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
f.notifyInternetOff()
}
if stat == events.InternetOn {
f.notifyInternetOn()
}
case address := <-addressChangedCh:
f.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
case address := <-addressChangedLogoutCh:
@ -222,28 +278,10 @@ func (f *frontendCLI) watchEvents() {
}
}
func (f *frontendCLI) getEventChannel(event string) <-chan string {
ch := make(chan string)
f.eventListener.Add(event, ch)
return ch
}
// IsAppRestarting returns whether the app is currently set to restart.
func (f *frontendCLI) IsAppRestarting() bool {
return f.appRestart
}
// Loop starts the frontend loop with an interactive shell.
func (f *frontendCLI) Loop(credentialsError error) error {
if credentialsError != nil {
f.notifyCredentialsError()
return credentialsError
}
f.preferences.SetBool(preferences.FirstStartKey, false)
func (f *frontendCLI) Loop() error {
f.Print(`
Welcome to ProtonMail Bridge interactive shell
Welcome to Proton Mail Bridge interactive shell
___....___
^^ __..-:'':__:..:__:'':-..__
_.-:__:.-:'': : : :'':-.:__:-._
@ -262,3 +300,12 @@ func (f *frontendCLI) Loop(credentialsError error) error {
f.Run()
return nil
}
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
}
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
func (f *frontendCLI) NotifySilentUpdateError(err error) {}

View File

@ -1,56 +1,51 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cli
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/pkg/connection"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/abiosoft/ishell"
)
var (
currentPort = "" //nolint[gochecknoglobals]
currentPort = "" //nolint:gochecknoglobals
)
func (f *frontendCLI) restart(c *ishell.Context) {
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
f.Println("Restarting Bridge...")
f.appRestart = true
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) checkInternetConnection(c *ishell.Context) {
if connection.CheckInternetConnection() == nil {
f.Println("Internet connection is available.")
} else {
f.Println("Can not contact server please check you internet connection.")
}
}
func (f *frontendCLI) printLogDir(c *ishell.Context) {
f.Println("Log files are stored in\n\n ", f.config.GetLogDir())
if path, err := f.locations.ProvideLogsPath(); err != nil {
f.Println("Failed to determine location of log files")
} else {
f.Println("Log files are stored in\n\n ", path)
}
}
func (f *frontendCLI) printManual(c *ishell.Context) {
@ -64,14 +59,17 @@ func (f *frontendCLI) deleteCache(c *ishell.Context) {
if !f.yesNoQuestion("Do you really want to remove all stored preferences") {
return
}
if err := f.bridge.ClearData(); err != nil {
f.printAndLogError("Cache clear failed: ", err.Error())
return
}
f.Println("Cached cleared, restarting bridge")
// Clearing data removes everything (db, preferences, ...)
// so everything has to be stopped and started again.
f.appRestart = true
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
f.restarter.SetToRestart()
f.Stop()
}
@ -79,7 +77,7 @@ func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
isSSL := f.preferences.GetBool(preferences.SMTPSSLKey)
isSSL := f.settings.GetBool(settings.SMTPSSLKey)
newSecurity := "SSL"
if isSSL {
newSecurity = "STARTTLS"
@ -88,9 +86,9 @@ func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
if f.yesNoQuestion(msg) {
f.preferences.SetBool(preferences.SMTPSSLKey, !isSSL)
f.settings.SetBool(settings.SMTPSSLKey, !isSSL)
f.Println("Restarting Bridge...")
f.appRestart = true
f.restarter.SetToRestart()
f.Stop()
}
}
@ -99,14 +97,14 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
currentPort = f.preferences.Get(preferences.IMAPPortKey)
currentPort = f.settings.Get(settings.IMAPPortKey)
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
if newIMAPPort == "" {
newIMAPPort = currentPort
}
imapPortChanged := newIMAPPort != currentPort
currentPort = f.preferences.Get(preferences.SMTPPortKey)
currentPort = f.settings.Get(settings.SMTPPortKey)
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
if newSMTPPort == "" {
newSMTPPort = currentPort
@ -120,34 +118,99 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
if imapPortChanged || smtpPortChanged {
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
f.preferences.Set(preferences.IMAPPortKey, newIMAPPort)
f.preferences.Set(preferences.SMTPPortKey, newSMTPPort)
f.settings.Set(settings.IMAPPortKey, newIMAPPort)
f.settings.Set(settings.SMTPPortKey, newSMTPPort)
f.Println("Restarting Bridge...")
f.appRestart = true
f.restarter.SetToRestart()
f.Stop()
} else {
f.Println("Nothing changed")
}
}
func (f *frontendCLI) toggleAllowProxy(c *ishell.Context) {
if f.preferences.GetBool(preferences.AllowProxyKey) {
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
f.preferences.SetBool(preferences.AllowProxyKey, false)
bridge.DisallowDoH()
func (f *frontendCLI) allowProxy(c *ishell.Context) {
if f.bridge.GetProxyAllowed() {
f.Println("Bridge is already set to use alternative routing to connect to Proton if it is being blocked.")
return
}
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
f.bridge.SetProxyAllowed(true)
}
}
func (f *frontendCLI) disallowProxy(c *ishell.Context) {
if !f.bridge.GetProxyAllowed() {
f.Println("Bridge is already set to NOT use alternative routing to connect to Proton if it is being blocked.")
return
}
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
f.bridge.SetProxyAllowed(false)
}
}
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
if f.settings.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache is already enabled.")
return
}
if f.yesNoQuestion("Are you sure you want to enable the local cache") {
if err := f.bridge.EnableCache(); err != nil {
f.Println("The local cache could not be enabled.")
return
}
} else {
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
f.preferences.SetBool(preferences.AllowProxyKey, true)
bridge.AllowDoH()
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) disableCacheOnDisk(c *ishell.Context) {
if !f.settings.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache is already disabled.")
return
}
if f.yesNoQuestion("Are you sure you want to disable the local cache") {
if err := f.bridge.DisableCache(); err != nil {
f.Println("The local cache could not be disabled.")
return
}
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) setCacheOnDiskLocation(c *ishell.Context) {
if !f.settings.GetBool(settings.CacheEnabledKey) {
f.Println("The local cache must be enabled.")
return
}
if location := f.settings.Get(settings.CacheLocationKey); location != "" {
f.Println("The current local cache location is:", location)
}
if location := f.readStringInAttempts("Enter a new location for the cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
if err := f.bridge.MigrateCache(f.settings.Get(settings.CacheLocationKey), location); err != nil {
f.Println("The local cache location could not be changed.")
return
}
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) isPortFree(port string) bool {
port = strings.Replace(port, ":", "", -1)
port = strings.ReplaceAll(port, ":", "")
if port == "" || port == currentPort {
return true
}
@ -162,3 +225,13 @@ func (f *frontendCLI) isPortFree(port string) bool {
}
return true
}
// NOTE(GODT-1158): Check free space in location.
func (f *frontendCLI) isCacheLocationUsable(location string) bool {
stat, err := os.Stat(location)
if err != nil {
return false
}
return stat.IsDir()
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cli
@ -21,40 +21,22 @@ import (
"strings"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/pkg/updates"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
isUpToDate, latestVersionInfo, err := f.updates.CheckIsBridgeUpToDate()
version, err := f.updater.Check()
if err != nil {
f.printAndLogError("Cannot retrieve version info: ", err)
f.checkInternetConnection(c)
f.Println("An error occurred while checking for updates.")
return
}
if isUpToDate {
f.Println("Your version is up to date.")
if f.updater.IsUpdateApplicable(version) {
f.Println("An update is available.")
} else {
f.notifyNeedUpgrade()
f.Println("")
f.printReleaseNotes(latestVersionInfo)
}
}
func (f *frontendCLI) printLocalReleaseNotes(c *ishell.Context) {
localVersion := f.updates.GetLocalVersion()
f.printReleaseNotes(localVersion)
}
func (f *frontendCLI) printReleaseNotes(versionInfo updates.VersionInfo) {
f.Println(bold("ProtonMail Bridge "+versionInfo.Version), "\n")
if versionInfo.ReleaseNotes != "" {
f.Println(bold("Release Notes"))
f.Println(versionInfo.ReleaseNotes)
}
if versionInfo.ReleaseFixedBugs != "" {
f.Println(bold("Fixed bugs"))
f.Println(versionInfo.ReleaseFixedBugs)
f.Println("Your version is up to date.")
}
}
@ -63,3 +45,56 @@ func (f *frontendCLI) printCredits(c *ishell.Context) {
f.Println(pkg)
}
}
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
if f.settings.GetBool(settings.AutoUpdateKey) {
f.Println("Bridge is already set to automatically install updates.")
return
}
f.Println("Bridge is currently set to NOT automatically install updates.")
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
f.settings.SetBool(settings.AutoUpdateKey, true)
}
}
func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
if !f.settings.GetBool(settings.AutoUpdateKey) {
f.Println("Bridge is already set to NOT automatically install updates.")
return
}
f.Println("Bridge is currently set to automatically install updates.")
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
f.settings.SetBool(settings.AutoUpdateKey, false)
}
}
func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
if f.bridge.GetUpdateChannel() == updater.EarlyChannel {
f.Println("Bridge is already on the early-access update channel.")
return
}
f.Println("Bridge is currently on the stable update channel.")
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
f.bridge.SetUpdateChannel(updater.EarlyChannel)
}
}
func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
if f.bridge.GetUpdateChannel() == updater.StableChannel {
f.Println("Bridge is already on the stable update channel.")
return
}
f.Println("Bridge is currently on the early-access update channel.")
f.Println("Switching to the stable channel may reset all data!")
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
f.bridge.SetUpdateChannel(updater.StableChannel)
}
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cli
@ -29,7 +29,7 @@ const (
)
var (
bold = color.New(color.Bold).SprintFunc() //nolint[gochecknoglobals]
bold = color.New(color.Bold).SprintFunc() //nolint:gochecknoglobals
)
func isNotEmpty(val string) bool {
@ -71,7 +71,7 @@ func (f *frontendCLI) printAndLogError(args ...interface{}) {
func (f *frontendCLI) processAPIError(err error) {
log.Warn("API error: ", err)
switch err {
case pmapi.ErrAPINotReachable:
case pmapi.ErrNoConnection:
f.notifyInternetOff()
case pmapi.ErrUpgradeApplication:
f.notifyNeedUpgrade()
@ -93,13 +93,18 @@ func (f *frontendCLI) notifyLogout(address string) {
}
func (f *frontendCLI) notifyNeedUpgrade() {
f.Println("Please download and install the newest version of application from", f.updates.GetDownloadLink())
version, err := f.updater.Check()
if err != nil {
log.WithError(err).Error("Failed to notify need upgrade")
return
}
f.Println("Please download and install the newest version of application from", version.LandingPage)
}
func (f *frontendCLI) notifyCredentialsError() {
// Print in 80-column width.
f.Println("ProtonMail Bridge is not able to detect a supported password manager")
f.Println("(pass, gnome-keyring). Please install and set up a supported password manager")
f.Println("Proton Mail Bridge is not able to detect a supported password manager")
f.Println("(secret-service or pass). Please install and set up a supported password manager")
f.Println("and restart the application.")
}
@ -109,15 +114,15 @@ func (f *frontendCLI) notifyCertIssue() {
be insecure.
Description:
ProtonMail Bridge was not able to establish a secure connection to Proton
Proton Mail Bridge was not able to establish a secure connection to Proton
servers due to a TLS certificate error. This means your connection may
potentially be insecure and susceptible to monitoring by third parties.
Recommendation:
* If you trust your network operator, you can continue to use ProtonMail
* If you trust your network operator, you can continue to use Proton Mail
as usual.
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
* If you don't trust your network operator, reconnect to Proton Mail over a VPN
(such as ProtonVPN) which encrypts your Internet connection, or use
a different network to access ProtonMail.
a different network to access Proton Mail.
`)
}

View File

@ -0,0 +1,76 @@
// 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 clientconfig provides automatic config of IMAP and SMTP.
// For now only for Apple Mail.
package clientconfig
import (
"errors"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/sirupsen/logrus"
)
type AutoConfig interface {
Name() string
Configure(imapPort int, smtpPort int, imapSSl, smtpSSL bool, user types.User, address string) error
}
var (
available = map[string]AutoConfig{} //nolint:gochecknoglobals
ErrNotAvailable = errors.New("configuration not available")
)
const AppleMailClient = "Apple Mail"
func ConfigureAppleMail(user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
return configure(AppleMailClient, user, address, s)
}
func configure(configName string, user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
log := logrus.WithField("pkg", "client_config").WithField("client", configName)
config, ok := available[configName]
if !ok {
return false, ErrNotAvailable
}
imapPort := s.GetInt(settings.IMAPPortKey)
imapSSL := false
smtpPort := s.GetInt(settings.SMTPPortKey)
smtpSSL := s.GetBool(settings.SMTPSSLKey)
if address == "" {
address = user.GetPrimaryAddress()
}
if configName == AppleMailClient {
// If configuring apple mail for Catalina or newer, users should use SSL.
needRestart = false
if !smtpSSL && useragent.IsCatalinaOrNewer() {
smtpSSL = true
s.SetBool(settings.SMTPSSLKey, true)
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
needRestart = true
}
}
return needRestart, config.Configure(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
}

View File

@ -0,0 +1,122 @@
// 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/>.
//go:build darwin
// +build darwin
package clientconfig
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/pkg/mobileconfig"
)
const (
bigSurPreferncesPane = "/System/Library/PreferencePanes/Profiles.prefPane"
)
func init() { //nolint:gochecknoinit
available[AppleMailClient] = &appleMail{}
}
type appleMail struct{}
func (c *appleMail) Name() string { return AppleMailClient }
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) error {
mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
confPath, err := saveConfigTemporarily(mc)
if err != nil {
return err
}
if useragent.IsBigSurOrNewer() {
return exec.Command("open", bigSurPreferncesPane, confPath).Run() //nolint:gosec G204: open command is safe, mobileconfig is generated by us
}
return exec.Command("open", confPath).Run() //nolint:gosec G204: open command is safe, mobileconfig is generated by us
}
func prepareMobileConfig(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) *mobileconfig.Config {
displayName := address
addresses := address
if user.IsCombinedAddressMode() {
displayName = user.GetPrimaryAddress()
addresses = strings.Join(user.GetAddresses(), ",")
}
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
return &mobileconfig.Config{
EmailAddress: addresses,
DisplayName: displayName,
Identifier: "protonmail " + displayName + timestamp,
IMAP: &mobileconfig.IMAP{
Hostname: bridge.Host,
Port: imapPort,
TLS: imapSSL,
Username: displayName,
Password: user.GetBridgePassword(),
},
SMTP: &mobileconfig.SMTP{
Hostname: bridge.Host,
Port: smtpPort,
TLS: smtpSSL,
Username: displayName,
},
}
}
func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
dir, err := ioutil.TempDir("", "protonmail-autoconfig")
if err != nil {
return
}
// Make sure the temporary file is deleted.
go func() {
<-time.After(10 * time.Minute)
_ = os.RemoveAll(dir)
}()
// Make sure the file is only readable for the current user.
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return
}
if err = mc.WriteOut(f); err != nil {
_ = f.Close()
return
}
_ = f.Close()
return
}

View File

@ -1,87 +1,90 @@
// Copyright (c) 2020 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.
// This file is part of Proton Mail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// 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.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package frontend provides all interfaces of the Bridge.
package frontend
import (
"github.com/0xAX/notificator"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/cli"
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/listener"
)
var (
log = config.GetLogEntry("frontend") // nolint[unused]
)
// Frontend is an interface to be implemented by each frontend type (cli, gui, html).
type Frontend interface {
Loop(credentialsError error) error
IsAppRestarting() bool
}
// HandlePanic handles panics which occur for users with GUI.
func HandlePanic() {
notify := notificator.New(notificator.Options{
DefaultIcon: "../frontend/ui/icon/icon.png",
AppName: "ProtonMail Bridge",
})
_ = notify.Push("Fatal Error", "The ProtonMail Bridge has encountered a fatal error. ", "/frontend/icon/icon.png", notificator.UR_CRITICAL)
Loop() error
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
SetVersion(update updater.VersionInfo)
NotifySilentUpdateInstalled()
NotifySilentUpdateError(error)
WaitUntilFrontendIsReady()
}
// New returns initialized frontend based on `frontendType`, which can be `cli` or `qt`.
func New(
version,
buildVersion,
programName,
frontendType string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
config *config.Config,
preferences *config.Preferences,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updates types.Updater,
updater types.Updater,
userAgent *useragent.UserAgent,
bridge *bridge.Bridge,
noEncConfirmator types.NoEncConfirmator,
restarter types.Restarter,
) Frontend {
bridgeWrap := types.NewBridgeWrap(bridge)
return new(version, buildVersion, frontendType, showWindowOnStart, panicHandler, config, preferences, eventListener, updates, bridgeWrap, noEncConfirmator)
}
func new(
version,
buildVersion,
frontendType string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
config *config.Config,
preferences *config.Preferences,
eventListener listener.Listener,
updates types.Updater,
bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator,
) Frontend {
switch frontendType {
case "qt":
return qt.New(
version,
buildVersion,
programName,
showWindowOnStart,
panicHandler,
locations,
settings,
eventListener,
updater,
userAgent,
bridgeWrap,
noEncConfirmator,
restarter,
)
case "cli":
return cli.New(panicHandler, config, preferences, eventListener, updates, bridge)
return cli.New(
panicHandler,
locations,
settings,
eventListener,
updater,
bridgeWrap,
restarter,
)
default:
return qt.New(version, buildVersion, showWindowOnStart, panicHandler, config, preferences, eventListener, updates, bridge, noEncConfirmator)
return nil
}
}

View File

@ -0,0 +1,190 @@
// 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/>.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
Item {
id: root
property ColorScheme colorScheme
property var user
property var _spacing: 12 * ProtonStyle.px
property color usedSpaceColor : {
if (!root.enabled) return root.colorScheme.text_weak
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
if (root.usedFraction < .50) return root.colorScheme.signal_success
if (root.usedFraction < .75) return root.colorScheme.signal_warning
return root.colorScheme.signal_danger
}
property real usedFraction: root.user ? reasonableFracion(root.user.usedBytes, root.user.totalBytes) : 0
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
function reasonableFracion(used, total){
var usedSafe = root.reasonableBytes(used)
var totalSafe = root.reasonableBytes(total)
if (totalSafe == 0 || usedSafe == 0) return 0
if (totalSafe <= usedSafe) return 1
return usedSafe / totalSafe
}
function reasonableBytes(bytes){
var safeBytes = bytes+0
if (safeBytes != bytes) return 0
if (safeBytes < 0) return 0
return Math.ceil(safeBytes)
}
function spaceWithUnits(bytes){
if (bytes*1 !== bytes || bytes == 0 ) return "0 kB"
var units = ['B',"kB", "MB", "GB", "TB"];
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
}
// width expected to be set by parent object
implicitHeight : children[0].implicitHeight
enum ViewType{
SmallView, LargeView
}
property var type : AccountDelegate.SmallView
RowLayout {
spacing: root._spacing
anchors {
top: root.top
left: root.left
right: root.rigth
}
Rectangle {
id: avatar
Layout.fillHeight: true
Layout.preferredWidth: height
radius: ProtonStyle.avatar_radius
color: root.colorScheme.background_avatar
Label {
colorScheme: root.colorScheme
anchors.fill: parent
text: root.user ? root.user.avatarText.toUpperCase(): ""
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Body
case AccountDelegate.LargeView: return Label.Title
}
}
font.weight: Font.Normal
color: "#FFFFFF"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}
ColumnLayout {
id: account
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0
Label {
Layout.maximumWidth: root.width - (
root._spacing + avatar.width
)
colorScheme: root.colorScheme
text: root.user ? user.username : ""
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Body
case AccountDelegate.LargeView: return Label.Title
}
}
elide: Text.ElideMiddle
}
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 }
RowLayout {
spacing: 0
Label {
colorScheme: root.colorScheme
text: root.user && root.user.loggedIn ? root.usedSpace : qsTr("Signed out")
color: root.usedSpaceColor
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
case AccountDelegate.LargeView: return Label.Body
}
}
}
Label {
colorScheme: root.colorScheme
text: root.user && root.user.loggedIn ? " / " + root.totalSpace : ""
color: root.colorScheme.text_weak
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
case AccountDelegate.LargeView: return Label.Body
}
}
}
}
Rectangle {
id: storage_bar
visible: root.user ? root.type == AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
height: 4 * ProtonStyle.px
radius: ProtonStyle.storage_bar_radius
color: root.colorScheme.border_weak
Rectangle {
id: storage_bar_filled
radius: ProtonStyle.storage_bar_radius
color: root.usedSpaceColor
visible: root.user ? parent.visible && root.user.loggedIn : false
anchors {
top : parent.top
bottom : parent.bottom
left : parent.left
}
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
}
}
}
Item {
Layout.fillWidth: true
}
}
}

View File

@ -0,0 +1,251 @@
// 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/>.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
Item {
id: root
property ColorScheme colorScheme
property var backend
property var notifications
property var user
signal showSignIn()
signal showSetupGuide(var user, string address)
property int _leftMargin: 64
property int _rightMargin: 64
property int _topMargin: 32
property int _detailsTopMargin: 25
property int _bottomMargin: 12
property int _spacing: 20
property int _lineWidth: 1
ScrollView {
id: scrollView
clip: true
anchors.fill: parent
Item {
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
width: scrollView.availableWidth
height: scrollView.availableHeight
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
// do not set implicitWidth because implicit width of ColumnLayout will be equal to maximum implicit width of
// internal items. And if one of internal items would be a Text or Label - implicit width of those is always
// equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed
implicitWidth: width
ColumnLayout {
spacing: 0
anchors.fill: parent
Rectangle {
id: topRectangle
color: root.colorScheme.background_norm
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
Layout.fillWidth: true
ColumnLayout {
spacing: root._spacing
anchors.fill: parent
anchors.leftMargin: root._leftMargin
anchors.rightMargin: root._rightMargin
anchors.topMargin: root._topMargin
anchors.bottomMargin: root._bottomMargin
RowLayout { // account delegate with action buttons
Layout.fillWidth: true
AccountDelegate {
Layout.fillWidth: true
colorScheme: root.colorScheme
user: root.user
type: AccountDelegate.LargeView
enabled: root.user ? root.user.loggedIn : false
}
Button {
Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme
text: qsTr("Sign out")
secondary: true
visible: root.user ? root.user.loggedIn : false
onClicked: {
if (!root.user) return
root.user.logout()
}
}
Button {
Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme
text: qsTr("Sign in")
secondary: true
visible: root.user ? !root.user.loggedIn : false
onClicked: {
if (!root.user) return
root.showSignIn()
}
}
Button {
Layout.alignment: Qt.AlignTop
colorScheme: root.colorScheme
icon.source: "icons/ic-trash.svg"
secondary: true
onClicked: {
if (!root.user) return
root.notifications.askDeleteAccount(root.user)
}
}
}
Rectangle {
Layout.fillWidth: true
height: root._lineWidth
color: root.colorScheme.border_weak
}
SettingsItem {
colorScheme: root.colorScheme
text: qsTr("Email clients")
actionText: qsTr("Configure")
description: qsTr("Using the mailbox details below (re)configure your client.")
type: SettingsItem.Button
enabled: root.user ? root.user.loggedIn : false
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
showSeparator: splitMode.visible
onClicked: {
if (!root.user) return
root.showSetupGuide(root.user, user.addresses[0])
}
Layout.fillWidth: true
}
SettingsItem {
id: splitMode
colorScheme: root.colorScheme
text: qsTr("Split addresses")
description: qsTr("Setup multiple email addresses individually.")
type: SettingsItem.Toggle
checked: root.user ? root.user.splitMode : false
visible: root.user ? root.user.addresses.length > 1 : false
enabled: root.user ? root.user.loggedIn : false
showSeparator: addressSelector.visible
onClicked: {
if (!splitMode.checked){
root.notifications.askEnableSplitMode(user)
} else {
addressSelector.currentIndex = 0
root.user.toggleSplitMode(!splitMode.checked)
}
}
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
enabled: root.user ? root.user.loggedIn : false
visible: root.user ? root.user.splitMode : false
ComboBox {
id: addressSelector
colorScheme: root.colorScheme
Layout.fillWidth: true
model: root.user ? root.user.addresses : null
}
Button {
colorScheme: root.colorScheme
text: qsTr("Configure")
secondary: true
onClicked: {
if (!root.user) return
root.showSetupGuide(root.user, addressSelector.displayText)
}
}
}
}
}
Rectangle {
color: root.colorScheme.background_weak
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
Layout.fillWidth: true
ColumnLayout {
id: configuration
anchors.fill: parent
anchors.leftMargin: root._leftMargin
anchors.rightMargin: root._rightMargin
anchors.topMargin: root._detailsTopMargin
anchors.bottomMargin: root._spacing
spacing: root._spacing
visible: root.user ? root.user.loggedIn : false
property string currentAddress: addressSelector.displayText
Label {
colorScheme: root.colorScheme
text: qsTr("Mailbox details")
type: Label.Body_semibold
}
Configuration {
colorScheme: root.colorScheme
title: qsTr("IMAP")
hostname: root.backend.hostname
port: root.backend.portIMAP.toString()
username: configuration.currentAddress
password: root.user ? root.user.password : ""
security: "STARTTLS"
}
Configuration {
colorScheme: root.colorScheme
title: qsTr("SMTP")
hostname : root.backend.hostname
port : root.backend.portSMTP.toString()
username : configuration.currentAddress
password : root.user ? root.user.password : ""
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
}
}
}
}
}
}
}

View File

@ -0,0 +1,234 @@
// 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/>.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import Proton 4.0
import Notifications 1.0
Popup {
id: root
property ColorScheme colorScheme
property Notification notification
property var mainWindow
topMargin: 37
leftMargin: (mainWindow.width - root.implicitWidth)/2
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
popupType: ApplicationWindow.PopupType.Banner
shouldShow: notification ? (notification.active && !notification.dismissed) : false
modal: false
Action {
id: defaultDismissAction
text: qsTr("OK")
onTriggered: {
if (!root.notification) {
return
}
root.notification.dismissed = true
}
}
RowLayout {
id: contentLayout
anchors.fill: parent
spacing: 0
Item {
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin
implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width + 10
radius: ProtonStyle.banner_radius
color: {
if (!root.notification) {
return "transparent"
}
switch (root.notification.type) {
case Notification.NotificationType.Info:
return root.colorScheme.signal_info
case Notification.NotificationType.Success:
return root.colorScheme.signal_success
case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning
case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger
}
}
}
RowLayout {
anchors.fill: parent
anchors.topMargin: 14
anchors.bottomMargin: 14
anchors.leftMargin: 16
spacing: 8
ColorImage {
color: root.colorScheme.text_invert
width: 24
height: 24
sourceSize.width: 24
sourceSize.height: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: {
if (!root.notification) {
return ""
}
switch (root.notification.type) {
case Notification.NotificationType.Info:
return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Success:
return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Warning:
return "./icons/ic-exclamation-circle-filled.svg"
case Notification.NotificationType.Danger:
return "./icons/ic-exclamation-circle-filled.svg"
}
}
}
Label {
colorScheme: root.colorScheme
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 16
color: root.colorScheme.text_invert
text: root.notification ? root.notification.description : ""
wrapMode: Text.WordWrap
}
}
}
Rectangle {
Layout.fillHeight: true
width: 1
color: {
if (!root.notification) {
return "transparent"
}
switch (root.notification.type) {
case Notification.NotificationType.Info:
return root.colorScheme.signal_info_active
case Notification.NotificationType.Success:
return root.colorScheme.signal_success_active
case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning_active
case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger_active
}
}
}
Button {
colorScheme: root.colorScheme
Layout.fillHeight: true
id: actionButton
action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction
background: Item {
clip: true
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width + 10
radius: ProtonStyle.banner_radius
color: {
if (!root.notification) {
return "transparent"
}
var norm
var hover
var active
switch (root.notification.type) {
case Notification.NotificationType.Info:
norm = root.colorScheme.signal_info
hover = root.colorScheme.signal_info_hover
active = root.colorScheme.signal_info_active
break;
case Notification.NotificationType.Success:
norm = root.colorScheme.signal_success
hover = root.colorScheme.signal_success_hover
active = root.colorScheme.signal_success_active
break;
case Notification.NotificationType.Warning:
norm = root.colorScheme.signal_warning
hover = root.colorScheme.signal_warning_hover
active = root.colorScheme.signal_warning_active
break;
case Notification.NotificationType.Danger:
norm = root.colorScheme.signal_danger
hover = root.colorScheme.signal_danger_hover
active = root.colorScheme.signal_danger_active
break;
}
if (actionButton.down) {
return active
}
if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) {
return hover
}
if (actionButton.loading) {
return hover
}
return norm
}
}
}
}
}
}

View File

@ -0,0 +1,269 @@
// 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/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Window 2.13
import Qt.labs.platform 1.1
import Proton 4.0
import Notifications 1.0
QtObject {
id: root
function isInInterval(num, lower_limit, upper_limit) {
return lower_limit <= num && num <= upper_limit
}
function bound(num, lower_limit, upper_limit) {
return Math.max(lower_limit, Math.min(upper_limit, num))
}
property var backend
property var title: "Proton Mail Bridge"
property Notifications _notifications: Notifications {
id: notifications
backend: root.backend
frontendMain: mainWindow
frontendStatus: statusWindow
frontendTray: trayIcon
}
property MainWindow _mainWindow: MainWindow {
id: mainWindow
visible: false
title: root.title
backend: root.backend
notifications: root._notifications
onVisibleChanged: {
backend.dockIconVisible = visible
}
Connections {
target: root.backend
onCacheUnavailable: {
mainWindow.showAndRise()
}
onColorSchemeNameChanged: root.setColorScheme()
}
}
property StatusWindow _statusWindow: StatusWindow {
id: statusWindow
visible: false
title: root.title
backend: root.backend
notifications: root._notifications
onShowMainWindow: {
mainWindow.showAndRise()
}
onShowHelp: {
mainWindow.showHelp()
mainWindow.showAndRise()
}
onShowSettings: {
mainWindow.showSettings()
mainWindow.showAndRise()
}
onShowSignIn: {
mainWindow.showSignIn(username)
mainWindow.showAndRise()
}
onQuit: {
backend.quit()
}
property rect screenRect
property rect iconRect
// use binding from function with width and height as arguments so it will be recalculated every time width and height are changed
property point position: getPosition(width, height)
x: position.x
y: position.y
function getPosition(_width, _height) {
if (screenRect.width === 0 || screenRect.height === 0) {
return Qt.point(0, 0)
}
var _x = 0
var _y = 0
// fit above
_y = iconRect.top - height
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
// position preferebly in the horizontal center but bound to the screen rect
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
return Qt.point(_x, _y)
}
// fit below
_y = iconRect.bottom
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
// position preferebly in the horizontal center but bound to the screen rect
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
return Qt.point(_x, _y)
}
// fit to the left
_x = iconRect.left - width
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
// position preferebly in the vertical center but bound to the screen rect
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
return Qt.point(_x, _y)
}
// fit to the right
_x = iconRect.right
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
// position preferebly in the vertical center but bound to the screen rect
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
return Qt.point(_x, _y)
}
// Fallback: position satatus window right above icon and let window manager decide.
console.warn("Can't position status window: screenRect =", screenRect, "iconRect =", iconRect)
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
return Qt.point(_x, _y)
}
}
property SystemTrayIcon _trayIcon: SystemTrayIcon {
id: trayIcon
visible: true
icon.source: "./icons/systray-mono.png"
icon.mask: true // make sure that systems like macOS will use proper color
tooltip: `Proton Mail Bridge v${backend.version}`
onActivated: {
function calcStatusWindowPosition() {
// On some platforms (X11 / Plasma) Qt does not provide icon position and geometry info.
// In this case we rely on cursor position
var iconRect = Qt.rect(geometry.x, geometry.y, geometry.width, geometry.height)
if (geometry.width == 0 && geometry.height == 0) {
var mousePos = backend.getCursorPos()
iconRect.x = mousePos.x
iconRect.y = mousePos.y
iconRect.width = 0
iconRect.height = 0
}
// Find screen
var screen
for (var i in Qt.application.screens) {
var _screen = Qt.application.screens[i]
if (
isInInterval(iconRect.x, _screen.virtualX, _screen.virtualX + _screen.width) &&
isInInterval(iconRect.y, _screen.virtualY, _screen.virtualY + _screen.height)
) {
screen = _screen
break
}
}
if (!screen) {
// Fallback to primary screen
screen = Qt.application.screens[0]
}
// In case we used mouse to detect icon position - we want to make a fake icon rectangle from a point
if (iconRect.width == 0 && iconRect.height == 0) {
iconRect.x = bound(iconRect.x - 16, screen.virtualX, screen.virtualX + screen.width - 32)
iconRect.y = bound(iconRect.y - 16, screen.virtualY, screen.virtualY + screen.height - 32)
iconRect.width = 32
iconRect.height = 32
}
statusWindow.screenRect = Qt.rect(screen.virtualX, screen.virtualY, screen.width, screen.height)
statusWindow.iconRect = iconRect
}
function toggleWindow(win) {
if (win.visible) {
win.close()
} else {
win.showAndRise()
}
}
switch (reason) {
case SystemTrayIcon.Unknown:
break;
case SystemTrayIcon.Context:
case SystemTrayIcon.Trigger:
case SystemTrayIcon.DoubleClick:
case SystemTrayIcon.MiddleClick:
calcStatusWindowPosition()
toggleWindow(statusWindow)
break;
default:
break;
}
}
}
Component.onCompleted: {
if (!root.backend) {
console.log("backend not loaded")
}
root.setColorScheme()
if (!root.backend.users) {
console.log("users not loaded")
}
var c = root.backend.users.count
var u = root.backend.users.get(0)
// DEBUG
if (c != 0) {
console.log("users non zero", c)
console.log("first user", u )
}
if (c === 0) {
mainWindow.showAndRise()
}
if (u) {
if (c === 1 && u.loggedIn === false) {
mainWindow.showAndRise()
}
}
if (root.backend.showOnStartup) {
mainWindow.showAndRise()
}
root.backend.guiReady()
}
function setColorScheme() {
if (root.backend.colorSchemeName == "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
if (root.backend.colorSchemeName == "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
}
}

View File

@ -0,0 +1,316 @@
// 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/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import Proton 4.0
ColumnLayout {
id: root
property var user
property var userIndex
property var backend
spacing : 5
Layout.fillHeight: true
//Layout.fillWidth: true
property ColorScheme colorScheme
TextField {
colorScheme: root.colorScheme
Layout.fillWidth: true
text: user !== undefined ? user.username : ""
onEditingFinished: {
user.username = text
}
}
ColumnLayout {
Layout.fillWidth: true
Switch {
id: userLoginSwitch
colorScheme: root.colorScheme
text: "LoggedIn"
enabled: user !== undefined && user.username.length > 0
checked: user ? user.loggedIn : false
onCheckedChanged: {
if (!user) {
return
}
if (checked) {
if (user === backend.loginUser) {
var newUserObject = backend.userComponent.createObject(backend, {username: user.username, loggedIn: true, setupGuideSeen: user.setupGuideSeen})
backend.users.append( { object: newUserObject } )
user.username = ""
user.resetLoginRequests()
return
}
user.loggedIn = true
user.resetLoginRequests()
return
} else {
user.loggedIn = false
user.resetLoginRequests()
}
}
}
Switch {
colorScheme: root.colorScheme
text: "Setup guide seen"
enabled: user !== undefined && user.username.length > 0
checked: user ? user.setupGuideSeen : false
onCheckedChanged: {
if (!user) {
return
}
user.setupGuideSeen = checked
}
}
}
RowLayout {
Layout.fillWidth: true
Label {
colorScheme: root.colorScheme
id: loginLabel
text: "Login:"
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
}
Button {
colorScheme: root.colorScheme
text: "name/pass error"
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
onClicked: {
root.backend.loginUsernamePasswordError("")
user.resetLoginRequests()
}
}
Button {
colorScheme: root.colorScheme
text: "free user error"
enabled: user !== undefined //&& user.isLoginRequested
onClicked: {
root.backend.loginFreeUserError()
user.resetLoginRequests()
}
}
Button {
colorScheme: root.colorScheme
text: "connection error"
enabled: user !== undefined //&& user.isLoginRequested
onClicked: {
root.backend.loginConnectionError("")
user.resetLoginRequests()
}
}
}
RowLayout {
Layout.fillWidth: true
Label {
colorScheme: root.colorScheme
id: faLabel
text: "2FA:"
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
}
Button {
colorScheme: root.colorScheme
text: "request"
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
onClicked: {
root.backend.login2FARequested(user.username)
user.isLogin2FARequested = true
}
}
Button {
colorScheme: root.colorScheme
text: "error"
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
onClicked: {
root.backend.login2FAError("")
user.isLogin2FAProvided = false
}
}
Button {
colorScheme: root.colorScheme
text: "Abort"
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
onClicked: {
root.backend.login2FAErrorAbort("")
user.resetLoginRequests()
}
}
}
RowLayout {
Layout.fillWidth: true
Label {
colorScheme: root.colorScheme
id: passLabel
text: "2 Password:"
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
}
Button {
colorScheme: root.colorScheme
text: "request"
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
onClicked: {
root.backend.login2PasswordRequested("")
user.isLogin2PasswordRequested = true
}
}
Button {
colorScheme: root.colorScheme
text: "error"
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
onClicked: {
root.backend.login2PasswordError("")
user.isLogin2PasswordProvided = false
}
}
Button {
colorScheme: root.colorScheme
text: "Abort"
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
onClicked: {
root.backend.login2PasswordErrorAbort("")
user.resetLoginRequests()
}
}
}
RowLayout {
Button {
colorScheme: root.colorScheme
text: "Login Finished"
onClicked: {
root.backend.loginFinished(0+loginFinishedIndex.text)
user.resetLoginRequests()
}
}
TextField {
id: loginFinishedIndex
colorScheme: root.colorScheme
label: "Index:"
text: root.userIndex
}
}
RowLayout {
Button {
colorScheme: root.colorScheme
text: "Already logged in"
onClicked: {
root.backend.loginAlreadyLoggedIn(0+loginAlreadyLoggedInIndex.text)
user.resetLoginRequests()
}
}
TextField {
id: loginAlreadyLoggedInIndex
colorScheme: root.colorScheme
label: "Index:"
text: root.userIndex
}
}
RowLayout {
TextField {
colorScheme: root.colorScheme
label: "used:"
text: user && user.usedBytes ? user.usedBytes : 0
onEditingFinished: {
user.usedBytes = parseFloat(text)
}
implicitWidth: 200
}
TextField {
colorScheme: root.colorScheme
label: "total:"
text: user && user.totalBytes ? user.totalBytes : 0
onEditingFinished: {
user.totalBytes = parseFloat(text)
}
implicitWidth: 200
}
}
RowLayout {
Label {colorScheme: root.colorScheme; text: "Split mode"}
Toggle { colorScheme: root.colorScheme; checked: user ? user.splitMode : false; onClicked: {user.splitMode = !user.splitMode}}
Button { colorScheme: root.colorScheme; text: "Toggle Finished"; onClicked: {user.toggleSplitModeFinished()}}
}
TextArea { // TODO: this is causing binding loop on imlicitWidth
colorScheme: root.colorScheme
text: user && user.addresses ? user.addresses.join("\n") : "user@protonmail.com"
Layout.fillWidth: true
onEditingFinished: {
user.addresses = text.split("\n")
}
}
Item {
Layout.fillHeight: true
}
}

View File

@ -0,0 +1,102 @@
// 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/>.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import Proton 4.0
ColumnLayout {
id: root
property ColorScheme colorScheme
property var backend
property alias currentIndex: usersListView.currentIndex
ListView {
id: usersListView
Layout.fillHeight: true
Layout.preferredWidth: 200
model: backend.usersTest
highlightFollowsCurrentItem: true
delegate: Item {
implicitHeight: children[0].implicitHeight + anchors.topMargin + anchors.bottomMargin
implicitWidth: children[0].implicitWidth + anchors.leftMargin + anchors.rightMargin
width: usersListView.width
anchors.margins: 10
Label {
colorScheme: root.colorScheme
text: modelData.username
anchors.margins: 10
anchors.fill: parent
MouseArea {
anchors.fill: parent
onClicked: {
usersListView.currentIndex = index
}
}
}
}
highlight: Rectangle {
color: root.colorScheme.interaction_default_active
}
}
RowLayout {
Layout.fillWidth: true
Button {
colorScheme: root.colorScheme
text: "+"
onClicked: {
var newUserObject = backend.userComponent.createObject(backend)
newUserObject.username = backend.loginUser.username.length > 0 ? backend.loginUser.username : "test@protonmail.com"
newUserObject.loggedIn = true
newUserObject.setupGuideSeen = true // backend.loginUser.setupGuideSeen
backend.loginUser.username = ""
backend.loginUser.loggedIn = false
backend.loginUser.setupGuideSeen = false
backend.users.append( { object: newUserObject } )
}
}
Button {
colorScheme: root.colorScheme
text: "-"
enabled: usersListView.currentIndex != 0
onClicked: {
// var userObject = backend.users.get(usersListView.currentIndex - 1)
backend.users.remove(usersListView.currentIndex - 1)
// userObject.deleteLater()
}
}
}
}

View File

@ -0,0 +1,28 @@
// 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/>.
import QtQml.Models 2.12
ListModel {
// overriding get method to ignore any role and return directly object itself
function get(row) {
if (row < 0 || row >= count) {
return undefined
}
return data(index(row, 0), Qt.DisplayRole)
}
}

View File

@ -1,430 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.8
import ProtonUI 1.0
import BridgeUI 1.0
// NOTE: Keep the Column so the height and width is inherited from content
Column {
id: root
state: status
anchors.left: parent.left
property int row_width: 50 * Style.px
property int row_height: Style.accounts.heightAccount
property var listalias : aliases.split(";")
property int iAccount: index
Accessible.role: go.goos=="windows" ? Accessible.Grouping : Accessible.Row
Accessible.name: qsTr("Account %1, status %2", "Accessible text describing account row with arguments: account name and status (connected/disconnected), resp.").arg(account).arg(statusMark.text)
Accessible.description: Accessible.name
Accessible.ignored: !enabled || !visible
// Main row
Rectangle {
id: mainaccRow
anchors.left: parent.left
width : row_width
height : row_height
state: { return isExpanded ? "expanded" : "collapsed" }
color: Style.main.background
property string actionName : (
isExpanded ?
qsTr("Collapse row for account %2", "Accessible text of button showing additional configuration of account") :
qsTr("Expand row for account %2", "Accessible text of button hiding additional configuration of account")
). arg(account)
// override by other buttons
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked : {
if (root.state=="connected") {
mainaccRow.toggle_accountSettings()
}
}
cursorShape : root.state == "connected" ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true
onEntered: {
if (mainaccRow.state=="collapsed") {
mainaccRow.color = Qt.lighter(Style.main.background,1.1)
}
}
onExited: {
if (mainaccRow.state=="collapsed") {
mainaccRow.color = Style.main.background
}
}
}
// toggle down/up icon
Text {
id: toggleIcon
anchors {
left : parent.left
verticalCenter : parent.verticalCenter
leftMargin : Style.main.leftMargin
}
color: Style.main.text
font {
pointSize : Style.accounts.sizeChevron * Style.pt
family : Style.fontawesome.name
}
text: Style.fa.chevron_down
MouseArea {
anchors.fill: parent
Accessible.role: Accessible.Button
Accessible.name: mainaccRow.actionName
Accessible.description: mainaccRow.actionName
Accessible.onPressAction : mainaccRow.toggle_accountSettings()
Accessible.ignored: root.state!="connected" || !root.enabled
}
}
// account name
TextMetrics {
id: accountMetrics
font : accountName.font
elide: Qt.ElideMiddle
elideWidth: Style.accounts.elideWidth
text: account
}
Text {
id: accountName
anchors {
verticalCenter : parent.verticalCenter
left : toggleIcon.left
leftMargin : Style.main.leftMargin
}
color: Style.main.text
font {
pointSize : (Style.main.fontSize+2*Style.px) * Style.pt
}
text: accountMetrics.elidedText
}
// status
ClickIconText {
id: statusMark
anchors {
verticalCenter : parent.verticalCenter
left : parent.left
leftMargin : Style.accounts.leftMargin2
}
text : qsTr("connected", "status of a listed logged-in account")
iconText : Style.fa.circle_o
textColor : Style.main.textGreen
enabled : false
Accessible.ignored: true
}
// logout
ClickIconText {
id: logoutAccount
anchors {
verticalCenter : parent.verticalCenter
left : parent.left
leftMargin : Style.accounts.leftMargin3
}
text : qsTr("Log out", "action to log out a connected account")
iconText : Style.fa.power_off
textBold : true
textColor : Style.main.textBlue
}
// remove
ClickIconText {
id: deleteAccount
anchors {
verticalCenter : parent.verticalCenter
right : parent.right
rightMargin : Style.main.rightMargin
}
text : qsTr("Remove", "deletes an account from the account settings page")
iconText : Style.fa.trash_o
textColor : Style.main.text
onClicked : {
dialogGlobal.input=root.iAccount
dialogGlobal.state="deleteUser"
dialogGlobal.show()
}
}
// functions
function toggle_accountSettings() {
if (root.state=="connected") {
if (mainaccRow.state=="collapsed" ) {
mainaccRow.state="expanded"
} else {
mainaccRow.state="collapsed"
}
}
}
states: [
State {
name: "collapsed"
PropertyChanges { target : toggleIcon ; text : root.state=="connected" ? Style.fa.chevron_down : " " }
PropertyChanges { target : accountName ; font.bold : false }
PropertyChanges { target : mainaccRow ; color : Style.main.background }
PropertyChanges { target : addressList ; visible : false }
},
State {
name: "expanded"
PropertyChanges { target : toggleIcon ; text : Style.fa.chevron_up }
PropertyChanges { target : accountName ; font.bold : true }
PropertyChanges { target : mainaccRow ; color : Style.accounts.backgroundExpanded }
PropertyChanges { target : addressList ; visible : true }
}
]
}
// List of adresses
Column {
id: addressList
anchors.left : parent.left
width: row_width
visible: false
property alias model : repeaterAddresses.model
Rectangle {
id: addressModeWrapper
anchors {
left : parent.left
right : parent.right
}
visible : mainaccRow.state=="expanded"
height : 2*Style.accounts.heightAddrRow/3
color : Style.accounts.backgroundExpanded
ClickIconText {
id: addressModeSwitch
anchors {
top : addressModeWrapper.top
right : addressModeWrapper.right
rightMargin : Style.main.rightMargin
}
textColor : Style.main.textBlue
iconText : Style.fa.exchange
iconOnRight : false
text : isCombinedAddressMode ?
qsTr("Switch to split addresses mode", "Text of button switching to mode with one configuration per each address.") :
qsTr("Switch to combined addresses mode", "Text of button switching to mode with one configuration for all addresses.")
onClicked: {
dialogGlobal.input=root.iAccount
dialogGlobal.state="addressmode"
dialogGlobal.show()
}
}
ClickIconText {
id: combinedAddressConfig
anchors {
top : addressModeWrapper.top
left : addressModeWrapper.left
leftMargin : Style.accounts.leftMarginAddr+Style.main.leftMargin
}
visible : isCombinedAddressMode
text : qsTr("Mailbox configuration", "Displays IMAP/SMTP settings information for a given account")
iconText : Style.fa.gear
textColor : Style.main.textBlue
onClicked : {
infoWin.showInfo(root.iAccount,0)
}
}
}
Repeater {
id: repeaterAddresses
model: ["one", "two"]
Rectangle {
id: addressRow
visible: !isCombinedAddressMode
anchors {
left : parent.left
right : parent.right
}
height: Style.accounts.heightAddrRow
color: Style.accounts.backgroundExpanded
// icon level down
Text {
id: levelDown
anchors {
left : parent.left
leftMargin : Style.accounts.leftMarginAddr
verticalCenter : wrapAddr.verticalCenter
}
text : Style.fa.level_up
font.family : Style.fontawesome.name
color : Style.main.textDisabled
rotation : 90
}
Rectangle {
id: wrapAddr
anchors {
top : parent.top
left : levelDown.right
right : parent.right
leftMargin : Style.main.leftMargin
rightMargin : Style.main.rightMargin
}
height: Style.accounts.heightAddr
border {
width : Style.main.border
color : Style.main.line
}
color: Style.accounts.backgroundAddrRow
TextMetrics {
id: addressMetrics
font: address.font
elideWidth: 2*wrapAddr.width/3
elide: Qt.ElideMiddle
text: modelData
}
Text {
id: address
anchors {
verticalCenter : parent.verticalCenter
left: parent.left
leftMargin: Style.main.leftMargin
}
font.pointSize : Style.main.fontSize * Style.pt
color: Style.main.text
text: addressMetrics.elidedText
}
ClickIconText {
id: addressConfig
anchors {
verticalCenter : parent.verticalCenter
right: parent.right
rightMargin: Style.main.rightMargin
}
text : qsTr("Address configuration", "Display the IMAP/SMTP configuration for address")
iconText : Style.fa.gear
textColor : Style.main.textBlue
onClicked : infoWin.showInfo(root.iAccount,index)
Accessible.description: qsTr("Address configuration for %1", "Accessible text of button displaying the IMAP/SMTP configuration for address %1").arg(modelData)
Accessible.ignored: !enabled
}
MouseArea {
id: clickSettings
anchors.fill: wrapAddr
onClicked : addressConfig.clicked()
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onPressed: {
wrapAddr.color = Qt.rgba(1,1,1,0.20)
}
onEntered: {
wrapAddr.color = Qt.rgba(1,1,1,0.15)
}
onExited: {
wrapAddr.color = Style.accounts.backgroundAddrRow
}
}
}
}
}
}
Rectangle {
id: line
color: Style.accounts.line
height: Style.accounts.heightLine
width: root.row_width
}
states: [
State {
name: "connected"
PropertyChanges {
target : addressList
model : listalias
}
PropertyChanges {
target : toggleIcon
color : Style.main.text
}
PropertyChanges {
target : accountName
color : Style.main.text
}
PropertyChanges {
target : statusMark
textColor : Style.main.textGreen
text : qsTr("connected", "status of a listed logged-in account")
iconText : Style.fa.circle
}
PropertyChanges {
target : logoutAccount
text : qsTr("Log out", "action to log out a connected account")
onClicked : {
mainaccRow.state="collapsed"
dialogGlobal.input = root.iAccount
dialogGlobal.state = "logout"
dialogGlobal.show()
dialogGlobal.confirmed()
}
}
},
State {
name: "disconnected"
PropertyChanges {
target : addressList
model : 0
}
PropertyChanges {
target : toggleIcon
color : Style.main.textDisabled
}
PropertyChanges {
target : accountName
color : Style.main.textDisabled
}
PropertyChanges {
target : statusMark
textColor : Style.main.textDisabled
text : qsTr("disconnected", "status of a listed logged-out account")
iconText : Style.fa.circle_o
}
PropertyChanges {
target : logoutAccount
text : qsTr("Log in", "action to log in a disconnected account")
onClicked : {
dialogAddUser.username = root.listalias[0]
dialogAddUser.show()
dialogAddUser.inputPassword.focusInput = true
}
}
}
]
}

View File

@ -1,72 +0,0 @@
// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Dialog with main menu
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
Rectangle {
id: root
color: "#aaff5577"
anchors {
left : tabbar.left
right : tabbar.right
top : tabbar.bottom
bottom : parent.bottom
}
visible: false
MouseArea {
anchors.fill: parent
onClicked: toggle()
}
Rectangle {
color : Style.menu.background
radius : Style.menu.radius
width : Style.menu.width
height : Style.menu.height
anchors {
top : parent.top
right : parent.right
topMargin : Style.menu.topMargin
rightMargin : Style.menu.rightMargin
}
MouseArea {
anchors.fill: parent
}
Text {
anchors.centerIn: parent
text: qsTr("About")
color: Style.menu.text
}
}
function toggle(){
if (root.visible == false) {
root.visible = true
} else {
root.visible = false
}
}
}

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