Compare commits

...

151 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
901 changed files with 27862 additions and 38527 deletions

33
.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,17 +14,12 @@ coverage.html
# Run files
mem.pprof
# Auto generated frontend
internal/frontend/qml/BridgeUI/*.qmlc
internal/frontend/qml/ImportExportUI/*.qmlc
internal/frontend/qml/ProtonUI/*.qmlc
internal/frontend/qml/ProtonUI/fontawesome.ttf
internal/frontend/qml/ProtonUI/images
internal/frontend/qml/ImportExportUI/images
frontend/qml/*.qmlc
# Credits files (generated).
# Auto generated
internal/**/credits.go
vendor
vendor-cache
/main.go
# Build files
/launcher-*
@ -37,18 +29,3 @@ internal/**/credits.go
/hasher
cmd/Desktop-Bridge/deploy
cmd/Import-Export/deploy
internal/frontend/qt*/moc.cpp
internal/frontend/qt*/moc.go
internal/frontend/qt*/moc.h
internal/frontend/qt*/moc_cgo_*.go
internal/frontend/qt*/moc_moc.h
internal/frontend/qt*/rcc.cpp
internal/frontend/qt*/rcc.qrc
internal/frontend/qt*/rcc_cgo_*.go
internal/frontend/rcc.cpp
internal/frontend/rcc.qrc
internal/frontend/rcc_cgo_*.go
vendor-cache/
/main.go

View File

@ -43,12 +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:
- env GOMAXPROCS=$(( ${CI_TAG_CPU} / 2 )) make lint
tags:
- medium
test:
test-linux:
stage: test
only:
- branches
@ -65,6 +69,14 @@ test:
tags:
- medium
test-windows:
extends: .build-windows-base
stage: test
only:
- branches
script:
- make test
test-integration:
stage: test
only:
@ -81,15 +93,37 @@ dependency-updates:
# Stage: BUILD
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:
- branches
- 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:
# 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.
# 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
@ -105,6 +139,7 @@ build-linux-qa:
extends: .build-base
only:
- web
- branches
script:
- BUILD_TAGS="build_qa" make build
artifacts:
@ -112,31 +147,10 @@ build-linux-qa:
paths:
- bridge_*.tgz
build-ie-linux:
extends: .build-base
script:
- make build-ie
artifacts:
name: "ie-linux-$CI_COMMIT_SHORT_SHA"
paths:
- ie_*.tgz
build-ie-linux-qa:
extends: .build-base
only:
- web
script:
- BUILD_TAGS="build_qa" make build-ie
artifacts:
name: "ie-linux-qa-$CI_COMMIT_SHORT_SHA"
paths:
- ie_*.tgz
.build-darwin-base:
extends: .build-base
before_script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- export PATH=/usr/local/bin:$PATH
- export PATH=/usr/local/opt/git/bin:$PATH
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
@ -160,6 +174,7 @@ build-darwin-qa:
extends: .build-darwin-base
only:
- web
- branches
script:
- BUILD_TAGS="build_qa" make build
artifacts:
@ -167,25 +182,39 @@ build-darwin-qa:
paths:
- bridge_*.tgz
build-ie-darwin:
extends: .build-darwin-base
script:
- make build-ie
artifacts:
name: "ie-darwin-$CI_COMMIT_SHORT_SHA"
paths:
- ie_*.tgz
build-ie-darwin-qa:
extends: .build-darwin-base
.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-ie
- BUILD_TAGS="build_qa" make build
artifacts:
name: "ie-darwin-qa-$CI_COMMIT_SHORT_SHA"
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
paths:
- ie_*.tgz
- bridge_*.tgz
# Stage: MIRROR

0
.gitmodules vendored
View File

View File

@ -1,8 +1,6 @@
---
run:
timeout: 10m
build-tags:
- nogui
skip-dirs:
- pkg/mime
@ -58,7 +56,6 @@ linters:
- 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]
- 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]

View File

@ -1,7 +1,9 @@
# Building ProtonMail Bridge and Import-Export app
# Building Proton Mail Bridge and Import-Export app
## Prerequisites
* 64-bit OS (the go-rfc5322 module cannot currently be compiled for 32-bit OSes)
* 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/)

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,72 +1,93 @@
# Copying
Copyright (c) 2020 Proton Technologies AG
Copyright (c) 2022 Proton AG
ProtonMail Bridge is free software: you can redistribute it and/or modify it
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, but WITHOUT ANY
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.
Proton Mail 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:
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 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](https://github.com/0xAX/notificator) | Available under [license](https://github.com/0xAX/notificator/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)
* [singleinstance](https://github.com/allan-simon/go-singleinstance) | Available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
* [cascadia](https://github.com/andybalholm/cascadia) | Available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
* [gocertifi](https://github.com/certifi/gocertifi) | Available under [license](https://github.com/certifi/gocertifi/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)
* [wincred](https://github.com/danieljoos/wincred) | Available under [license](https://github.com/danieljoos/wincred/blob/master/LICENSE)
* [credential-helpers](https://github.com/docker/docker-credential-helpers) | Available under [license](https://github.com/docker/docker-credential-helpers/blob/master/LICENSE)
* [imap](https://github.com/emersion/go-imap) | Available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
* [imap-appendlimit](https://github.com/emersion/go-imap-appendlimit) | Available under [license](https://github.com/emersion/go-imap-appendlimit/blob/master/LICENSE)
* [imap-idle](https://github.com/emersion/go-imap-idle) | Available under [license](https://github.com/emersion/go-imap-idle/blob/master/LICENSE)
* [imap-quota](https://github.com/emersion/go-imap-quota) | Available under [license](https://github.com/emersion/go-imap-quota/blob/master/LICENSE)
* [sasl](https://github.com/emersion/go-sasl) | Available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
* [smtp](https://github.com/emersion/go-smtp) | Available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
* [textwrapper](https://github.com/emersion/go-textwrapper) | Available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
* [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.md)
* [shlex](https://github.com/flynn-archive/go-shlex) | Available under [license](https://github.com/flynn-archive/go-shlex/blob/master/COPYING)
* [raven](https://github.com/getsentry/raven-go) | Available under [license](https://github.com/getsentry/raven-go/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty) | Available under [license](https://github.com/go-resty/resty/blob/master/LICENSE)
* [mock](https://github.com/golang/mock) | Available under [license](https://github.com/golang/mock/blob/master/LICENSE)
* [cmp](https://github.com/google/go-cmp) | Available under [license](https://github.com/google/go-cmp/blob/master/LICENSE)
* [gopherjs](https://github.com/gopherjs/gopherjs) | Available under [license](https://github.com/gopherjs/gopherjs/blob/master/LICENSE)
* [multierror](https://github.com/hashicorp/go-multierror) | Available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
* [bcrypt](https://github.com/jameskeane/bcrypt) | Available under [license](https://github.com/jameskeane/bcrypt/blob/master/LICENSE)
* [html2text](https://github.com/jaytaylor/html2text) | Available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
* [enmime](https://github.com/jhillyerd/enmime) | Available under [license](https://github.com/jhillyerd/enmime/blob/master/LICENSE)
* [osext](https://github.com/kardianos/osext) | Available under [license](https://github.com/kardianos/osext/blob/master/LICENSE)
* [keychain](https://github.com/keybase/go-keychain) | Available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [aurora](https://github.com/logrusorgru/aurora) | Available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) | Available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [uuid](https://github.com/myesui/uuid) | Available under [license](https://github.com/myesui/uuid/blob/master/LICENSE)
* [jsondiff](https://github.com/nsf/jsondiff) | Available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
* [logrus](https://github.com/sirupsen/logrus) | Available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
* [golang](https://github.com/skratchdot/open-golang) | Available under [license](https://github.com/skratchdot/open-golang/blob/master/LICENSE)
* [testify](https://github.com/stretchr/testify) | Available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
* [uuid](https://github.com/twinj/uuid) | Available under [license](https://github.com/twinj/uuid/blob/master/LICENSE)
* [cli](https://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)
<!-- 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 -->

View File

@ -1,7 +1,198 @@
# ProtonMail Bridge and Import-Export app Changelog
# Proton Mail Bridge and Import-Export app Changelog
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [Bridge 2.2.0] Millau
### Added
* Rebranding:
* GODT-1508: Splash screen for rebranding.
* GODT-1542: Update login screen for rebranding.
* GODT-1260: Renaming.
* GODT-1502: Rebranding: color and radius.
* GODT-1549: Add notification when address list changes.
* GODT-1560: Dependecy licenses update and link.
### Changed
* GODT-1543: Using one buffered event for off and on connection.
* GODT-1550: Update dependencies.
* GODT-1545 GODT-1521: Change wording and enable release notes link.
### Fixed
* GODT-1534: Reset address when leaving split mode.
## [Bridge 2.1.3] London
### Added
GODT-1525: Add keybase/go-keychain/secretservice as new keychain helper.
### Changed
GODT-1527: Change bug report description.
### Fixed
GODT-1537: Manual in-app update mechanism.
## [Bridge 2.1.2] London
### Added
* GODT-1522: Rebuild macOS keychain notification.
* GODT-1437 Add new proxy provider (Quad9 with port).
* GODT-1516: Return notification on missing keychain.
### Changed
* GODT-1451: Do not check for gnome keyring to allow other implementations of secret-service API. Thanks to @remgodow.
* GODT-1516 GODT-1451: KeepassXC is crashing on start. We need to block it until it's fixed.
### Fixed
* GODT-1524: Logout issues with macOS.
* GODT-1503 GODT-1492: Improve email validation and username in bug report.
* GODT-1507: Enable autostart after Qt setup.
* GODT-1515: Do not crash when bridge users got disconnected.
## [Bridge 2.1.1] London
### Added
* GODT-1376: Add first userID to sentry scope.
* GODT-1375: Add host architecture to sentry reports.
* GODT-1364: Add windows CI machine for tests, and build.
### Fixed
* GODT-1499: Remove message from DB once it is not on server any more.
## [Bridge 2.1.0] London
### Fixed
* GODT-1482: Comment or mitigate panics, unlock cache when needed.
* GODT-1481: Always turn off non-encrypted recipient report.
### Changed
* GODT-1494: Update GopenPGP to 2.4.1.
* GODT-1490: Update go sentry and openpgp.
* GODT-1474: Optimising live integration tests.
* GODT-1483: Correct scope in sentry report.
* GODT-1477: Change CoD wording.
### Added
* GODT-1478: Add GUI settings for keychain selection.
* Other: Change copyright year.
* GODT-1329: Dark mode, with autodetect.
## [Bridge 2.0.1] Kwai
### Fixed
* GODT-1468: Fix main windows status and add background context without retry.
## [Bridge 2.0.0] Kwai
### Added
* GODT-22: New GUI style and improved UX:
* GODT-1168 GODT-1169 Qml artifacts for preview.
* GODT-1177: Remove Import-Export from repo.
* GODT-1167 GODT-1179 Make run-qml-preview.
* GODT-1051: Add factory reset to bridge object.
* GODT-1179 GODT-658: Components and login flows.
* GODT-1051: Factory reset button.
* GODT-1158: Adding cache on disk signals.
* GODT-1298: Signal GUI is ready and rise window.
* Other: Reactive show on startup.
* GODT-1319: Set sourceSize everywhere for images.
* GODT-1317 Use large png for systray and mark it as mask.
* 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.
* GODT-1271: Fix Status margings.
* GODT-1320: Add loading property to each action within a notification.
* GODT-1210: Add "free user" banner.
* GODT-1314: Limit description field length within 150/800 bounds.
* GODT-1250: Fix Port settings wording.
* GODT-1369: Fix link render and wording in Help view.
* GODT-1358: Fix wording.
* GODT-1272: Fix status view layout.
* GODT-1336: Fix showing window on startup.
* GODT-175: Add option to attach logs for bug reports.
* GODT-1272: Ultimate fix for MacOS transparency.
* GODT-1384: Fix SettingsView scroll.
* GODT-1385: Fix port setting.
* GODT-1378: varia GUI fixes.
* GODT-1390: Fix autostart toggle.
* GODT-1251: Fix change SMTP settings.
* GODT-1389: Fix buttons and banner layout.
* GODT-1316: Set default TextArea and TextField behavior.
* GODT-1244: Refactor switching stable-early and factory reset.
* GODT-1351: Cache and update of space bytes in user object.
* GODT-1351: Fix used size update from mail operations.
* GODT-1411: refactor SettingView content to fill height.
* GODT-1327: Reset cache path to default when disabling.
* GODT-1412: Refactor paths and links.
* GODT-1226: Fix status window position.
* GODT-1366: Simple lookup of index and select current user.
* GODT-1325: Add "already logged in" notification.
* GODT-1391: Fix link colors across GUI.
* GODT-1391: Fix color for avatar text.
* GODT-1442: Fix "Sign In" button.
* GODT-1428: Fix welcome illustration by using PNG.
* GODT-1455 Adding links to setup guide.
* GODT-1456: Make text selectable and clickable.
* GODT-1459: Wording.
* GODT-1460 GODT-1462: Adding delete account dialog and fixing status view brief and icon.
* GODT-1458: Splash screen and wording.
* GODT-1158: Caching encrypted full body messages on disk:
* GODT-1433: Do not save message to cache if it's a draft.
* GODT-1431 Do not cache message during new message event when CoD is off.
* GODT-1381 Treat readonly folder as failure for cache on disk.
* GODT-1431 Prevent watcher when not using disk on cache.
* GODT-1381: Use in-memory cache in case local cache is unavailable.
* GODT-1356 GODT-1302: Cache on disk concurency and API retries.
* GODT-1332 Added tests for cache move functions.
* GODT-1332: moved cache related functions to separate file.
* GODT-1332 moving cache does not work on Windows.
* GODT-1367: use waitgroup instead of channel in pool/pchan.
* GODT-1367: Use sync.Once to only close pool jobs once.
* GODT-1349: Change cache-related settings when enabling/disabling/moving cache.
* GODT-1350: stop cacher/worker properly when logging out user.
* GODT-1158: Store full messages bodies on disk.
* GODT-1433 Adding first integration test for drafts.
### Changed
* GODT-1438: Turn off SW OpenGL on windows and add debug info about graphic renderer.
* GODT-1425: Factory reset enables launch on startup.
* GODT-1433 Message.Type is deprecated, use Flags instead.
* GODT-1388: Refactor Alternative routing.
## [Bridge 1.8.12] James
### Fixed
* GODT-1432: Check if keys are active before unlocking.
## [Bridge 1.8.11] James
### Fixed
* GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
* GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append.
* GODT-1392: Fix broken header fields for attachments.
* GODT-1360: Fix live integration test.
* GODT-968: Messages in All Mail should not be able to mark as deleted.
* GODT-967: Append external message to All Mail should be APPEND to Archive instead.
* GODT-966: Append internal message to AllMail should be no action.
* GODT-965: MOVE command should end with error for All Mail.
* GODT-963: STORE removing junk or adding nojunk should move message to inbox.
### Changed
* GODT-1397: Update bbolt to v1.3.6.
* GODT-1410: Remove event ID from sentry report description.
* GODT-1395: CI should fail on go.sum changed.
## [Bridge 1.8.10] James
### Fixed
@ -13,6 +204,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-1205: "RCPT TO" does not contain all addressed from "CC".
* GODT-1103: Cleanup on windows when uninstalling Bridge.
## [Bridge 1.8.9] James
### Fixed

View File

@ -7,29 +7,16 @@ TARGET_CMD?=Desktop-Bridge
TARGET_OS?=${GOOS}
## Build
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
.PHONY: build build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=1.8.10+git
IE_APP_VERSION?=1.3.3+git
BRIDGE_APP_VERSION?=2.2.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico
SRC_ICNS:=Bridge.icns
SRC_SVG:=logo.svg
TGT_ICNS:=Bridge.icns
EXE_NAME:=proton-bridge
CONFIGNAME:=bridge
WINDRES_DEFINE:=BUILD_BRIDGE
ifeq "${TARGET_CMD}" "Import-Export"
APP_VERSION:=${IE_APP_VERSION}
SRC_ICO:=ie.ico
SRC_ICNS:=ie.icns
SRC_SVG:=ie.svg
TGT_ICNS:=ImportExport.icns
EXE_NAME:=proton-ie
CONFIGNAME:=importExport
WINDRES_DEFINE:=BUILD_IE
endif
REVISION:=$(shell git rev-parse --short=10 HEAD)
BUILD_TIME:=$(shell date +%FT%T%z)
@ -41,7 +28,6 @@ ifneq "${BUILD_LDFLAGS}" ""
GO_LDFLAGS+=${BUILD_LDFLAGS}
endif
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
GO_LDFLAGS_LAUNCHER+=$(addprefix -X main.,ConfigName=${CONFIGNAME} ExeName=proton-${APP})
ifeq "${TARGET_OS}" "windows"
GO_LDFLAGS_LAUNCHER+=-H=windowsgui
endif
@ -70,9 +56,6 @@ EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
EXE_QT_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE_QT}
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
ifeq "${TARGET_CMD}" "Import-Export"
TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz
endif
ifdef QT_API
VENDOR_TARGET:=prepare-vendor update-qt-docs
@ -82,15 +65,9 @@ endif
build: ${TGZ_TARGET}
build-ie:
TARGET_CMD=Import-Export $(MAKE) build
build-nogui: gofiles
go build ${BUILD_FLAGS} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
build-ie-nogui:
TARGET_CMD=Import-Export $(MAKE) build-nogui
ifeq "${GOOS}" "windows"
PRERESOURCECMD:=cp ./resource.syso ./cmd/launcher/resource.syso
POSTRESOURCECMD:=rm -f ./cmd/launcher/resource.syso
@ -100,9 +77,6 @@ build-launcher: ${RESOURCE_FILE}
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${EXE} ./cmd/launcher/
${POSTRESOURCECMD}
build-launcher-ie:
TARGET_CMD=Import-Export $(MAKE) build-launcher
versioner:
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
@ -111,10 +85,10 @@ hasher:
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
rm -f $@
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ .
cd ${DEPLOY_DIR}/${TARGET_OS} && tar -czvf ../../../../$@ .
${DEPLOY_DIR}/linux: ${EXE_TARGET}
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
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/
@ -124,7 +98,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
fi
cp ./internal/frontend/share/icons/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${TGT_ICNS}
cp ./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"
@ -132,7 +106,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
${DEPLOY_DIR}/windows: ${EXE_TARGET}
cp ./internal/frontend/share/icons/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
cp ./internal/frontend/share/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
cp LICENSE ${DEPLOY_DIR}/windows/
QT_BUILD_TARGET:=build desktop
@ -153,9 +127,9 @@ ${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
WINDRES_YEAR:=$(shell date +%Y)
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/icons/${SRC_ICO} .FORCE
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/icons/ -D ${WINDRES_DEFINE} -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
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 update-qt-docs
@ -230,16 +204,13 @@ test: gofiles
./internal/cookies/... \
./internal/crash/... \
./internal/events/... \
./internal/frontend/autoconfig/... \
./internal/frontend/cli/... \
./internal/imap/... \
./internal/importexport/... \
./internal/locations/... \
./internal/logging/... \
./internal/metrics/... \
./internal/smtp/... \
./internal/store/... \
./internal/transfer/... \
./internal/updater/... \
./internal/users/... \
./internal/versioner/... \
@ -259,17 +230,19 @@ integration-test-bridge:
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/transfer PanicHandler,IMAPClientProvider > internal/transfer/mocks/mocks.go
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser,ChangeNotifier > internal/store/mocks/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-changelog
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
@ -285,7 +258,7 @@ updates: install-go-mod-outdated
doc:
godoc -http=:6060
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html release-notes/ie_stable.html release-notes/ie_early.html
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html
release-notes/%.html: release-notes/%.md
./utils/release_notes.sh $^
@ -293,26 +266,22 @@ release-notes/%.html: release-notes/%.md
.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/importexport/credits.go
gofiles: ./internal/bridge/credits.go
./internal/bridge/credits.go: ./utils/credits.sh go.mod
cd ./utils/ && ./credits.sh bridge
./internal/importexport/credits.go: ./utils/credits.sh go.mod
cd ./utils/ && ./credits.sh importexport
## Run and debug
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview run-ie run-ie-qt run-ie-qt-cli run-ie-nogui run-ie-nogui-cli clean-vendor clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common clean
.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_FLAGS_IE?=-m -l=${LOG}
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
@ -322,28 +291,17 @@ run-nogui-cli: clean-vendor gofiles
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}" cmd/${TARGET_CMD}/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
run-ie-qml-preview:
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
run-ie:
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run
run-ie-qt:
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-qt
run-ie-nogui:
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-nogui
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
clean-frontend-qt-ie:
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local clean
clean-frontend-qt-common:
$(MAKE) -C internal/frontend/qt-common -f Makefile.local clean
$(MAKE) -C internal/frontend -f Makefile.local clean
clean-vendor: clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common
clean-vendor: clean-frontend-qt clean-frontend-qt-common
rm -rf ./vendor
clean: clean-vendor

View File

@ -1,22 +1,22 @@
# ProtonMail Bridge and Import Export app
Copyright (c) 2020 Proton Technologies AG
# Proton Mail Bridge and Import Export app
Copyright (c) 2022 Proton AG
This repository holds the ProtonMail Bridge and the ProtonMail Import-Export applications.
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
For a detailed build information see [BUILDS](./BUILDS.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 Bridge
ProtonMail Bridge for e-mail clients.
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
@ -25,9 +25,9 @@ background.
More details [on the public website](https://protonmail.com/bridge).
## Description Import-Export app
ProtonMail Import-Export app for importing and exporting messages.
Proton Mail Import-Export app for importing and exporting messages.
To transfer messages, firstly log in using your ProtonMail credentials.
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,
@ -37,10 +37,12 @@ check the results.
More details [on the public website](https://protonmail.com/import-export).
## Launchers
Launchers are binaries used to run the ProtonMail Bridge or Import-Export apps.
The Import-Export app is developed in separate branch `master-ie`.
Official distributions of the ProtonMail Bridge and Import-Export apps contain
## 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
@ -50,11 +52,13 @@ 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
@ -69,7 +73,6 @@ or
### Integration testing
- `TEST_ENV`: set which env to use (fake or live)
- `TEST_APP`: set which app to test (bridge or ie)
- `TEST_ACCOUNTS`: set JSON file with configured accounts
- `TAGS`: set build tags for tests
- `FEATURES`: set feature dir, file or scenario to test

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,19 +1,19 @@
// Copyright (c) 2021 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
@ -43,8 +43,8 @@ import (
)
const (
appName = "ProtonMail Bridge"
appUsage = "ProtonMail IMAP and SMTP Bridge"
appName = "Proton Mail Bridge"
appUsage = "Proton Mail IMAP and SMTP Bridge"
configName = "bridge"
updateURLName = "bridge"
keychainName = "bridge"

View File

@ -1,57 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"os"
"github.com/ProtonMail/proton-bridge/internal/app/base"
"github.com/ProtonMail/proton-bridge/internal/app/ie"
"github.com/sirupsen/logrus"
)
const (
appName = "ProtonMail Import-Export app"
appUsage = "Import and export messages to/from your ProtonMail account"
configName = "importExport"
updateURLName = "ie"
keychainName = "import-export-app"
cacheVersion = "c11"
)
func main() {
base, err := base.New(
appName,
appUsage,
configName,
updateURLName,
keychainName,
cacheVersion,
)
if err != nil {
logrus.WithError(err).Fatal("Failed to create app base")
}
// Other instance already running.
if base == nil {
return
}
if err := ie.New(base).Run(os.Args); err != nil {
logrus.WithError(err).Fatal("IE exited with error")
}
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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
@ -38,25 +38,24 @@ import (
"github.com/sirupsen/logrus"
)
const appName = "ProtonMail Launcher"
var (
ConfigName = "" // nolint[gochecknoglobals]
ExeName = "" // nolint[gochecknoglobals]
const (
appName = "Proton Mail Launcher"
configName = "bridge"
exeName = "proton-bridge"
)
func main() { // nolint[funlen]
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))
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)
locations := locations.New(locationsProvider, configName)
logsPath, err := locations.ProvideLogsPath()
if err != nil {
@ -87,9 +86,9 @@ func main() { // nolint[funlen]
versioner := versioner.New(updatesPath)
exe, err := getPathToUpdatedExecutable(ExeName, versioner, kr, reporter)
exe, err := getPathToUpdatedExecutable(exeName, versioner, kr, reporter)
if err != nil {
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
if exe, err = getFallbackExecutable(exeName, versioner); err != nil {
logrus.WithError(err).Fatal("Failed to find any launchable executable")
}
}
@ -99,7 +98,7 @@ func main() { // nolint[funlen]
logrus.WithError(err).Fatal("Failed to determine path to launcher")
}
cmd := exec.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) // nolint[gosec]
cmd := exec.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) //nolint:gosec
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout

View File

@ -1,8 +1,8 @@
[Desktop Entry]
Type=Application
Version=1.1
Name=ProtonMail Bridge
GenericName=ProtonMail Bridge for Linux
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

View File

@ -1,11 +0,0 @@
[Desktop Entry]
Type=Application
Version=1.1
Name=ProtonMail Import-Export app
GenericName=ProtonMail Import-Export app for Linux
Comment=The Import-Export app helps you to migrate your emails from local files or remote IMAP servers to ProtonMail or simply export emails to local folder.
Icon=protonmail-import-export-app
Exec=protonmail-import-export-app
Terminal=false
Categories=Office;Email;Network
StartupWMClass=protonmail-import-export-app

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,135 +0,0 @@
# Import-Export app
## Main blocks
This is basic overview of the main Import-Export blocks.
```mermaid
graph LR
S[ProtonMail server]
U[User]
subgraph "Import-Export app"
Users
Frontend["Qt / CLI"]
ImportExport
Transfer
Frontend --> ImportExport
Frontend --> Transfer
ImportExport --> Users
ImportExport --> Transfer
end
EML --> Transfer
MBOX --> Transfer
IMAP --> Transfer
S --> Transfer
Transfer --> EML
Transfer --> MBOX
Transfer --> S
U --> Frontend
```
## Code structure
More detailed graph of main types used in Import-Export app and connection between them.
```mermaid
graph TD
PM[ProtonMail Server]
EML[EML]
MBOX[MBOX]
IMAP[IMAP]
subgraph "Import-Export app"
subgraph "pkg users"
subgraph "pkg credentials"
CredStore[Store]
Creds[Credentials]
CredStore --> Creds
end
US[Users]
U[User]
US --> U
end
subgraph "pkg frontend"
CLI
Qt
end
subgraph "pkg importExport"
IE[ImportExport]
end
subgraph "pkg transfer"
Transfer
Rules
Progress
Provider
LocalProvider
EMLProvider
MBOXProvider
IMAPProvider
PMAPIProvider
Mailbox
Message
Transfer --> |source|Provider
Transfer --> |target|Provider
Transfer --> Rules
Transfer --> Progress
Provider --> LocalProvider
Provider --> EMLProvider
Provider --> MBOXProvider
Provider --> IMAPProvider
Provider --> PMAPIProvider
LocalProvider --> EMLProvider
LocalProvider --> MBOXProvider
Provider --> Mailbox
Provider --> Message
end
subgraph PMAPI
APIM[ClientManager]
APIC[Client]
APIM --> APIC
end
end
CLI --> IE
CLI --> Transfer
CLI --> Progress
Qt --> IE
Qt --> Transfer
Qt --> Progress
U --> CredStore
U --> Creds
US --> APIM
U --> APIM
PMAPIProvider --> APIM
EMLProvider --> EML
MBOXProvider --> MBOX
IMAPProvider --> IMAP
IE --> US
IE --> Transfer
APIC --> PM
```

View File

@ -1,14 +1,9 @@
# Documentation
# Bridge Documentation
Documentation pages in order to read for a novice:
## Bridge
* [Bridge code](bridge.md)
* [Internal Bridge database](database.md)
* [Communication between Bridge, Client and Server](communication.md)
* [Encryption](encryption.md)
## Import-Export app
* [Import-Export code](importexport.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_

31
go.mod
View File

@ -14,12 +14,12 @@ require (
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c
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/v2 v2.2.2
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
@ -28,11 +28,12 @@ require (
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
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-mbox v1.0.2
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
@ -40,23 +41,26 @@ require (
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
github.com/fatih/color v1.9.0
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/getsentry/sentry-go v0.8.0
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.1
github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.1.1
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
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-20200502122510-cda31fe0c86d
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/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/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
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
@ -64,10 +68,12 @@ require (
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.5
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
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 (
@ -75,4 +81,5 @@ replace (
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
)

104
go.sum
View File

@ -29,9 +29,8 @@ github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756
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-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/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=
@ -42,14 +41,12 @@ github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1
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.0-20210910093455-a843a0b9adff h1:eiue56XAPSkOpsy5Fwnyz4+Vd7i2cN5D4orc++Irt1g=
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
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/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
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=
@ -105,6 +102,8 @@ github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6T
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=
@ -116,6 +115,11 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
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=
@ -124,8 +128,6 @@ github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1y
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-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
@ -145,8 +147,8 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWT
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
github.com/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=
@ -164,10 +166,13 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
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=
@ -183,9 +188,9 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-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=
@ -197,9 +202,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
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=
@ -247,6 +251,9 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw
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=
@ -262,6 +269,8 @@ github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7
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=
@ -278,7 +287,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/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=
@ -286,14 +295,18 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
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 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
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.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-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=
@ -349,8 +362,13 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:
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=
@ -369,8 +387,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d 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=
@ -418,6 +434,7 @@ github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKn
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=
@ -433,8 +450,8 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB
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.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
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=
@ -451,8 +468,11 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
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 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
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=
@ -497,8 +517,12 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
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 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
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=
@ -508,6 +532,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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=
@ -531,21 +556,39 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
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/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
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.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 h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
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-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -567,6 +610,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 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=
@ -608,6 +652,7 @@ 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=
@ -621,4 +666,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
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) 2021 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.
//
@ -34,7 +34,7 @@ import (
)
var (
log = logrus.WithField("pkg", "api") //nolint[gochecknoglobals]
log = logrus.WithField("pkg", "api") //nolint:gochecknoglobals
)
type apiServer struct {
@ -44,7 +44,7 @@ type apiServer struct {
}
// NewAPIServer returns prepared API server struct.
func NewAPIServer(settings *settings.Settings, eventListener listener.Listener) *apiServer { //nolint[golint]
func NewAPIServer(settings *settings.Settings, eventListener listener.Listener) *apiServer { //nolint:revive
return &apiServer{
host: bridge.Host,
settings: settings,
@ -68,7 +68,7 @@ func (api *apiServer) ListenAndServe() {
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 {

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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,19 +1,19 @@
// Copyright (c) 2021 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
@ -42,7 +42,7 @@ func CheckOtherInstanceAndFocus(port int) error {
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,19 +1,19 @@
// Copyright (c) 2021 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 base

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 base implements a common application base currently shared by bridge and IE.
// The base includes the following:
@ -73,7 +73,8 @@ const (
FlagCLI = "cli"
flagCLIShort = "c"
flagRestart = "restart"
flagLauncher = "launcher"
FlagLauncher = "launcher"
FlagNoWindow = "no-window"
)
type Base struct {
@ -101,7 +102,7 @@ type Base struct {
teardown []func() error // actions to perform when app is exiting
}
func New( // nolint[funlen]
func New( //nolint:funlen
appName,
appUsage,
configName,
@ -158,6 +159,10 @@ func New( // nolint[funlen]
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
@ -188,9 +193,11 @@ func New( // nolint[funlen]
cm := pmapi.New(cfg)
sentryReporter.SetClientFromManager(cm)
cm.AddConnectionObserver(pmapi.NewConnectionObserver(
func() { listener.Emit(events.InternetOffEvent, "") },
func() { listener.Emit(events.InternetOnEvent, "") },
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOff) },
func() { listener.Emit(events.InternetConnChangedEvent, events.InternetOn) },
))
jar, err := cookies.NewCookieJar(settingsObj)
@ -233,9 +240,9 @@ func New( // nolint[funlen]
}
autostart := &autostart.App{
Name: appName,
Name: startupNameForRebranding(appName),
DisplayName: appName,
Exec: []string{exe},
Exec: []string{exe, "--" + FlagNoWindow},
}
return &Base{
@ -264,13 +271,13 @@ func New( // nolint[funlen]
}, nil
}
func (b *Base) NewApp(action func(*Base, *cli.Context) error) *cli.App {
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.run(action)
app.Action = b.wrapMainLoop(mainLoop)
app.Flags = []cli.Flag{
&cli.BoolFlag{
Name: flagCPUProfile,
@ -292,13 +299,17 @@ func (b *Base) NewApp(action func(*Base, *cli.Context) error) *cli.App {
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,
Name: FlagLauncher,
Usage: "The launcher to use to restart the application",
Hidden: true,
},
@ -317,15 +328,18 @@ func (b *Base) AddTeardownAction(fn func() error) {
b.teardown = append(b.teardown, fn)
}
func (b *Base) run(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc { // nolint[funlen]
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/autostart.
if launcher := c.String(flagLauncher); launcher != "" {
b.Autostart.Exec = []string{launcher}
// 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) {
@ -350,10 +364,13 @@ func (b *Base) run(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc {
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
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 base
@ -50,7 +50,7 @@ func migrateFiles(configName string) error {
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
if err := migrateUpdatesFrom16x(configName, locations); err != nil { //nolint:revive It is more clear to structure this way
return err
}
return nil

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

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 base

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 base
@ -33,6 +33,7 @@ func (b *Base) restartApp(crash bool) error {
if crash {
args = incrementRestartFlag(os.Args)[1:]
defer func() { os.Exit(1) }()
} else {
args = os.Args[1:]
}
@ -42,7 +43,7 @@ func (b *Base) restartApp(crash bool) error {
WithField("args", args).
Warn("Restarting")
return exec.Command(b.command, args...).Start() // nolint[gosec]
return exec.Command(b.command, args...).Start() //nolint:gosec
}
// incrementRestartFlag increments the value of the restart flag.

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 base

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 implements the bridge CLI application.
package bridge
@ -24,7 +24,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/app/base"
"github.com/ProtonMail/proton-bridge/internal/bridge"
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"
@ -32,7 +32,10 @@ import (
"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"
@ -41,12 +44,15 @@ import (
const (
flagLogIMAP = "log-imap"
flagLogSMTP = "log-smtp"
flagNoWindow = "no-window"
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(run)
app := base.NewApp(mailLoop)
app.Flags = append(app.Flags, []cli.Flag{
&cli.StringFlag{
@ -55,9 +61,6 @@ func New(base *base.Base) *cli.App {
&cli.BoolFlag{
Name: flagLogSMTP,
Usage: "Enable logging of SMTP communications (may contain decrypted data!)"},
&cli.BoolFlag{
Name: flagNoWindow,
Usage: "Don't show window after start"},
&cli.BoolFlag{
Name: flagNonInteractive,
Usage: "Start Bridge entirely noninteractively"},
@ -66,15 +69,47 @@ func New(base *base.Base) *cli.App {
return app
}
func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
tlsConfig, err := loadTLSConfig(b)
if err != nil {
logrus.WithError(err).Fatal("Failed to load TLS config")
return err
}
bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.SentryReporter, b.CrashHandler, b.Listener, b.CM, b.Creds, b.Updater, b.Versioner)
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, bridge)
// 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()
@ -100,9 +135,6 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe()
}()
// Bridge supports no-window option which we should use for autostart.
b.Autostart.Exec = append(b.Autostart.Exec, "--"+flagNoWindow)
// We want to remove old versions if the app exits successfully.
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
@ -125,7 +157,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
constants.BuildVersion,
b.Name,
frontendMode,
!c.Bool(flagNoWindow),
!c.Bool(base.FlagNoWindow),
b.CrashHandler,
b.Locations,
b.Settings,
@ -134,7 +166,6 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
b.UserAgent,
bridge,
smtpBackend,
b.Autostart,
b,
)
@ -233,3 +264,51 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
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

@ -1,110 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package ie implements the ie CLI application.
package ie
import (
"time"
"github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/app/base"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"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/importexport"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
func New(b *base.Base) *cli.App {
return b.NewApp(run)
}
func run(b *base.Base, c *cli.Context) error {
ie := importexport.New(b.Locations, b.Cache, b.CrashHandler, b.Listener, b.CM, b.Creds)
go func() {
defer b.CrashHandler.HandlePanic()
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
}()
var frontendMode string
switch {
case c.Bool(base.FlagCLI):
frontendMode = "cli"
default:
frontendMode = "qt"
}
// 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)
f := frontend.NewImportExport(
constants.Version,
constants.BuildVersion,
b.Name,
frontendMode,
b.CrashHandler,
b.Locations,
b.Settings,
b.Listener,
b.Updater,
ie,
b,
)
// Watch for updates routine
go func() {
ticker := time.NewTicker(time.Hour)
for {
checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey))
<-ticker.C
}
}()
return f.Loop()
}
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) { //nolint[unparam]
log := logrus.WithField("pkg", "app/ie")
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")
f.NotifyManualUpdate(version, u.CanInstall(version))
}

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,44 +1,48 @@
// Copyright (c) 2021 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 functionality of Bridge app.
package bridge
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"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/ProtonMail/proton-bridge/pkg/listener"
logrus "github.com/sirupsen/logrus"
)
var (
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
)
var log = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
var ErrLocalCacheUnavailable = errors.New("local cache is unavailable")
type Bridge struct {
*users.Users
@ -48,46 +52,69 @@ type Bridge struct {
clientManager pmapi.Manager
updater Updater
versioner Versioner
cacheProvider CacheProvider
autostart *autostart.App
// Bridge's global errors list.
errors []error
isFirstStart bool
lastVersion string
}
func New(
locations Locator,
cache Cacher,
s SettingsProvider,
cacheProvider CacheProvider,
setting SettingsProvider,
sentryReporter *sentry.Reporter,
panicHandler users.PanicHandler,
eventListener listener.Listener,
cache cache.Cache,
builder *message.Builder,
clientManager pmapi.Manager,
credStorer users.CredentialsStorer,
updater Updater,
versioner Versioner,
autostart *autostart.App,
) *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 s.GetBool(settings.AllowProxyKey) {
if setting.GetBool(settings.AllowProxyKey) {
clientManager.AllowProxy()
}
storeFactory := newStoreFactory(cache, sentryReporter, panicHandler, eventListener)
u := users.New(locations, panicHandler, eventListener, clientManager, credStorer, storeFactory, true)
b := &Bridge{
Users: u,
u := users.New(
locations,
panicHandler,
eventListener,
clientManager,
credStorer,
newStoreFactory(cacheProvider, sentryReporter, panicHandler, eventListener, cache, builder),
)
b := &Bridge{
Users: u,
locations: locations,
settings: s,
settings: setting,
clientManager: clientManager,
updater: updater,
versioner: versioner,
cacheProvider: cacheProvider,
autostart: autostart,
isFirstStart: false,
}
if s.GetBool(settings.FirstStartKey) {
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")
}
s.SetBool(settings.FirstStartKey, false)
setting.SetBool(settings.FirstStartKey, false)
}
// 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()
return b
@ -117,55 +144,60 @@ func (b *Bridge) heartbeat() {
}
}
// ReportBug reports a new bug from the user.
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
return b.clientManager.ReportBug(context.Background(), pmapi.ReportBugReq{
OS: osType,
OSVersion: osVersion,
Browser: emailClient,
Title: "[Bridge] Bug",
Description: description,
Username: accountName,
Email: address,
})
}
// GetUpdateChannel returns currently set update channel.
func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
return updater.UpdateChannel(b.settings.Get(settings.UpdateChannelKey))
}
// SetUpdateChannel switches update channel.
// Downgrading to previous version (by switching from early to stable, for example)
// requires clearing all data including update files due to possibility of
// inconsistency between versions and absence of backwards migration scripts.
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) (needRestart bool, err error) {
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) {
b.settings.Set(settings.UpdateChannelKey, string(channel))
}
func (b *Bridge) resetToLatestStable() error {
version, err := b.updater.Check()
if err != nil {
return false, err
// 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")
}
return nil
}
// We have to deal right away only with downgrade - that action needs to
// clear data and updates, and install bridge right away. But regular
// upgrade can be leaved out for periodic check.
if !b.updater.IsDowngrade(version) {
return false, nil
}
if err := b.Users.ClearData(); err != nil {
log.WithError(err).Error("Failed to clear data while downgrading channel")
}
if err := b.locations.ClearUpdates(); err != nil {
log.WithError(err).Error("Failed to clear updates while downgrading channel")
// If current version is same as upstream stable version - do nothing.
if version.Version.Equal(semver.MustParse(constants.Version)) {
return nil
}
if err := b.updater.InstallUpdate(version); err != nil {
return false, err
return err
}
return true, b.versioner.RemoveOtherVersions(version.Version)
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.
@ -177,3 +209,96 @@ func (b *Bridge) GetKeychainApp() string {
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
}
func (b *Bridge) DisableCache() error {
if err := b.Users.DisableCache(); err != nil {
return err
}
b.settings.SetBool(settings.CacheEnabledKey, false)
// Reset back to the default location when disabling.
b.settings.Set(settings.CacheLocationKey, b.cacheProvider.GetDefaultMessageCacheDir())
return nil
}
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
}
// 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()
}
}
// 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)
}
// 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
}
}
}
// 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

@ -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) 2021 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,19 +1,19 @@
// Copyright (c) 2021 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 'Fri Jan 22 11:01:06 AM CET 2021'. DO NOT EDIT.

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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
@ -23,47 +23,65 @@ import (
"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 {
cache Cacher
cacheProvider CacheProvider
sentryReporter *sentry.Reporter
panicHandler users.PanicHandler
eventListener listener.Listener
storeCache *store.Cache
events *store.Events
cache cache.Cache
builder *message.Builder
}
func newStoreFactory(
cache Cacher,
cacheProvider CacheProvider,
sentryReporter *sentry.Reporter,
panicHandler users.PanicHandler,
eventListener listener.Listener,
cache cache.Cache,
builder *message.Builder,
) *storeFactory {
return &storeFactory{
cache: cache,
cacheProvider: cacheProvider,
sentryReporter: sentryReporter,
panicHandler: panicHandler,
eventListener: eventListener,
storeCache: store.NewCache(cache.GetIMAPCachePath()),
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) {
storePath := getUserStorePath(f.cache.GetDBDir(), user.ID())
return store.New(f.sentryReporter, f.panicHandler, user, f.eventListener, storePath, f.storeCache)
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 {
storePath := getUserStorePath(f.cache.GetDBDir(), userID)
return store.RemoveStore(f.storeCache, storePath, userID)
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) {
fileName := fmt.Sprintf("mailbox-%v.db", userID)
return filepath.Join(storeDir, fileName)
return filepath.Join(storeDir, fmt.Sprintf("mailbox-%v.db", userID))
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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
@ -26,11 +26,13 @@ import (
type Locator interface {
Clear() error
ClearUpdates() error
ProvideLogsPath() (string, error)
}
type Cacher interface {
type CacheProvider interface {
GetIMAPCachePath() string
GetDBDir() string
GetDefaultMessageCacheDir() string
}
type SettingsProvider interface {
@ -38,6 +40,7 @@ type SettingsProvider interface {
Set(key string, value string)
GetBool(key string) bool
SetBool(key string, val bool)
GetInt(key string) int
}
type Updater interface {

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 cache provides access to contents inside a cache directory.
package cache
@ -45,6 +45,11 @@ 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")

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 cache

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 settings
@ -61,7 +61,7 @@ func (p *keyValueStore) load() error {
if err != nil {
return err
}
defer f.Close() //nolint[errcheck]
defer f.Close() //nolint:errcheck,gosec
return json.NewDecoder(f).Decode(&p.cache)
}
@ -100,18 +100,28 @@ func (p *keyValueStore) GetBool(key string) bool {
}
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
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 settings
@ -25,81 +25,118 @@ import (
"github.com/stretchr/testify/require"
)
const testPrefFilePath = "/tmp/pref.json"
func TestLoadNoKeyValueStore(t *testing.T) {
pref := newTestEmptyKeyValueStore(t)
require.Equal(t, "", pref.Get("key"))
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
r.Equal("", pref.Get("key"))
}
func TestLoadBadKeyValueStore(t *testing.T) {
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"key\":\"value"), 0700))
pref := newKeyValueStore(testPrefFilePath)
require.Equal(t, "", pref.Get("key"))
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 TestKeyValueStoreGet(t *testing.T) {
pref := newTestKeyValueStore(t)
require.Equal(t, "value", pref.Get("str"))
require.Equal(t, "42", pref.Get("int"))
require.Equal(t, "true", pref.Get("bool"))
require.Equal(t, "t", pref.Get("falseBool"))
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) {
pref := newTestKeyValueStore(t)
require.Equal(t, 0, pref.GetInt("str"))
require.Equal(t, 42, pref.GetInt("int"))
require.Equal(t, 0, pref.GetInt("bool"))
require.Equal(t, 0, pref.GetInt("falseBool"))
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) {
pref := newTestKeyValueStore(t)
require.Equal(t, false, pref.GetBool("str"))
require.Equal(t, false, pref.GetBool("int"))
require.Equal(t, true, pref.GetBool("bool"))
require.Equal(t, false, pref.GetBool("falseBool"))
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) {
pref := newTestEmptyKeyValueStore(t)
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.setDefault("key", "value")
pref.setDefault("key", "othervalue")
require.Equal(t, "value", pref.Get("key"))
r.Equal("value", pref.Get("key"))
}
func TestKeyValueStoreSet(t *testing.T) {
pref := newTestEmptyKeyValueStore(t)
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.Set("str", "value")
checkSavedKeyValueStore(t, "{\n\t\"str\": \"value\"\n}")
checkSavedKeyValueStore(r, pref.path, "{\n\t\"str\": \"value\"\n}")
}
func TestKeyValueStoreSetInt(t *testing.T) {
pref := newTestEmptyKeyValueStore(t)
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.SetInt("int", 42)
checkSavedKeyValueStore(t, "{\n\t\"int\": \"42\"\n}")
checkSavedKeyValueStore(r, pref.path, "{\n\t\"int\": \"42\"\n}")
}
func TestKeyValueStoreSetBool(t *testing.T) {
pref := newTestEmptyKeyValueStore(t)
r := require.New(t)
pref, clean := newTestEmptyKeyValueStore(r)
defer clean()
pref.SetBool("trueBool", true)
pref.SetBool("falseBool", false)
checkSavedKeyValueStore(t, "{\n\t\"falseBool\": \"false\",\n\t\"trueBool\": \"true\"\n}")
checkSavedKeyValueStore(r, pref.path, "{\n\t\"falseBool\": \"false\",\n\t\"trueBool\": \"true\"\n}")
}
func newTestEmptyKeyValueStore(t *testing.T) *keyValueStore {
require.NoError(t, os.RemoveAll(testPrefFilePath))
return newKeyValueStore(testPrefFilePath)
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 newTestKeyValueStore(t *testing.T) *keyValueStore {
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
return newKeyValueStore(testPrefFilePath)
func newTestEmptyKeyValueStore(r *require.Assertions) (*keyValueStore, func()) {
path, clean := newTmpFile(r)
return newKeyValueStore(path), clean
}
func checkSavedKeyValueStore(t *testing.T, expected string) {
data, err := ioutil.ReadFile(testPrefFilePath)
require.NoError(t, err)
require.Equal(t, expected, string(data))
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

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 settings provides access to persistent user settings.
package settings
@ -43,6 +43,18 @@ const (
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 {
@ -78,8 +90,19 @@ func (s *Settings) setDefaultValues() {
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(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)

View File

@ -1,26 +1,26 @@
// Copyright (c) 2021 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 tls
import "os/exec"
func addTrustedCert(certPath string) error {
return exec.Command( // nolint[gosec]
return exec.Command( //nolint:gosec
"/usr/bin/security",
"execute-with-privileges",
"/usr/bin/security",
@ -34,7 +34,7 @@ func addTrustedCert(certPath string) error {
}
func removeTrustedCert(certPath string) error {
return exec.Command( // nolint[gosec]
return exec.Command( //nolint:gosec
"/usr/bin/security",
"execute-with-privileges",
"/usr/bin/security",

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 tls

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 tls

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 tls
@ -55,8 +55,8 @@ func NewTLSTemplate() (*x509.Certificate, error) {
SerialNumber: serialNumber,
Subject: pkix.Name{
Country: []string{"CH"},
Organization: []string{"Proton Technologies AG"},
OrganizationalUnit: []string{"ProtonMail"},
Organization: []string{"Proton AG"},
OrganizationalUnit: []string{"Proton Mail"},
CommonName: "127.0.0.1",
},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
@ -110,7 +110,7 @@ func (t *TLS) GenerateCerts(template *x509.Certificate) error {
if err != nil {
return err
}
defer certOut.Close() // nolint[errcheck]
defer certOut.Close() //nolint:errcheck,gosec
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return err
@ -120,7 +120,7 @@ func (t *TLS) GenerateCerts(template *x509.Certificate) error {
if err != nil {
return err
}
defer keyOut.Close() // nolint[errcheck]
defer keyOut.Close() //nolint:errcheck,gosec
return pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
}
@ -144,7 +144,7 @@ func (t *TLS) GetConfig() (*tls.Config, error) {
caCertPool := x509.NewCertPool()
caCertPool.AddCert(c.Leaf)
// nolint[gosec]: We need to support older TLS versions for AppleMail and Outlook.
//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",

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 tls

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 useragent

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 useragent

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 useragent

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 useragent

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 Proton Technologies AG
// Copyright (c) 2022 Proton AG
//
// This file is part of ProtonMail Bridge.Bridge.
// This file is part of Proton Mail Bridge.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 constants contains variables that are set via ldflags during build.
package constants
@ -22,7 +22,7 @@ import "fmt"
const VendorName = "protonmail"
// nolint[gochecknoglobals]
//nolint:gochecknoglobals
var (
// Version of the build.
Version = ""

View File

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

View File

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

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 cookies implements a persistent cookie jar which satisfies the http.CookieJar interface.
package cookies

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 cookies

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 crash

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 crash implements a crash handler with configurable recovery actions.
package crash

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 crash

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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
@ -34,13 +34,15 @@ const (
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
@ -51,7 +53,9 @@ func SetupEvents(listener listener.Listener) {
listener.SetLimit(LogoutEvent, LogoutEventTimeout)
listener.SetBuffer(ErrorEvent)
listener.SetBuffer(CredentialsErrorEvent)
listener.SetBuffer(InternetOffEvent)
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,33 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package 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.User, addressIndex int) error
}
var available []AutoConfig //nolint[gochecknoglobals]
func Available() []AutoConfig {
return available
}

View File

@ -1,100 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"fmt"
"strconv"
"strings"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/abiosoft/ishell"
)
// completeUsernames is a helper to complete usernames as the user types.
func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
if len(args) > 1 {
return
}
arg := ""
if len(args) == 1 {
arg = args[0]
}
for _, user := range f.ie.GetUsers() {
if strings.HasPrefix(strings.ToLower(user.Username()), strings.ToLower(arg)) {
usernames = append(usernames, user.Username())
}
}
return
}
// noAccountWrapper is a decorator for functions which need any account to be properly functional.
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
return func(c *ishell.Context) {
users := f.ie.GetUsers()
if len(users) == 0 {
f.Println("No active accounts. Please add account to continue.")
} else {
callback(c)
}
}
}
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.User {
user := f.getUserByIndexOrName("")
if user != nil {
return user
}
numberOfAccounts := len(f.ie.GetUsers())
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
if len(c.Args) == 0 {
f.Printf("Please choose %s or username.\n", indexRange)
return nil
}
arg := c.Args[0]
user = f.getUserByIndexOrName(arg)
if user == nil {
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
return nil
}
return user
}
func (f *frontendCLI) getUserByIndexOrName(arg string) types.User {
users := f.ie.GetUsers()
numberOfAccounts := len(users)
if numberOfAccounts == 0 {
return nil
}
if numberOfAccounts == 1 {
return users[0]
}
if index, err := strconv.Atoi(arg); err == nil {
if index < 0 || index >= numberOfAccounts {
return nil
}
return users[index]
}
for _, user := range users {
if user.Username() == arg {
return user
}
}
return nil
}

View File

@ -1,154 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"context"
"strings"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) listAccounts(c *ishell.Context) {
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
f.Printf(bold(strings.ReplaceAll(spacing, "d", "s")), "#", "account", "status", "address mode")
for idx, user := range f.ie.GetUsers() {
connected := "disconnected"
if user.IsConnected() {
connected = "connected"
}
mode := "split"
if user.IsCombinedAddressMode() {
mode = "combined"
}
f.Printf(spacing, idx, user.Username(), connected, mode)
}
f.Println()
}
func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
f.ShowPrompt(false)
defer f.ShowPrompt(true)
loginName := ""
if len(c.Args) > 0 {
user := f.getUserByIndexOrName(c.Args[0])
if user != nil {
loginName = user.GetPrimaryAddress()
}
}
if loginName == "" {
loginName = f.readStringInAttempts("Username", c.ReadLine, isNotEmpty)
if loginName == "" {
return
}
} else {
f.Println("Username:", loginName)
}
password := f.readStringInAttempts("Password", c.ReadPassword, isNotEmpty)
if password == "" {
return
}
f.Println("Authenticating ... ")
client, auth, err := f.ie.Login(loginName, []byte(password))
if err != nil {
f.processAPIError(err)
return
}
if auth.HasTwoFactor() {
twoFactor := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
if twoFactor == "" {
return
}
err = client.Auth2FA(context.Background(), twoFactor)
if err != nil {
f.processAPIError(err)
return
}
}
mailboxPassword := password
if auth.HasMailboxPassword() {
mailboxPassword = f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)
}
if mailboxPassword == "" {
return
}
f.Println("Adding account ...")
user, err := f.ie.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)
return
}
f.Printf("Account %s was added successfully.\n", bold(user.Username()))
}
func (f *frontendCLI) logoutAccount(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c)
if user == nil {
return
}
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username())) {
if err := user.Logout(); err != nil {
f.printAndLogError("Logging out failed: ", err)
}
}
}
func (f *frontendCLI) deleteAccount(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c)
if user == nil {
return
}
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username())) {
clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
if err := f.ie.DeleteUser(user.ID(), clearCache); err != nil {
f.printAndLogError("Cannot delete account: ", err)
return
}
}
}
func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
if !f.yesNoQuestion("Do you really want remove all accounts") {
return
}
for _, user := range f.ie.GetUsers() {
if err := f.ie.DeleteUser(user.ID(), false); err != nil {
f.printAndLogError("Cannot delete account ", user.Username(), ": ", err)
}
}
c.Println("Keychain cleared")
}

View File

@ -1,224 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Package cliie provides CLI interface of the Import-Export app.
package cliie
import (
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"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 = logrus.WithField("pkg", "frontend/cli-ie") //nolint[gochecknoglobals]
)
type frontendCLI struct {
*ishell.Shell
locations *locations.Locations
eventListener listener.Listener
updater types.Updater
ie types.ImportExporter
restarter types.Restarter
}
// New returns a new CLI frontend configured with the given options.
func New( //nolint[funlen]
panicHandler types.PanicHandler,
locations *locations.Locations,
eventListener listener.Listener,
updater types.Updater,
ie types.ImportExporter,
restarter types.Restarter,
) *frontendCLI { //nolint[golint]
fe := &frontendCLI{
Shell: ishell.New(),
locations: locations,
eventListener: eventListener,
updater: updater,
ie: ie,
restarter: restarter,
}
// Clear commands.
clearCmd := &ishell.Cmd{Name: "clear",
Help: "remove stored accounts and preferences. (alias: cl)",
Aliases: []string{"cl"},
}
clearCmd.AddCmd(&ishell.Cmd{Name: "accounts",
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
Aliases: []string{"a", "k", "keychain"},
Func: fe.deleteAccounts,
})
fe.AddCmd(clearCmd)
// Check commands.
checkCmd := &ishell.Cmd{Name: "check", Help: "check internet connection or new version."}
checkCmd.AddCmd(&ishell.Cmd{Name: "updates",
Help: "check for Import-Export updates. (aliases: u, v, version)",
Aliases: []string{"u", "version", "v"},
Func: fe.checkUpdates,
})
fe.AddCmd(checkCmd)
// Print info commands.
fe.AddCmd(&ishell.Cmd{Name: "log-dir",
Help: "print path to directory with logs. (aliases: log, logs)",
Aliases: []string{"log", "logs"},
Func: fe.printLogDir,
})
fe.AddCmd(&ishell.Cmd{Name: "manual",
Help: "print URL with instructions. (alias: man)",
Aliases: []string{"man"},
Func: fe.printManual,
})
fe.AddCmd(&ishell.Cmd{Name: "credits",
Help: "print used resources.",
Func: fe.printCredits,
})
// Account commands.
fe.AddCmd(&ishell.Cmd{Name: "list",
Help: "print the list of accounts. (aliases: l, ls)",
Func: fe.noAccountWrapper(fe.listAccounts),
Aliases: []string{"l", "ls"},
})
fe.AddCmd(&ishell.Cmd{Name: "login",
Help: "login procedure to add or connect account. Optionally use index or account as parameter. (aliases: a, add, con, connect)",
Func: fe.loginAccount,
Aliases: []string{"add", "a", "con", "connect"},
Completer: fe.completeUsernames,
})
fe.AddCmd(&ishell.Cmd{Name: "logout",
Help: "disconnect the account. Use index or account name as parameter. (aliases: d, disconnect)",
Func: fe.noAccountWrapper(fe.logoutAccount),
Aliases: []string{"d", "disconnect"},
Completer: fe.completeUsernames,
})
fe.AddCmd(&ishell.Cmd{Name: "delete",
Help: "remove the account from keychain. Use index or account name as parameter. (aliases: del, rm, remove)",
Func: fe.noAccountWrapper(fe.deleteAccount),
Aliases: []string{"del", "rm", "remove"},
Completer: fe.completeUsernames,
})
// Import-Export commands.
importCmd := &ishell.Cmd{Name: "import",
Help: "import messages. (alias: imp)",
Aliases: []string{"imp"},
}
importCmd.AddCmd(&ishell.Cmd{Name: "local",
Help: "import local messages. (aliases: loc)",
Func: fe.noAccountWrapper(fe.importLocalMessages),
Aliases: []string{"loc"},
})
importCmd.AddCmd(&ishell.Cmd{Name: "remote",
Help: "import remote messages. (aliases: rem)",
Func: fe.noAccountWrapper(fe.importRemoteMessages),
Aliases: []string{"rem"},
})
fe.AddCmd(importCmd)
exportCmd := &ishell.Cmd{Name: "export",
Help: "export messages. (alias: exp)",
Aliases: []string{"exp"},
}
exportCmd.AddCmd(&ishell.Cmd{Name: "eml",
Help: "export messages to eml files.",
Func: fe.noAccountWrapper(fe.exportMessagesToEML),
})
exportCmd.AddCmd(&ishell.Cmd{Name: "mbox",
Help: "export messages to mbox files.",
Func: fe.noAccountWrapper(fe.exportMessagesToMBOX),
})
fe.AddCmd(exportCmd)
// System commands.
fe.AddCmd(&ishell.Cmd{Name: "restart",
Help: "restart the Import-Export app.",
Func: fe.restart,
})
go func() {
defer panicHandler.HandlePanic()
fe.watchEvents()
}()
return fe
}
func (f *frontendCLI) watchEvents() {
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
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("Import-Export failed:", errorDetails)
case <-credentialsErrorCh:
f.notifyCredentialsError()
case <-internetOffCh:
f.notifyInternetOff()
case <-internetOnCh:
f.notifyInternetOn()
case address := <-addressChangedLogoutCh:
f.notifyLogout(address)
case userID := <-logoutCh:
user, err := f.ie.GetUser(userID)
if err != nil {
return
}
f.notifyLogout(user.Username())
case <-certIssue:
f.notifyCertIssue()
}
}
}
// Loop starts the frontend loop with an interactive shell.
func (f *frontendCLI) Loop() error {
f.Print(`
Welcome to ProtonMail Import-Export app interactive shell
WARNING: The CLI is an experimental feature and does not yet cover all functionality.
`)
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,232 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"fmt"
"os"
"strings"
"time"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/transfer"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) importLocalMessages(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user, path := f.getUserAndPath(c, false)
if user == nil || path == "" {
return
}
t, err := f.ie.GetLocalImporter(user.Username(), user.GetPrimaryAddress(), path)
f.transfer(t, err, false, true)
}
func (f *frontendCLI) importRemoteMessages(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c)
if user == nil {
return
}
username := f.readStringInAttempts("IMAP username", c.ReadLine, isNotEmpty)
if username == "" {
return
}
password := f.readStringInAttempts("IMAP password", c.ReadPassword, isNotEmpty)
if password == "" {
return
}
host := f.readStringInAttempts("IMAP host", c.ReadLine, isNotEmpty)
if host == "" {
return
}
port := f.readStringInAttempts("IMAP port", c.ReadLine, isNotEmpty)
if port == "" {
return
}
t, err := f.ie.GetRemoteImporter(user.Username(), user.GetPrimaryAddress(), username, password, host, port)
f.transfer(t, err, false, true)
}
func (f *frontendCLI) exportMessagesToEML(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user, path := f.getUserAndPath(c, true)
if user == nil || path == "" {
return
}
t, err := f.ie.GetEMLExporter(user.Username(), user.GetPrimaryAddress(), path)
f.transfer(t, err, true, false)
}
func (f *frontendCLI) exportMessagesToMBOX(c *ishell.Context) {
f.ShowPrompt(false)
defer f.ShowPrompt(true)
user, path := f.getUserAndPath(c, true)
if user == nil || path == "" {
return
}
t, err := f.ie.GetMBOXExporter(user.Username(), user.GetPrimaryAddress(), path)
f.transfer(t, err, true, false)
}
func (f *frontendCLI) getUserAndPath(c *ishell.Context, createPath bool) (types.User, string) {
user := f.askUserByIndexOrName(c)
if user == nil {
return nil, ""
}
path := f.readStringInAttempts("Path of EML and MBOX files", c.ReadLine, isNotEmpty)
if path == "" {
return nil, ""
}
if createPath {
_ = os.Mkdir(path, os.ModePerm)
}
return user, path
}
func (f *frontendCLI) transfer(t *transfer.Transfer, err error, askSkipEncrypted bool, askGlobalMailbox bool) {
if err != nil {
f.printAndLogError("Failed to init transferrer: ", err)
return
}
if askSkipEncrypted {
skipEncryptedMessages := f.yesNoQuestion("Skip encrypted messages")
t.SetSkipEncryptedMessages(skipEncryptedMessages)
}
if !f.setTransferRules(t) {
return
}
if askGlobalMailbox {
if err := f.setTransferGlobalMailbox(t); err != nil {
f.printAndLogError("Failed to create global mailbox: ", err)
return
}
}
progress := t.Start()
for range progress.GetUpdateChannel() {
f.printTransferProgress(progress)
}
f.printTransferResult(progress)
}
func (f *frontendCLI) setTransferGlobalMailbox(t *transfer.Transfer) error {
labelName := fmt.Sprintf("Imported %s", time.Now().Format("Jan-02-2006 15:04"))
useGlobalLabel := f.yesNoQuestion("Use global label " + labelName)
if !useGlobalLabel {
return nil
}
globalMailbox, err := t.CreateTargetMailbox(transfer.Mailbox{
Name: labelName,
Color: pmapi.LabelColors[0],
IsExclusive: false,
})
if err != nil {
return err
}
t.SetGlobalMailbox(&globalMailbox)
return nil
}
func (f *frontendCLI) setTransferRules(t *transfer.Transfer) bool {
f.Println("Rules:")
for _, rule := range t.GetRules() {
if !rule.Active {
continue
}
targets := strings.Join(rule.TargetMailboxNames(), ", ")
if rule.HasTimeLimit() {
f.Printf(" %-30s → %s (%s - %s)\n", rule.SourceMailbox.Name, targets, rule.FromDate(), rule.ToDate())
} else {
f.Printf(" %-30s → %s\n", rule.SourceMailbox.Name, targets)
}
}
return f.yesNoQuestion("Proceed")
}
func (f *frontendCLI) printTransferProgress(progress *transfer.Progress) {
counts := progress.GetCounts()
if counts.Total != 0 {
f.Println(fmt.Sprintf(
"Progress update: %d (%d / %d) / %d, skipped: %d, failed: %d",
counts.Imported,
counts.Exported,
counts.Added,
counts.Total,
counts.Skipped,
counts.Failed,
))
}
if progress.IsPaused() {
f.Printf("Transfer is paused bacause %s", progress.PauseReason())
if !f.yesNoQuestion("Continue (y) or stop (n)") {
progress.Stop()
}
}
}
func (f *frontendCLI) printTransferResult(progress *transfer.Progress) {
err := progress.GetFatalError()
if err != nil {
f.Println("Transfer failed: " + err.Error())
return
}
statuses := progress.GetFailedMessages()
if len(statuses) == 0 {
f.Println("Transfer finished!")
return
}
f.Println("Transfer finished with errors:")
for _, messageStatus := range statuses {
f.Printf(
" %-17s | %-30s | %-30s\n %s: %s\n",
messageStatus.Time.Format("Jan 02 2006 15:04"),
messageStatus.From,
messageStatus.Subject,
messageStatus.SourceID,
messageStatus.GetErrorMessage(),
)
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) restart(c *ishell.Context) {
if f.yesNoQuestion("Are you sure you want to restart the Import-Export app") {
f.Println("Restarting the Import-Export app...")
f.restarter.SetToRestart()
f.Stop()
}
}
func (f *frontendCLI) printLogDir(c *ishell.Context) {
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) {
f.Println("More instructions about the Import-Export app can be found at\n\n https://protonmail.com/support/categories/import-export/")
}

View File

@ -1,35 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"strings"
"github.com/ProtonMail/proton-bridge/internal/importexport"
"github.com/abiosoft/ishell"
)
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
f.Println("Your version is up to date.")
}
func (f *frontendCLI) printCredits(c *ishell.Context) {
for _, pkg := range strings.Split(importexport.Credits, ";") {
f.Println(pkg)
}
}

View File

@ -1,128 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package cliie
import (
"strings"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/fatih/color"
)
const (
maxInputRepeat = 2
)
var (
bold = color.New(color.Bold).SprintFunc() //nolint[gochecknoglobals]
)
func isNotEmpty(val string) bool {
return val != ""
}
func (f *frontendCLI) yesNoQuestion(question string) bool {
f.Print(question, "? yes/"+bold("no")+": ")
yes := "yes"
answer := strings.ToLower(f.ReadLine())
for i := 0; i < len(answer); i++ {
if i >= len(yes) || answer[i] != yes[i] {
return false // Everything else is false.
}
}
return len(answer) > 0 // Empty is false.
}
func (f *frontendCLI) readStringInAttempts(title string, readFunc func() string, isOK func(string) bool) (value string) {
f.Printf("%s: ", title)
value = readFunc()
title = strings.ToLower(string(title[0])) + title[1:]
for i := 0; !isOK(value); i++ {
if i >= maxInputRepeat {
f.Println("Too many attempts")
return ""
}
f.Printf("Please fill %s: ", title)
value = readFunc()
}
return
}
func (f *frontendCLI) printAndLogError(args ...interface{}) {
log.Error(args...)
f.Println(args...)
}
func (f *frontendCLI) processAPIError(err error) {
log.Warn("API error: ", err)
switch err {
case pmapi.ErrNoConnection:
f.notifyInternetOff()
case pmapi.ErrUpgradeApplication:
f.notifyNeedUpgrade()
default:
f.Println("Server error:", err.Error())
}
}
func (f *frontendCLI) notifyInternetOff() {
f.Println("Internet connection is not available.")
}
func (f *frontendCLI) notifyInternetOn() {
f.Println("Internet connection is available again.")
}
func (f *frontendCLI) notifyLogout(address string) {
f.Printf("Account %s is disconnected. Login to continue using this account with email client.", address)
}
func (f *frontendCLI) notifyNeedUpgrade() {
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() { // nolint[unused]
// Print in 80-column width.
f.Println("ProtonMail Import-Export app 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("and restart the application.")
}
func (f *frontendCLI) notifyCertIssue() {
// Print in 80-column width.
f.Println(`Connection security error: Your network connection to Proton services may
be insecure.
Description:
ProtonMail Import-Export 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
as usual.
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
(such as ProtonVPN) which encrypts your Internet connection, or use
a different network to access ProtonMail.
`)
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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
@ -88,7 +88,7 @@ func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
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)
@ -192,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,19 +1,19 @@
// Copyright (c) 2021 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
@ -31,7 +31,7 @@ import (
)
var (
log = logrus.WithField("pkg", "frontend/cli") //nolint[gochecknoglobals]
log = logrus.WithField("pkg", "frontend/cli") //nolint:gochecknoglobals
)
type frontendCLI struct {
@ -47,7 +47,7 @@ type frontendCLI struct {
}
// New returns a new CLI frontend configured with the given options.
func New( //nolint[funlen]
func New( //nolint:funlen
panicHandler types.PanicHandler,
locations *locations.Locations,
@ -56,7 +56,7 @@ func New( //nolint[funlen]
updater types.Updater,
bridge types.Bridger,
restarter types.Restarter,
) *frontendCLI { //nolint[golint]
) *frontendCLI { //nolint:revive
fe := &frontendCLI{
Shell: ishell.New(),
@ -84,6 +84,11 @@ func New( //nolint[funlen]
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.
@ -123,6 +128,24 @@ func New( //nolint[funlen]
})
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",
@ -221,8 +244,7 @@ func New( //nolint[funlen]
func (f *frontendCLI) watchEvents() {
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
@ -233,10 +255,13 @@ func (f *frontendCLI) watchEvents() {
f.Println("Bridge failed:", errorDetails)
case <-credentialsErrorCh:
f.notifyCredentialsError()
case <-internetOffCh:
f.notifyInternetOff()
case <-internetOnCh:
f.notifyInternetOn()
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:
@ -256,7 +281,7 @@ func (f *frontendCLI) watchEvents() {
// Loop starts the frontend loop with an interactive shell.
func (f *frontendCLI) Loop() error {
f.Print(`
Welcome to ProtonMail Bridge interactive shell
Welcome to Proton Mail Bridge interactive shell
___....___
^^ __..-:'':__:..:__:'':-..__
_.-:__:.-:'': : : :'':-.:__:-._

View File

@ -1,24 +1,25 @@
// Copyright (c) 2021 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"
@ -28,7 +29,7 @@ import (
)
var (
currentPort = "" //nolint[gochecknoglobals]
currentPort = "" //nolint:gochecknoglobals
)
func (f *frontendCLI) restart(c *ishell.Context) {
@ -58,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.
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
f.restarter.SetToRestart()
f.Stop()
}
@ -125,7 +129,7 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
}
func (f *frontendCLI) allowProxy(c *ishell.Context) {
if f.settings.GetBool(settings.AllowProxyKey) {
if f.bridge.GetProxyAllowed() {
f.Println("Bridge is already set to use alternative routing to connect to Proton if it is being blocked.")
return
}
@ -133,13 +137,12 @@ func (f *frontendCLI) allowProxy(c *ishell.Context) {
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.settings.SetBool(settings.AllowProxyKey, true)
f.bridge.AllowProxy()
f.bridge.SetProxyAllowed(true)
}
}
func (f *frontendCLI) disallowProxy(c *ishell.Context) {
if !f.settings.GetBool(settings.AllowProxyKey) {
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
}
@ -147,8 +150,62 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
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.settings.SetBool(settings.AllowProxyKey, false)
f.bridge.DisallowProxy()
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
}
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()
}
}
@ -168,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) 2021 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
@ -81,13 +81,7 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
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") {
needRestart, err := f.bridge.SetUpdateChannel(updater.EarlyChannel)
if err != nil {
f.Println("There was a problem switching update channel.")
}
if needRestart {
f.restarter.SetToRestart()
}
f.bridge.SetUpdateChannel(updater.EarlyChannel)
}
}
@ -101,12 +95,6 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
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") {
needRestart, err := f.bridge.SetUpdateChannel(updater.StableChannel)
if err != nil {
f.Println("There was a problem switching update channel.")
}
if needRestart {
f.restarter.SetToRestart()
}
f.bridge.SetUpdateChannel(updater.StableChannel)
}
}

View File

@ -1,19 +1,19 @@
// Copyright (c) 2021 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 {
@ -101,10 +101,10 @@ func (f *frontendCLI) notifyNeedUpgrade() {
f.Println("Please download and install the newest version of application from", version.LandingPage)
}
func (f *frontendCLI) notifyCredentialsError() { // nolint[unused]
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.")
}
@ -114,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

@ -1,23 +1,24 @@
// Copyright (c) 2021 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/>.
//go:build darwin
// +build darwin
package autoconfig
package clientconfig
import (
"io/ioutil"
@ -38,18 +39,16 @@ const (
bigSurPreferncesPane = "/System/Library/PreferencePanes/Profiles.prefPane"
)
func init() { //nolint[gochecknoinit]
available = append(available, &appleMail{})
func init() { //nolint:gochecknoinit
available[AppleMailClient] = &appleMail{}
}
type appleMail struct{}
func (c *appleMail) Name() string {
return "Apple Mail"
}
func (c *appleMail) Name() string { return AppleMailClient }
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, addressIndex int) error {
mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, addressIndex)
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 {
@ -57,27 +56,19 @@ func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, use
}
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", 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
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, addressIndex int) *mobileconfig.Config {
var addresses string
var displayName string
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(), ",")
} else {
for idx, address := range user.GetAddresses() {
if idx == addressIndex {
displayName = address
break
}
}
addresses = displayName
}
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
@ -109,10 +100,10 @@ func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
}
// Make sure the temporary file is deleted.
go (func() {
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"))

View File

@ -1,45 +1,35 @@
// Copyright (c) 2021 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/ProtonMail/go-autostart"
"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"
cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie"
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
qtie "github.com/ProtonMail/proton-bridge/internal/frontend/qt-ie"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/importexport"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/sirupsen/logrus"
)
var (
log = logrus.WithField("pkg", "frontend") // nolint[unused]
)
// Frontend is an interface to be implemented by each frontend type (cli, gui, html).
type Frontend interface {
Loop() error
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
@ -64,58 +54,11 @@ func New(
userAgent *useragent.UserAgent,
bridge *bridge.Bridge,
noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App,
restarter types.Restarter,
) Frontend {
bridgeWrap := types.NewBridgeWrap(bridge)
return newBridgeFrontend(
version,
buildVersion,
programName,
frontendType,
showWindowOnStart,
panicHandler,
locations,
settings,
eventListener,
updater,
userAgent,
bridgeWrap,
noEncConfirmator,
autostart,
restarter,
)
}
func newBridgeFrontend(
version,
buildVersion,
programName,
frontendType string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App,
restarter types.Restarter,
) Frontend {
switch frontendType {
case "cli":
return cli.New(
panicHandler,
locations,
settings,
eventListener,
updater,
bridge,
restarter,
)
default:
case "qt":
return qt.New(
version,
buildVersion,
@ -127,79 +70,21 @@ func newBridgeFrontend(
eventListener,
updater,
userAgent,
bridge,
bridgeWrap,
noEncConfirmator,
autostart,
restarter,
)
}
}
// NewImportExport returns initialized frontend based on `frontendType`, which can be `cli` or `qt`.
func NewImportExport(
version,
buildVersion,
programName,
frontendType string,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
ie *importexport.ImportExport,
restarter types.Restarter,
) Frontend {
ieWrap := types.NewImportExportWrap(ie)
return newIEFrontend(
version,
buildVersion,
programName,
frontendType,
panicHandler,
locations,
settings,
eventListener,
updater,
ieWrap,
restarter,
)
}
func newIEFrontend(
version,
buildVersion,
programName,
frontendType string,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
ie types.ImportExporter,
restarter types.Restarter,
) Frontend {
switch frontendType {
case "cli":
return cliie.New(
panicHandler,
locations,
eventListener,
updater,
ie,
restarter,
)
default:
return qtie.New(
version,
buildVersion,
programName,
return cli.New(
panicHandler,
locations,
settings,
eventListener,
updater,
ie,
bridgeWrap,
restarter,
)
default:
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) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
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) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// 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
}
}
}

View File

@ -1,49 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
Item {
Rectangle {
anchors.centerIn: parent
width: Style.main.width
height: 3*Style.main.height/4
color: "transparent"
//color: "red"
ListView {
anchors.fill: parent
clip : true
model : go.credits.split(";")
delegate: AccessibleText {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData
color: Style.main.text
font.pointSize : Style.main.fontSize * Style.pt
}
footer: ButtonRounded {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Close", "close window")
onClicked: dialogCredits.hide()
}
}
}
}

View File

@ -1,124 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Dialog with Yes/No buttons
import QtQuick 2.8
import ProtonUI 1.0
Dialog {
id: root
title : ""
isDialogBusy: false
property string firstParagraph : qsTr("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.", "instructions that appear on welcome screen at first start")
property string secondParagraph : qsTr("To add your ProtonMail account to the Bridge and <strong>generate your Bridge password</strong>, please see <a href=\"https://protonmail.com/bridge/install\">the installation guide</a> for detailed setup instructions.", "confirms and dismisses a notification (URL that leads to installation guide should stay intact)")
Column {
id: dialogMessage
property int heightInputs : welcome.height + middleSep.height + instructions.height + buttSep.height + buttonOkay.height + imageSep.height + logo.height
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/2 }
Text {
id:welcome
color: Style.main.text
font.bold: true
font.pointSize: 1.5*Style.main.fontSize*Style.pt
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to the", "welcome screen that appears on first start")
}
Rectangle {id: imageSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.dialog.spacing
Image {
id: logo
anchors.bottom : pmbridge.baseline
height : 2*Style.main.fontSize
fillMode : Image.PreserveAspectFit
mipmap : true
source : "../ProtonUI/images/pm_logo.png"
}
AccessibleText {
id:pmbridge
color: Style.main.text
font.bold: true
font.pointSize: 2.2*Style.main.fontSize*Style.pt
horizontalAlignment: Text.AlignHCenter
text: qsTr("ProtonMail Bridge", "app title")
Accessible.name: this.clearText(pmbridge.text)
Accessible.description: this.clearText(welcome.text+ " " + pmbridge.text + ". " + root.firstParagraph + ". " + root.secondParagraph)
}
}
Rectangle { id:middleSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
Text {
id:instructions
color: Style.main.text
font.pointSize: Style.main.fontSize*Style.pt
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
width: root.width/1.5
wrapMode: Text.Wrap
textFormat: Text.RichText
text: "<html><style>a { color: "+Style.main.textBlue+"; text-decoration: none;}</style><body>"+
root.firstParagraph +
"<br/><br/>"+
root.secondParagraph +
"</body></html>"
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
Rectangle { id:buttSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
ButtonRounded {
id:buttonOkay
color_main: Style.dialog.text
color_minor: Style.main.textBlue
isOpaque: true
fa_icon: Style.fa.check
text: qsTr("Okay", "confirms and dismisses a notification")
onClicked : root.hide()
anchors.horizontalCenter: parent.horizontalCenter
}
}
timer.interval : 3000
Connections {
target: timer
onTriggered: {
}
}
onShow : {
pmbridge.Accessible.selected = true
}
}

View File

@ -1,194 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Change default keychain dialog
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
import QtQuick.Controls 2.2 as QC
import QtQuick.Layouts 1.0
Dialog {
id: root
title : "Change which keychain Bridge uses as default"
subtitle : "Select which keychain is used (Bridge will automatically restart)"
isDialogBusy: currentIndex==1
property var selectedKeychain
Connections {
target: go.selectedKeychain
onValueChanged: {
console.debug("go.selectedKeychain == ", go.selectedKeychain)
}
}
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Item {
Layout.fillWidth: true
Layout.minimumHeight: root.titleHeight + Style.dialog.heightSeparator
Layout.maximumHeight: root.titleHeight + Style.dialog.heightSeparator
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
anchors.centerIn: parent
Repeater {
id: keychainRadioButtons
model: go.availableKeychain
QC.RadioButton {
id: radioDelegate
text: modelData
checked: go.selectedKeychain === modelData
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: Style.main.spacing
indicator: Text {
text : radioDelegate.checked ? Style.fa.check_circle : Style.fa.circle_o
color : radioDelegate.checked ? Style.main.textBlue : Style.main.textInactive
font {
pointSize: Style.dialog.iconSize * Style.pt
family: Style.fontawesome.name
}
}
contentItem: Text {
text: radioDelegate.text
color: Style.main.text
font {
pointSize: Style.dialog.fontSize * Style.pt
bold: checked
}
horizontalAlignment : Text.AlignHCenter
verticalAlignment : Text.AlignVCenter
leftPadding: Style.dialog.iconSize
}
onCheckedChanged: {
if (checked) {
root.selectedKeychain = modelData
}
}
}
}
Item {
Layout.fillWidth: true
Layout.minimumHeight: Style.dialog.heightSeparator
Layout.maximumHeight: Style.dialog.heightSeparator
}
Row {
id: buttonRow
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
spacing: Style.dialog.spacing
ButtonRounded {
id:buttonNo
color_main: Style.dialog.text
fa_icon: Style.fa.times
text: qsTr("Cancel", "dismisses current action")
onClicked : root.hide()
}
ButtonRounded {
id: buttonYes
color_main: Style.dialog.text
color_minor: Style.main.textBlue
isOpaque: true
fa_icon: Style.fa.check
text: qsTr("Okay", "confirms and dismisses a notification")
onClicked : root.confirmed()
}
}
}
}
}
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Item {
Layout.fillWidth: true
Layout.minimumHeight: root.titleHeight + Style.dialog.heightSeparator
Layout.maximumHeight: root.titleHeight + Style.dialog.heightSeparator
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Text {
id: answ
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width : parent.width/2
color: Style.dialog.text
font {
pointSize : Style.dialog.fontSize * Style.pt
bold : true
}
text : "Default keychain is now set to " + root.selectedKeychain +
"\n\n" +
qsTr("Settings will be applied after the next start.", "notification about setting being applied after next start") +
"\n\n" +
qsTr("Bridge will now restart.", "notification about restarting")
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: root.hide()
}
Shortcut {
sequence: "Enter"
onActivated: root.confirmed()
}
function confirmed() {
if (selectedKeychain === go.selectedKeychain) {
root.hide()
return
}
incrementCurrentIndex()
timer.start()
}
timer.interval : 5000
Connections {
target: timer
onTriggered: {
// This action triggers restart on the backend side.
go.selectedKeychain = selectedKeychain
}
}
}

View File

@ -1,233 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Dialog with Yes/No buttons
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
import QtQuick.Controls 2.2 as QC
Dialog {
id: root
title : "Set IMAP & SMTP settings"
subtitle : "Changes require reconfiguration of Mail client. (Bridge will automatically restart)"
isDialogBusy: currentIndex==1
Column {
id: dialogMessage
property int heightInputs : imapPort.height + middleSep.height + smtpPort.height + buttonSep.height + buttonRow.height + secSMTPSep.height + securitySMTP.height
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/1.6 }
InputField {
id: imapPort
iconText : Style.fa.hashtag
label : qsTr("IMAP port", "entry field to choose port used for the IMAP server")
text : "undef"
}
Rectangle { id:middleSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
InputField {
id: smtpPort
iconText : Style.fa.hashtag
label : qsTr("SMTP port", "entry field to choose port used for the SMTP server")
text : "undef"
}
Rectangle { id:secSMTPSep; color : Style.transparent; width : Style.main.dummy; height : Style.dialog.heightSeparator }
// SSL button group
Rectangle {
anchors.horizontalCenter : parent.horizontalCenter
width : Style.dialog.widthInput
height : securitySMTPLabel.height + securitySMTP.height
color : "transparent"
AccessibleText {
id: securitySMTPLabel
anchors.left : parent.left
text:qsTr("SMTP connection mode")
color: Style.dialog.text
font {
pointSize : Style.dialog.fontSize * Style.pt
bold : true
}
}
QC.ButtonGroup {
buttons: securitySMTP.children
}
Row {
id: securitySMTP
spacing: Style.dialog.spacing
anchors.top: securitySMTPLabel.bottom
anchors.topMargin: Style.dialog.fontSize
CheckBoxLabel {
id: securitySMTPSSL
text: qsTr("SSL")
}
CheckBoxLabel {
checked: true
id: securitySMTPSTARTTLS
text: qsTr("STARTTLS")
}
}
}
Rectangle { id:buttonSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
Row {
id: buttonRow
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.dialog.spacing
ButtonRounded {
id:buttonNo
color_main: Style.dialog.text
fa_icon: Style.fa.times
text: qsTr("Cancel", "dismisses current action")
onClicked : root.hide()
}
ButtonRounded {
id: buttonYes
color_main: Style.dialog.text
color_minor: Style.main.textBlue
isOpaque: true
fa_icon: Style.fa.check
text: qsTr("Okay", "confirms and dismisses a notification")
onClicked : root.confirmed()
}
}
}
Column {
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-answ.height)/2 }
Text {
id: answ
anchors.horizontalCenter: parent.horizontalCenter
width : parent.width/2
color: Style.dialog.text
font {
pointSize : Style.dialog.fontSize * Style.pt
bold : true
}
text : "IMAP: " + imapPort.text + "\nSMTP: " + smtpPort.text + "\nSMTP Connection Mode: " + getSelectedSSLMode() + "\n\n" +
qsTr("Settings will be applied after the next start. You will need to reconfigure your email client(s).", "after user changes their ports they will see this notification to reconfigure their setup") +
"\n\n" +
qsTr("Bridge will now restart.", "after user changes their ports this appears to notify the user of restart")
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
}
function areInputsOK() {
var isOK = true
var imapUnchanged = false
var secSMTPUnchanged = (securitySMTPSTARTTLS.checked == go.isSMTPSTARTTLS())
root.warning.text = ""
if (imapPort.text!=go.getIMAPPort()) {
if (go.isPortOpen(imapPort.text)!=0) {
imapPort.rightIcon = Style.fa.exclamation_triangle
root.warning.text = qsTr("Port number is not available.", "if the user changes one of their ports to a port that is occupied by another application")
isOK=false
} else {
imapPort.rightIcon = Style.fa.check_circle
}
} else {
imapPort.rightIcon = ""
imapUnchanged = true
}
if (smtpPort.text!=go.getSMTPPort()) {
if (go.isPortOpen(smtpPort.text)!=0) {
smtpPort.rightIcon = Style.fa.exclamation_triangle
root.warning.text = qsTr("Port number is not available.", "if the user changes one of their ports to a port that is occupied by another application")
isOK=false
} else {
smtpPort.rightIcon = Style.fa.check_circle
}
} else {
smtpPort.rightIcon = ""
if (imapUnchanged && secSMTPUnchanged) {
root.warning.text = qsTr("Please change at least one port number or SMTP security.", "if the user tries to change IMAP/SMTP ports to the same ports as before")
isOK=false
}
}
if (imapPort.text == smtpPort.text) {
smtpPort.rightIcon = Style.fa.exclamation_triangle
root.warning.text = qsTr("Port numbers must be different.", "if the user sets both the IMAP and SMTP ports to the same number")
isOK=false
}
root.warning.visible = !isOK
return isOK
}
function confirmed() {
if (areInputsOK()) {
incrementCurrentIndex()
timer.start()
}
}
function getSelectedSSLMode() {
if (securitySMTPSTARTTLS.checked == true) {
return "STARTTLS"
} else {
return "SSL"
}
}
onShow : {
imapPort.text = go.getIMAPPort()
smtpPort.text = go.getSMTPPort()
if (go.isSMTPSTARTTLS()) {
securitySMTPSTARTTLS.checked = true
} else {
securitySMTPSSL.checked = true
}
areInputsOK()
root.warning.visible = false
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: root.hide()
}
Shortcut {
sequence: "Enter"
onActivated: root.confirmed()
}
timer.interval : 3000
Connections {
target: timer
onTriggered: {
go.setPortsAndSecurity(imapPort.text, smtpPort.text, securitySMTPSTARTTLS.checked)
go.setToRestart()
Qt.quit()
}
}
}

View File

@ -1,77 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
Dialog {
id: root
title: qsTr("Connection security error", "Title of modal explainning TLS issue")
property string par1Title : qsTr("Description:", "Title of paragraph describing the issue")
property string par1Text : qsTr (
"ProtonMail 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.",
"A paragraph describing the issue"
)
property string par2Title : qsTr("Recommendation:", "Title of paragraph describing recommended steps")
property string par2Text : qsTr (
"If you are on a corporate or public network, the network administrator may be monitoring or intercepting all traffic.",
"A paragraph describing network issue"
)
property string par2ul1 : qsTr(
"If you trust your network operator, you can continue to use ProtonMail as usual.",
"A list item describing recomendation for trusted network"
)
property string par2ul2 : qsTr(
"If you don't trust your network operator, reconnect to ProtonMail over a VPN (such as ProtonVPN) "+
"which encrypts your Internet connection, or use a different network to access ProtonMail.",
"A list item describing recomendation for untrusted network"
)
property string par3Text : qsTr("Learn more on our knowledge base article","A paragraph describing where to find more information")
property string kbArticleText : qsTr("What is TLS certificate error.", "Link text for knowledge base article")
property string kbArticleLink : "https://protonmail.com/support/knowledge-base/"
Item {
AccessibleText {
anchors.centerIn: parent
color: Style.old.pm_white
linkColor: color
width: parent.width - 50 * Style.px
wrapMode: Text.WordWrap
font.pointSize: Style.main.fontSize*Style.pt
onLinkActivated: Qt.openUrlExternally(link)
text: "<h3>"+par1Title+"</h3>"+
par1Text+"<br>\n"+
"<h3>"+par2Title+"</h3>"+
par2Text+
"<ul>"+
"<li>"+par2ul1+"</li>"+
"<li>"+par2ul2+"</li>"+
"</ul>"+"<br>\n"+
""
//par3Text+
//" <a href='"+kbArticleLink+"'>"+kbArticleText+"</a>\n"
}
}
}

View File

@ -1,403 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Dialog with Yes/No buttons
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
Dialog {
id: root
title : ""
property string input
property alias question : msg.text
property alias note : noteText.text
property alias answer : answ.text
property alias buttonYes : buttonYes
property alias buttonNo : buttonNo
isDialogBusy: currentIndex==1
signal confirmed()
Column {
id: dialogMessage
property int heightInputs : msg.height+
middleSep.height+
buttonRow.height +
(checkboxSep.visible ? checkboxSep.height : 0 ) +
(noteSep.visible ? noteSep.height : 0 ) +
(checkBoxWrapper.visible ? checkBoxWrapper.height : 0 ) +
(root.note!="" ? noteText.height : 0 )
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/2 }
AccessibleText {
id:noteText
anchors.horizontalCenter: parent.horizontalCenter
color: Style.dialog.text
font {
pointSize: Style.dialog.fontSize * Style.pt
bold: false
}
width: 2*root.width/3
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
}
Rectangle { id: noteSep; visible: note!=""; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator}
AccessibleText {
id: msg
anchors.horizontalCenter: parent.horizontalCenter
color: Style.dialog.text
font {
pointSize: Style.dialog.fontSize * Style.pt
bold: true
}
width: 2*parent.width/3
text : ""
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
}
Rectangle { id: checkboxSep; visible: checkBoxWrapper.visible; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator}
Row {
id: checkBoxWrapper
property bool isChecked : false
visible: root.state=="deleteUser"
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.dialog.spacing
function toggle() {
checkBoxWrapper.isChecked = !checkBoxWrapper.isChecked
}
Text {
id: checkbox
font {
pointSize : Style.dialog.iconSize * Style.pt
family : Style.fontawesome.name
}
anchors.verticalCenter : parent.verticalCenter
text: checkBoxWrapper.isChecked ? Style.fa.check_square_o : Style.fa.square_o
color: checkBoxWrapper.isChecked ? Style.main.textBlue : Style.main.text
MouseArea {
anchors.fill: parent
onPressed: checkBoxWrapper.toggle()
cursorShape: Qt.PointingHandCursor
}
}
Text {
id: checkBoxNote
anchors.verticalCenter : parent.verticalCenter
text: qsTr("Additionally delete all stored preferences and data", "when removing an account, this extra preference additionally deletes all cached data")
color: Style.main.text
font.pointSize: Style.dialog.fontSize * Style.pt
MouseArea {
anchors.fill: parent
onPressed: checkBoxWrapper.toggle()
cursorShape: Qt.PointingHandCursor
Accessible.role: Accessible.CheckBox
Accessible.checked: checkBoxWrapper.isChecked
Accessible.name: checkBoxNote.text
Accessible.description: checkBoxNote.text
Accessible.ignored: checkBoxNote.text == ""
Accessible.onToggleAction: checkBoxWrapper.toggle()
Accessible.onPressAction: checkBoxWrapper.toggle()
}
}
}
Rectangle { id: middleSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
Row {
id: buttonRow
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.dialog.spacing
ButtonRounded {
id:buttonNo
visible: root.state != "toggleEarlyAccess"
color_main: Style.dialog.text
fa_icon: Style.fa.times
text: qsTr("No")
onClicked : root.hide()
}
ButtonRounded {
id: buttonYes
color_main: Style.dialog.text
color_minor: Style.main.textBlue
isOpaque: true
fa_icon: Style.fa.check
text: root.state == "toggleEarlyAccess" ? qsTr("Ok") : qsTr("Yes")
onClicked : {
currentIndex=1
root.confirmed()
}
}
}
}
Column {
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-answ.height)/2 }
AccessibleText {
id: answ
anchors.horizontalCenter: parent.horizontalCenter
color: Style.old.pm_white
font {
pointSize : Style.dialog.fontSize * Style.pt
bold : true
}
width: 3*parent.width/4
horizontalAlignment: Text.AlignHCenter
text : qsTr("Waiting...", "in general this displays between screens when processing data takes a long time")
wrapMode: Text.Wrap
}
}
states : [
State {
name: "quit"
PropertyChanges {
target: root
currentIndex : 0
title : qsTr("Close Bridge", "quits the application")
question : qsTr("Are you sure you want to close the Bridge?", "asked when user tries to quit the application")
note : ""
answer : qsTr("Closing Bridge...", "displayed when user is quitting application")
}
},
State {
name: "logout"
PropertyChanges {
target: root
currentIndex : 1
title : qsTr("Logout", "title of page that displays during account logout")
question : ""
note : ""
answer : qsTr("Logging out...", "displays during account logout")
}
},
State {
name: "deleteUser"
PropertyChanges {
target: root
currentIndex : 0
title : qsTr("Delete account", "title of page that displays during account deletion")
question : qsTr("Are you sure you want to remove this account?", "displays during account deletion")
note : ""
answer : qsTr("Deleting ...", "displays during account deletion")
}
},
State {
name: "clearChain"
PropertyChanges {
target : root
currentIndex : 0
title : qsTr("Clear keychain", "title of page that displays during keychain clearing")
question : qsTr("Are you sure you want to clear your keychain?", "displays during keychain clearing")
note : qsTr("This will remove all accounts that you have added to the Bridge and disconnect you from your email client(s).", "displays during keychain clearing")
answer : qsTr("Clearing the keychain ...", "displays during keychain clearing")
}
},
State {
name: "clearCache"
PropertyChanges {
target: root
currentIndex : 0
title : qsTr("Clear cache", "title of page that displays during cache clearing")
question : qsTr("Are you sure you want to clear your local cache?", "displays during cache clearing")
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.", "displays during cache clearing")
answer : qsTr("Clearing the cache ...", "displays during cache clearing")
}
},
State {
name: "checkUpdates"
PropertyChanges {
target: root
currentIndex : 1
title : ""
question : ""
note : ""
answer : qsTr("Checking for updates ...", "displays if user clicks the Check for Updates button in the Help tab")
}
},
State {
name: "addressmode"
PropertyChanges {
target: root
currentIndex : 0
title : ""
question : qsTr("Do you want to continue?", "asked when the user changes between split and combined address mode")
note : qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.", "displayed when the user changes between split and combined address mode")
answer : qsTr("Configuring address mode...", "displayed when the user changes between split and combined address mode")
}
},
State {
name: "toggleAutoStart"
PropertyChanges {
target: root
currentIndex : 1
question : ""
note : ""
title : ""
answer : {
var msgTurnOn = qsTr("Turning on automatic start of Bridge...", "when the automatic start feature is selected")
var msgTurnOff = qsTr("Turning off automatic start of Bridge...", "when the automatic start feature is deselected")
return go.isAutoStart==false ? msgTurnOff : msgTurnOn
}
}
},
State {
name: "toggleAllowProxy"
PropertyChanges {
target: root
currentIndex : 0
question : {
var questionTurnOn = qsTr("Do you want to allow alternative routing?")
var questionTurnOff = qsTr("Do you want to disallow alternative routing?")
return go.isProxyAllowed==false ? questionTurnOn : questionTurnOff
}
note : qsTr("In case Proton sites are blocked, this setting allows Bridge to try alternative network routing to reach Proton, which can be useful for bypassing firewalls or network issues. We recommend keeping this setting on for greater reliability.")
title : {
var titleTurnOn = qsTr("Allow alternative routing")
var titleTurnOff = qsTr("Disallow alternative routing")
return go.isProxyAllowed==false ? titleTurnOn : titleTurnOff
}
answer : {
var msgTurnOn = qsTr("Allowing Bridge to use alternative routing to connect to Proton...", "when the allow proxy feature is selected")
var msgTurnOff = qsTr("Disallowing Bridge to use alternative routing to connect to Proton...", "when the allow proxy feature is deselected")
return go.isProxyAllowed==false ? msgTurnOn : msgTurnOff
}
}
},
State {
name: "toggleEarlyAccessOn"
PropertyChanges {
target: root
currentIndex : 0
question : qsTr("Do you want to be the first to get the latest updates? Please keep in mind that early versions may be less stable.")
note : ""
title : qsTr("Enable early access")
answer : qsTr("Enabling early access...")
}
},
State {
name: "toggleEarlyAccessOff"
PropertyChanges {
target: root
currentIndex : 0
question : qsTr("Are you sure you want to leave early access? Please keep in mind this operation clears the cache and restarts Bridge.")
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.")
title : qsTr("Disable early access")
answer : qsTr("Disabling early access...")
}
},
State {
name: "noKeychain"
PropertyChanges {
target: root
currentIndex : 0
note : qsTr(
"%1 is not able to detected a supported password manager (pass, gnome-keyring). Please install and setup supported password manager and restart the application.",
"Error message when no keychain is detected"
).arg(go.programTitle)
question : qsTr("Do you want to close application now?", "when no password manager found." )
title : "No system password manager detected"
answer : qsTr("Closing Bridge...", "displayed when user is quitting application")
}
},
State {
name: "undef";
PropertyChanges {
target: root
currentIndex : 1
question : ""
note : ""
title : ""
answer : ""
}
}
]
Shortcut {
sequence: StandardKey.Cancel
onActivated: root.hide()
}
Shortcut {
sequence: "Enter"
onActivated: root.confirmed()
}
onHide: {
checkBoxWrapper.isChecked = false
state = "undef"
}
onShow: {
// hide all other dialogs
winMain.dialogAddUser .visible = false
winMain.dialogChangePort .visible = false
winMain.dialogCredits .visible = false
root.visible = true
}
onConfirmed : {
if (state == "quit" || state == "instance exists" ) {
timer.interval = 1000
} else {
timer.interval = 300
}
answ.forceActiveFocus()
timer.start()
}
Connections {
target: timer
onTriggered: {
if ( state == "addressmode" ) { go.switchAddressMode (input) }
if ( state == "clearChain" ) { go.clearKeychain () }
if ( state == "clearCache" ) { go.clearCache () }
if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) }
if ( state == "logout" ) { go.logoutAccount (input) }
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () }
if ( state == "toggleEarlyAccessOn" ) { go.toggleEarlyAccess () }
if ( state == "toggleEarlyAccessOff" ) { go.toggleEarlyAccess () }
if ( state == "quit" ) { Qt.quit () }
if ( state == "instance exists" ) { Qt.quit () }
if ( state == "noKeychain" ) { Qt.quit () }
if ( state == "checkUpdates" ) { }
}
}
Keys.onPressed: {
if (event.key == Qt.Key_Enter) {
root.confirmed()
}
}
}

View File

@ -1,143 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// List the settings
import QtQuick 2.8
import BridgeUI 1.0
import ProtonUI 1.0
Item {
id: root
// must have wrapper
Rectangle {
id: wrapper
anchors.centerIn: parent
width: parent.width
height: parent.height
color: Style.main.background
// content
Column {
anchors.horizontalCenter : parent.horizontalCenter
ButtonIconText {
id: logs
anchors.left: parent.left
text: qsTr("Logs", "title of button that takes user to logs directory")
leftIcon.text : Style.fa.align_justify
rightIcon.text : Style.fa.chevron_circle_right
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
onClicked: go.openLogs()
}
ButtonIconText {
id: bugreport
anchors.left: parent.left
text: qsTr("Report Bug", "title of button that takes user to bug report form")
leftIcon.text : Style.fa.bug
rightIcon.text : Style.fa.chevron_circle_right
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
onClicked: bugreportWin.show()
}
ButtonIconText {
id: manual
anchors.left: parent.left
text: qsTr("Setup Guide", "title of button that opens setup and installation guide")
leftIcon.text : Style.fa.book
rightIcon.text : Style.fa.chevron_circle_right
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
onClicked: go.openManual()
}
ButtonIconText {
id: updates
anchors.left: parent.left
text: qsTr("Check for Updates", "title of button to check for any app updates")
leftIcon.text : Style.fa.refresh
rightIcon.text : Style.fa.chevron_circle_right
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
onClicked: {
go.checkForUpdates()
}
}
// Bottom version notes
Rectangle {
anchors.horizontalCenter : parent.horizontalCenter
height: viewAccount.separatorNoAccount - 3.2*manual.height
width: wrapper.width
color : "transparent"
AccessibleText {
anchors {
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
color: Style.main.textDisabled
horizontalAlignment: Qt.AlignHCenter
font.pointSize : Style.main.fontSize * Style.pt
text:
"ProtonMail Bridge "+go.getBackendVersion()+"\n"+
"© 2021 Proton Technologies AG"
}
}
Row {
anchors.left : parent.left
Rectangle { height: Style.dialog.spacing; width: (wrapper.width - credits.width - licenseFile.width - release.width - sepaCreditsRelease.width)/2; color: "transparent"}
ClickIconText {
id:credits
iconText : ""
text : qsTr("Credits", "link to click on to view list of credited libraries")
textColor : Style.main.textDisabled
fontSize : Style.main.fontSize
textUnderline : true
onClicked : winMain.dialogCredits.show()
}
Rectangle {id: sepaLicenseFile ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
ClickIconText {
id:licenseFile
iconText : ""
text : qsTr("License", "link to click on to view license file")
textColor : Style.main.textDisabled
fontSize : Style.main.fontSize
textUnderline : true
onClicked : {
go.openLicenseFile()
}
}
Rectangle {id: sepaCreditsRelease ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
ClickIconText {
id:release
iconText : ""
text : qsTr("Release notes", "link to click on to view release notes for this version of the app")
textColor : Style.main.textDisabled
fontSize : Style.main.fontSize
textUnderline : true
onClicked : gui.openReleaseNotes()
}
}
}
}
}

View File

@ -1,144 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Window for imap and smtp settings
import QtQuick 2.8
import QtQuick.Window 2.2
import BridgeUI 1.0
import ProtonUI 1.0
Window {
id:root
width : Style.info.width
height : Style.info.height
minimumWidth : Style.info.width
minimumHeight : Style.info.height
maximumWidth : Style.info.width
maximumHeight : Style.info.height
color: "transparent"
flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint
title : address
Accessible.role: Accessible.Window
Accessible.name: qsTr("Configuration information for %1").arg(address)
Accessible.description: Accessible.name
property QtObject accData : QtObject { // avoid null-pointer error
property string account : "undef"
property string aliases : "undef"
property string hostname : "undef"
property string password : "undef"
property int portIMAP : 0
property int portSMTP : 0
}
property string address : "undef"
property int indexAccount : 0
property int indexAddress : 0
WindowTitleBar {
id: titleBar
window: root
}
Rectangle { // background
color: Style.main.background
anchors {
left : parent.left
right : parent.right
top : titleBar.bottom
bottom : parent.bottom
}
border {
width: Style.main.border
color: Style.tabbar.background
}
}
// info content
Column {
anchors {
left: parent.left
top: titleBar.bottom
leftMargin: Style.main.leftMargin
topMargin: Style.info.topMargin
}
width : root.width - Style.main.leftMargin - Style.main.rightMargin
TextLabel { text: qsTr("IMAP SETTINGS", "title of the portion of the configuration screen that contains IMAP settings"); state: "heading" }
Rectangle { width: parent.width; height: Style.info.topMargin; color: "#00000000"}
Grid {
columns: 2
rowSpacing: Style.main.fontSize
TextLabel { text: qsTr("Hostname", "in configuration screen, displays the server hostname (127.0.0.1)") + ":"} TextValue { text: root.accData.hostname }
TextLabel { text: qsTr("Port", "in configuration screen, displays the server port (ex. 1025)") + ":"} TextValue { text: root.accData.portIMAP }
TextLabel { text: qsTr("Username", "in configuration screen, displays the username to use with the desktop client") + ":"} TextValue { text: root.address }
TextLabel { text: qsTr("Password", "in configuration screen, displays the Bridge password to use with the desktop client") + ":"} TextValue { text: root.accData.password }
TextLabel { text: qsTr("Security", "in configuration screen, displays the IMAP security settings") + ":"} TextValue { text: "STARTTLS" }
}
Rectangle { width: Style.main.dummy; height: Style.main.fontSize; color: "#00000000"}
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
TextLabel { text: qsTr("SMTP SETTINGS", "title of the portion of the configuration screen that contains SMTP settings"); state: "heading" }
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
Grid {
columns: 2
rowSpacing: Style.main.fontSize
TextLabel { text: qsTr("Hostname", "in configuration screen, displays the server hostname (127.0.0.1)") + ":"} TextValue { text: root.accData.hostname }
TextLabel { text: qsTr("Port", "in configuration screen, displays the server port (ex. 1025)") + ":"} TextValue { text: root.accData.portSMTP }
TextLabel { text: qsTr("Username", "in configuration screen, displays the username to use with the desktop client") + ":"} TextValue { text: root.address }
TextLabel { text: qsTr("Password", "in configuration screen, displays the Bridge password to use with the desktop client") + ":"} TextValue { text: root.accData.password }
TextLabel { text: qsTr("Security", "in configuration screen, displays the SMTP security settings") + ":"} TextValue { text: go.isSMTPSTARTTLS() ? "STARTTLS" : "SSL" }
}
Rectangle { width: Style.main.dummy; height: Style.main.fontSize; color: "#00000000"}
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
}
// apple mail button
ButtonRounded{
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: Style.info.topMargin
}
color_main : Style.main.textBlue
isOpaque: false
text: qsTr("Configure Apple Mail", "button on configuration screen to automatically configure Apple Mail")
height: Style.main.fontSize*2
width: 2*parent.width/3
onClicked: {
go.configureAppleMail(root.indexAccount, root.indexAddress)
}
visible: go.goos == "darwin"
}
function showInfo(iAccount, iAddress) {
root.indexAccount = iAccount
root.indexAddress = iAddress
root.accData = accountsModel.get(iAccount)
root.address = accData.aliases.split(";")[iAddress]
root.show()
root.raise()
root.requestActivate()
}
function hide() {
root.visible = false
}
}

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