chore: merge release/Quebec into devel
This commit is contained in:
commit
3ebd2f53f4
|
@ -6,9 +6,12 @@
|
|||
* Go 1.18
|
||||
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
||||
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||
* GCC (linux), msvc (windows) or Xcode (macOS)
|
||||
* Windres (windows)
|
||||
* libglvnd and libsecret development files (linux)
|
||||
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
||||
* Windres (Windows)
|
||||
* libglvnd and libsecret development files (Linux)
|
||||
* pkg-config (Linux)
|
||||
* cmake, ninja-build and Qt 6 are required to build the graphical user interface. On Linux,
|
||||
the Mesa OpenGL development files are also needed.
|
||||
|
||||
To enable the sending of crash reports using Sentry please set the
|
||||
`DSN_SENTRY` environment variable with the client key of your sentry project before build.
|
||||
|
|
20
Changelog.md
20
Changelog.md
|
@ -2,13 +2,31 @@
|
|||
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Bridge 3.2.0] Rialto
|
||||
|
||||
### Changed
|
||||
* GODT-2502: Improve logs.
|
||||
* GODT-2551: Store and Recover Last User Agent from Vault.
|
||||
* GODT-2550: Verify IMAP ID is set properly.
|
||||
* GODT-2554: Compute telemetry availability from API UserSettings.
|
||||
* Add missing double quotes in test.
|
||||
* GODT-2239: Unit tests for BridgeUtils.cpp in bridgepp.
|
||||
* Replace go-rfc5322 with gluon's rfc5322 parser.
|
||||
* GODT-2483: Install cert without external tool on macOS.
|
||||
|
||||
### Fixed
|
||||
* GODT-2550: Announce IMAP ID Capability.
|
||||
* GODT-2574: Fix label/unlabel of large amounts of messages.
|
||||
* GODT-2573: Handle invalid header fields in message.
|
||||
* GODT-2573: Crash on null update.
|
||||
* GODT-2407: Replace invalid email addresses with emtpy for new Drafts.
|
||||
|
||||
|
||||
## [Bridge 3.1.2] Quebec
|
||||
|
||||
### Changed
|
||||
* GODT-2582 Dedup recovered messages folder.
|
||||
|
||||
|
||||
## [Bridge 3.1.1] Quebec
|
||||
|
||||
### Fixed
|
||||
|
|
2
Makefile
2
Makefile
|
@ -256,6 +256,8 @@ mocks:
|
|||
mockgen --package mocks github.com/ProtonMail/gluon/async PanicHandler > internal/bridge/mocks/async_mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/gluon/reporter Reporter > internal/bridge/mocks/gluon_mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/updater Downloader,Installer > internal/updater/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/v3/internal/telemetry HeartbeatManager > internal/telemetry/mocks/mocks.go
|
||||
cp internal/telemetry/mocks/mocks.go internal/bridge/mocks/telemetry_mocks.go
|
||||
|
||||
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog
|
||||
|
||||
|
|
20
go.mod
20
go.mod
|
@ -5,10 +5,10 @@ go 1.18
|
|||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.15.1-0.20230425092028-a5ce20d22175
|
||||
github.com/ProtonMail/gluon v0.16.1-0.20230425073628-8ec759b512f1
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230406143739-c7596e170799
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.5.2
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230426081144-f77778bae1be
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
|
||||
|
@ -43,9 +43,9 @@ require (
|
|||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/sys v0.5.0
|
||||
golang.org/x/text v0.7.0
|
||||
golang.org/x/net v0.8.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/text v0.8.0
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
howett.net/plist v1.0.0
|
||||
|
@ -55,8 +55,8 @@ require (
|
|||
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb // indirect
|
||||
entgo.io/ent v0.11.8 // indirect
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230322105811-d73448b7e800 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
github.com/ProtonMail/go-srp v0.0.5 // indirect
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
|
@ -116,10 +116,10 @@ require (
|
|||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/zclconf/go-cty v1.12.1 // indirect
|
||||
golang.org/x/arch v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
43
go.sum
43
go.sum
|
@ -28,24 +28,23 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
|
|||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||
github.com/ProtonMail/gluon v0.15.1-0.20230425092028-a5ce20d22175 h1:gVTIzUcXhIyv10rAy1t+6dvMOLdEhYoF+vwXp29Wtrg=
|
||||
github.com/ProtonMail/gluon v0.15.1-0.20230425092028-a5ce20d22175/go.mod h1:yA4hk6CJw0BMo+YL8Y3ckCYs5L20sysu9xseshwY3QI=
|
||||
github.com/ProtonMail/gluon v0.16.1-0.20230425073628-8ec759b512f1 h1:6YTRwn95hnTymOz5g+MbikfJXYE4xGJkIgTLm0t/1w8=
|
||||
github.com/ProtonMail/gluon v0.16.1-0.20230425073628-8ec759b512f1/go.mod h1:yA4hk6CJw0BMo+YL8Y3ckCYs5L20sysu9xseshwY3QI=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230124153114-0acdc8ae009b/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230322105811-d73448b7e800 h1:o8/VQLSiuRkkSAfVOpFCG1GnTsWxFIOPLvJ2O7hJcFg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230322105811-d73448b7e800/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
|
||||
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 h1:dS7r5z4iGS0qCjM7UwWdsEMzQesUQbGcXdSm2/tWboA=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230406143739-c7596e170799 h1:slk4Drrkij1EVTnFOlIDyJsfjt69tnw8w2g1NMb253U=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230406143739-c7596e170799/go.mod h1:kis4GD6FHp1ZWnenSBepldt8ai+vYalDPeey9yGwyXk=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230426081144-f77778bae1be h1:TNHnEyUQDf97CRGCFWLxg7I5ASSEMO3TN2lbNw2cD6U=
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230426081144-f77778bae1be/go.mod h1:UkrG9gN2o9mzdx/an0XRc6a4s5Haef1A7Eyd2iXlw28=
|
||||
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
|
||||
github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.5.2 h1:97SjlWNAxXl9P22lgwgrZRshQdiEfAht0g3ZoiA1GCw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.5.2/go.mod h1:52qDaCnto6r+CoWbuU50T77XQt99lIs46HtHtvgFO3o=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton/go.mod h1:S1lYsaGHykYpxxh2SnJL6ypcAlANKj5NRSY6HxKryKQ=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
||||
|
@ -433,12 +432,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
|
||||
|
@ -454,7 +452,6 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
|
@ -481,8 +478,10 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
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=
|
||||
|
@ -493,7 +492,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -525,11 +523,13 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
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=
|
||||
|
@ -537,8 +537,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -562,8 +563,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 h1:/X0t/E4VxbZE7MLS7auvE7YICHeVvbIa9vkOVvYW/24=
|
||||
golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -35,6 +36,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||
|
@ -244,6 +246,15 @@ func run(c *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"lastVersion": v.GetLastVersion().String(),
|
||||
"showAllMail": v.GetShowAllMail(),
|
||||
"updateCh": v.GetUpdateChannel(),
|
||||
"autoUpdate": v.GetAutoUpdate(),
|
||||
"rollout": v.GetUpdateRollout(),
|
||||
"DoH": v.GetProxyAllowed(),
|
||||
}).Info("Vault loaded")
|
||||
|
||||
// Load the cookies from the vault.
|
||||
return withCookieJar(v, func(cookieJar http.CookieJar) error {
|
||||
// Create a new bridge instance.
|
||||
|
@ -258,6 +269,9 @@ func run(c *cli.Context) error {
|
|||
b.PushError(bridge.ErrVaultCorrupt)
|
||||
}
|
||||
|
||||
// Start telemetry heartbeat process
|
||||
b.StartHeartbeat(b)
|
||||
|
||||
// Run the frontend.
|
||||
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
|
||||
})
|
||||
|
@ -417,6 +431,10 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
|||
return fmt.Errorf("could not create cookie jar: %w", err)
|
||||
}
|
||||
|
||||
if err := setDeviceCookies(persister); err != nil {
|
||||
return fmt.Errorf("could not set device cookies: %w", err)
|
||||
}
|
||||
|
||||
// Persist the cookies to the vault when we close.
|
||||
defer func() {
|
||||
logrus.Debug("Persisting cookies")
|
||||
|
@ -428,3 +446,21 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
|||
|
||||
return fn(persister)
|
||||
}
|
||||
|
||||
func setDeviceCookies(jar *cookies.Jar) error {
|
||||
url, err := url.Parse(constants.APIHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for name, value := range map[string]string{
|
||||
"hhn": sentry.GetProtectedHostname(),
|
||||
"tz": sentry.GetTimeZone(),
|
||||
"lng": sentry.GetSystemLang(),
|
||||
"clr": string(theme.DefaultTheme()),
|
||||
} {
|
||||
jar.SetCookies(url, []*http.Cookie{{Name: name, Value: value, Secure: true}})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package bridge
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
|
@ -36,6 +37,14 @@ func newAPIOptions(
|
|||
transport http.RoundTripper,
|
||||
panicHandler async.PanicHandler,
|
||||
) []proton.Option {
|
||||
|
||||
if allow := os.Getenv("BRIDGE_ALLOW_PROXY"); allow != "" {
|
||||
transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
|
||||
opt := defaultAPIOptions(apiURL, version, cookieJar, transport, panicHandler)
|
||||
|
||||
if host := os.Getenv("BRIDGE_API_HOST"); host != "" {
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
|
@ -78,6 +79,9 @@ type Bridge struct {
|
|||
updater Updater
|
||||
installCh chan installJob
|
||||
|
||||
// heartbeat is the telemetry heartbeat for metrics.
|
||||
heartbeat telemetry.Heartbeat
|
||||
|
||||
// curVersion is the current version of the bridge,
|
||||
// newVersion is the version that was installed by the updater.
|
||||
curVersion *semver.Version
|
||||
|
@ -126,6 +130,9 @@ type Bridge struct {
|
|||
// goUpdate triggers a check/install of updates.
|
||||
goUpdate func()
|
||||
|
||||
// goHeartbeat triggers a check/sending if heartbeat is needed.
|
||||
goHeartbeat func()
|
||||
|
||||
uidValidityGenerator imap.UIDValidityGenerator
|
||||
}
|
||||
|
||||
|
@ -237,6 +244,8 @@ func newBridge(
|
|||
return nil, fmt.Errorf("failed to save last version indicator: %w", err)
|
||||
}
|
||||
|
||||
identifier.SetClientString(vault.GetLastUserAgent())
|
||||
|
||||
imapServer, err := newIMAPServer(
|
||||
gluonCacheDir,
|
||||
gluonDataDir,
|
||||
|
|
|
@ -49,6 +49,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/tests"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
imapid "github.com/emersion/go-imap-id"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -170,6 +171,92 @@ func TestBridge_UserAgent(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestBridge_UserAgent_Persistence(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
currentUserAgent := b.GetCurrentUserAgent()
|
||||
require.Contains(t, currentUserAgent, vault.DefaultUserAgent)
|
||||
|
||||
imapClient, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = imapClient.Logout() }()
|
||||
|
||||
idClient := imapid.NewClient(imapClient)
|
||||
|
||||
// Set IMAP ID before Login to have the value capture in the Login API Call.
|
||||
_, err = idClient.ID(imapid.ID{
|
||||
imapid.FieldName: "MyFancyClient",
|
||||
imapid.FieldVersion: "0.1.2",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Login the user.
|
||||
_, err = b.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert that the user agent then contains the platform.
|
||||
require.Contains(t, b.GetCurrentUserAgent(), "MyFancyClient/0.1.2")
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
currentUserAgent := bridge.GetCurrentUserAgent()
|
||||
require.Contains(t, currentUserAgent, "MyFancyClient/0.1.2")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_UserAgentFromIMAPID(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var (
|
||||
calls []server.Call
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
s.AddCallWatcher(func(call server.Call) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
calls = append(calls, call)
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
imapClient, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = imapClient.Logout() }()
|
||||
|
||||
idClient := imapid.NewClient(imapClient)
|
||||
|
||||
// Set IMAP ID before Login to have the value capture in the Login API Call.
|
||||
_, err = idClient.ID(imapid.ID{
|
||||
imapid.FieldName: "MyFancyClient",
|
||||
imapid.FieldVersion: "0.1.2",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Login the user.
|
||||
userID, err := b.LoginFull(context.Background(), username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := b.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, info.State == bridge.Connected)
|
||||
|
||||
require.NoError(t, imapClient.Login(info.Addresses[0], string(info.BridgePass)))
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
userAgent := calls[len(calls)-1].RequestHeader.Get("User-Agent")
|
||||
|
||||
// Assert that the user agent was sent to the API.
|
||||
require.Contains(t, userAgent, b.GetCurrentUserAgent())
|
||||
require.Contains(t, userAgent, "MyFancyClient/0.1.2")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_Cookies(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
var (
|
||||
|
@ -736,6 +823,9 @@ func withBridgeNoMocks(
|
|||
require.NoError(t, err)
|
||||
require.Empty(t, bridge.GetErrors())
|
||||
|
||||
// Start the Heartbeat process.
|
||||
bridge.StartHeartbeat(mocks.Heartbeat)
|
||||
|
||||
// Wait for bridge to finish loading users.
|
||||
waitForEvent(t, eventCh, events.AllUsersLoaded{})
|
||||
// Wait for bridge to start the IMAP server.
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const HeartbeatCheckInterval = time.Hour
|
||||
|
||||
func (bridge *Bridge) IsTelemetryAvailable() bool {
|
||||
var flag = true
|
||||
if bridge.GetTelemetryDisabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
flag = flag && user.IsTelemetryEnabled(context.Background())
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
return flag
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SendHeartbeat(heartbeat *telemetry.HeartbeatData) bool {
|
||||
data, err := json.Marshal(heartbeat)
|
||||
if err != nil {
|
||||
if err := bridge.reporter.ReportMessageWithContext("Cannot parse heartbeat data.", reporter.Context{
|
||||
"error": err,
|
||||
}); err != nil {
|
||||
logrus.WithError(err).Error("Failed to parse heartbeat data.")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var sent = false
|
||||
|
||||
safe.RLock(func() {
|
||||
for _, user := range bridge.users {
|
||||
if err := user.SendTelemetry(context.Background(), data); err == nil {
|
||||
sent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
|
||||
return sent
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetLastHeartbeatSent() time.Time {
|
||||
return bridge.vault.GetLastHeartbeatSent()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetLastHeartbeatSent(timestamp time.Time) error {
|
||||
return bridge.vault.SetLastHeartbeatSent(timestamp)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) StartHeartbeat(manager telemetry.HeartbeatManager) {
|
||||
bridge.heartbeat = telemetry.NewHeartbeat(manager, 1143, 1025, bridge.GetGluonCacheDir(), keychain.DefaultHelper)
|
||||
|
||||
// Check for heartbeat when triggered.
|
||||
bridge.goHeartbeat = bridge.tasks.PeriodicOrTrigger(HeartbeatCheckInterval, 0, func(ctx context.Context) {
|
||||
logrus.Debug("Checking for heartbeat")
|
||||
|
||||
bridge.heartbeat.TrySending()
|
||||
})
|
||||
|
||||
bridge.heartbeat.SetRollout(bridge.GetUpdateRollout())
|
||||
bridge.heartbeat.SetAutoStart(bridge.GetAutostart())
|
||||
bridge.heartbeat.SetAutoUpdate(bridge.GetAutoUpdate())
|
||||
bridge.heartbeat.SetBeta(bridge.GetUpdateChannel())
|
||||
bridge.heartbeat.SetDoh(bridge.GetProxyAllowed())
|
||||
bridge.heartbeat.SetShowAllMail(bridge.GetShowAllMail())
|
||||
bridge.heartbeat.SetIMAPConnectionMode(bridge.GetIMAPSSL())
|
||||
bridge.heartbeat.SetSMTPConnectionMode(bridge.GetSMTPSSL())
|
||||
bridge.heartbeat.SetIMAPPort(bridge.GetIMAPPort())
|
||||
bridge.heartbeat.SetSMTPPort(bridge.GetSMTPPort())
|
||||
bridge.heartbeat.SetCacheLocation(bridge.GetGluonCacheDir())
|
||||
if val, err := bridge.GetKeychainApp(); err != nil {
|
||||
bridge.heartbeat.SetKeyChainPref(val)
|
||||
} else {
|
||||
bridge.heartbeat.SetKeyChainPref(keychain.DefaultHelper)
|
||||
}
|
||||
bridge.heartbeat.SetPrevVersion(bridge.GetLastVersion().String())
|
||||
|
||||
safe.RLock(func() {
|
||||
var splitMode = false
|
||||
for _, user := range bridge.users {
|
||||
if user.GetAddressMode() == vault.SplitMode {
|
||||
splitMode = true
|
||||
break
|
||||
}
|
||||
}
|
||||
var nbAccount = len(bridge.users)
|
||||
bridge.heartbeat.SetNbAccount(nbAccount)
|
||||
bridge.heartbeat.SetSplitMode(splitMode)
|
||||
|
||||
// Do not try to send if there is no user yet.
|
||||
if nbAccount > 0 {
|
||||
defer bridge.goHeartbeat()
|
||||
}
|
||||
}, bridge.usersLock)
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package bridge
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||
return bridge.identifier.GetUserAgent()
|
||||
}
|
||||
|
@ -24,3 +26,17 @@ func (bridge *Bridge) GetCurrentUserAgent() string {
|
|||
func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
||||
bridge.identifier.SetPlatform(platform)
|
||||
}
|
||||
|
||||
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||
currentUserAgent := bridge.identifier.GetClientString()
|
||||
|
||||
bridge.identifier.SetClient(name, version)
|
||||
|
||||
newUserAgent := bridge.identifier.GetClientString()
|
||||
|
||||
if currentUserAgent != newUserAgent {
|
||||
if err := bridge.vault.SetLastUserAgent(newUserAgent); err != nil {
|
||||
logrus.WithError(err).Error("Failed to write new user agent to vault")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,18 +41,16 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultClientName = "UnknownClient"
|
||||
defaultClientVersion = "0.0.1"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) serveIMAP() error {
|
||||
port, err := func() (int, error) {
|
||||
if bridge.imapServer == nil {
|
||||
return 0, fmt.Errorf("no IMAP server instance running")
|
||||
}
|
||||
|
||||
logrus.Info("Starting IMAP server")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"port": bridge.vault.GetIMAPPort(),
|
||||
"ssl": bridge.vault.GetIMAPSSL(),
|
||||
}).Info("Starting IMAP server")
|
||||
|
||||
imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig)
|
||||
if err != nil {
|
||||
|
@ -249,11 +247,6 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
|||
}).Info("Received mailbox message count")
|
||||
}
|
||||
|
||||
case imapEvents.SessionAdded:
|
||||
if !bridge.identifier.HasClient() {
|
||||
bridge.identifier.SetClient(defaultClientName, defaultClientVersion)
|
||||
}
|
||||
|
||||
case imapEvents.IMAPID:
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"sessionID": event.SessionID,
|
||||
|
@ -262,7 +255,7 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
|||
}).Info("Received IMAP ID")
|
||||
|
||||
if event.IMAPID.Name != "" && event.IMAPID.Version != "" {
|
||||
bridge.identifier.SetClient(event.IMAPID.Name, event.IMAPID.Version)
|
||||
bridge.setUserAgent(event.IMAPID.Name, event.IMAPID.Version)
|
||||
}
|
||||
|
||||
case imapEvents.LoginFailed:
|
||||
|
|
|
@ -24,6 +24,7 @@ type Mocks struct {
|
|||
|
||||
CrashHandler *mocks.MockPanicHandler
|
||||
Reporter *mocks.MockReporter
|
||||
Heartbeat *mocks.MockHeartbeatManager
|
||||
}
|
||||
|
||||
func NewMocks(tb testing.TB, version, minAuto *semver.Version) *Mocks {
|
||||
|
@ -39,14 +40,18 @@ func NewMocks(tb testing.TB, version, minAuto *semver.Version) *Mocks {
|
|||
|
||||
CrashHandler: mocks.NewMockPanicHandler(ctl),
|
||||
Reporter: mocks.NewMockReporter(ctl),
|
||||
Heartbeat: mocks.NewMockHeartbeatManager(ctl),
|
||||
}
|
||||
|
||||
// When getting the TLS issue channel, we want to return the test channel.
|
||||
mocks.TLSReporter.EXPECT().GetTLSIssueCh().Return(mocks.TLSIssueCh).AnyTimes()
|
||||
|
||||
// This is called at he end of any go-routine:
|
||||
// This is called at the end of any go-routine:
|
||||
mocks.CrashHandler.EXPECT().HandlePanic(gomock.Any()).AnyTimes()
|
||||
|
||||
// this is called at start of heartbeat process.
|
||||
mocks.Heartbeat.EXPECT().IsTelemetryAvailable().AnyTimes()
|
||||
|
||||
return mocks
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/proton-bridge/v3/internal/telemetry (interfaces: HeartbeatManager)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
telemetry "github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockHeartbeatManager is a mock of HeartbeatManager interface.
|
||||
type MockHeartbeatManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHeartbeatManagerMockRecorder
|
||||
}
|
||||
|
||||
// MockHeartbeatManagerMockRecorder is the mock recorder for MockHeartbeatManager.
|
||||
type MockHeartbeatManagerMockRecorder struct {
|
||||
mock *MockHeartbeatManager
|
||||
}
|
||||
|
||||
// NewMockHeartbeatManager creates a new mock instance.
|
||||
func NewMockHeartbeatManager(ctrl *gomock.Controller) *MockHeartbeatManager {
|
||||
mock := &MockHeartbeatManager{ctrl: ctrl}
|
||||
mock.recorder = &MockHeartbeatManagerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockHeartbeatManager) EXPECT() *MockHeartbeatManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetLastHeartbeatSent mocks base method.
|
||||
func (m *MockHeartbeatManager) GetLastHeartbeatSent() time.Time {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLastHeartbeatSent")
|
||||
ret0, _ := ret[0].(time.Time)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetLastHeartbeatSent indicates an expected call of GetLastHeartbeatSent.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) GetLastHeartbeatSent() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).GetLastHeartbeatSent))
|
||||
}
|
||||
|
||||
// IsTelemetryAvailable mocks base method.
|
||||
func (m *MockHeartbeatManager) IsTelemetryAvailable() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsTelemetryAvailable")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsTelemetryAvailable indicates an expected call of IsTelemetryAvailable.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) IsTelemetryAvailable() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsTelemetryAvailable", reflect.TypeOf((*MockHeartbeatManager)(nil).IsTelemetryAvailable))
|
||||
}
|
||||
|
||||
// SendHeartbeat mocks base method.
|
||||
func (m *MockHeartbeatManager) SendHeartbeat(arg0 *telemetry.HeartbeatData) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendHeartbeat", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendHeartbeat indicates an expected call of SendHeartbeat.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) SendHeartbeat(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendHeartbeat", reflect.TypeOf((*MockHeartbeatManager)(nil).SendHeartbeat), arg0)
|
||||
}
|
||||
|
||||
// SetLastHeartbeatSent mocks base method.
|
||||
func (m *MockHeartbeatManager) SetLastHeartbeatSent(arg0 time.Time) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLastHeartbeatSent", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetLastHeartbeatSent indicates an expected call of SetLastHeartbeatSent.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) SetLastHeartbeatSent(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).SetLastHeartbeatSent), arg0)
|
||||
}
|
|
@ -46,6 +46,8 @@ func (bridge *Bridge) SetKeychainApp(helper string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetKeyChainPref(helper)
|
||||
|
||||
return vault.SetHelper(vaultDir, helper)
|
||||
}
|
||||
|
||||
|
@ -62,6 +64,8 @@ func (bridge *Bridge) SetIMAPPort(newPort int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetIMAPPort(newPort)
|
||||
|
||||
return bridge.restartIMAP()
|
||||
}
|
||||
|
||||
|
@ -78,6 +82,8 @@ func (bridge *Bridge) SetIMAPSSL(newSSL bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetIMAPConnectionMode(newSSL)
|
||||
|
||||
return bridge.restartIMAP()
|
||||
}
|
||||
|
||||
|
@ -94,6 +100,8 @@ func (bridge *Bridge) SetSMTPPort(newPort int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetSMTPPort(newPort)
|
||||
|
||||
return bridge.restartSMTP()
|
||||
}
|
||||
|
||||
|
@ -110,6 +118,8 @@ func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetSMTPConnectionMode(newSSL)
|
||||
|
||||
return bridge.restartSMTP()
|
||||
}
|
||||
|
||||
|
@ -141,6 +151,8 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
|
|||
}
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetCacheLocation(newGluonDir)
|
||||
|
||||
gluonDataDir, err := bridge.GetGluonDataDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get Gluon Database directory: %w", err)
|
||||
|
@ -207,6 +219,8 @@ func (bridge *Bridge) SetProxyAllowed(allowed bool) error {
|
|||
bridge.proxyCtl.DisallowProxy()
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetDoh(allowed)
|
||||
|
||||
return bridge.vault.SetProxyAllowed(allowed)
|
||||
}
|
||||
|
||||
|
@ -220,6 +234,8 @@ func (bridge *Bridge) SetShowAllMail(show bool) error {
|
|||
user.SetShowAllMail(show)
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetShowAllMail(show)
|
||||
|
||||
return bridge.vault.SetShowAllMail(show)
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
@ -233,6 +249,8 @@ func (bridge *Bridge) SetAutostart(autostart bool) error {
|
|||
if err := bridge.vault.SetAutostart(autostart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetAutoStart(autostart)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -253,6 +271,10 @@ func (bridge *Bridge) SetAutostart(autostart bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetUpdateRollout() float64 {
|
||||
return bridge.vault.GetUpdateRollout()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAutoUpdate() bool {
|
||||
return bridge.vault.GetAutoUpdate()
|
||||
}
|
||||
|
@ -266,11 +288,28 @@ func (bridge *Bridge) SetAutoUpdate(autoUpdate bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetAutoUpdate(autoUpdate)
|
||||
|
||||
bridge.goUpdate()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetTelemetryDisabled() bool {
|
||||
return bridge.vault.GetTelemetryDisabled()
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetTelemetryDisabled(isDisabled bool) error {
|
||||
if err := bridge.vault.SetTelemetryDisabled(isDisabled); err != nil {
|
||||
return err
|
||||
}
|
||||
// If telemetry is re-enabled locally, try to send the heartbeat.
|
||||
if !isDisabled {
|
||||
defer bridge.goHeartbeat()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetUpdateChannel() updater.Channel {
|
||||
return bridge.vault.GetUpdateChannel()
|
||||
}
|
||||
|
@ -284,6 +323,8 @@ func (bridge *Bridge) SetUpdateChannel(channel updater.Channel) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetBeta(channel)
|
||||
|
||||
bridge.goUpdate()
|
||||
|
||||
return nil
|
||||
|
|
|
@ -33,7 +33,10 @@ import (
|
|||
|
||||
func (bridge *Bridge) serveSMTP() error {
|
||||
port, err := func() (int, error) {
|
||||
logrus.Info("Starting SMTP server")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"port": bridge.vault.GetSMTPPort(),
|
||||
"ssl": bridge.vault.GetSMTPSSL(),
|
||||
}).Info("Starting SMTP server")
|
||||
|
||||
smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig)
|
||||
if err != nil {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type smtpBackend struct {
|
||||
|
@ -85,7 +86,7 @@ func (s *smtpSession) Rcpt(to string) error {
|
|||
}
|
||||
|
||||
func (s *smtpSession) Data(r io.Reader) error {
|
||||
return safe.RLockRet(func() error {
|
||||
err := safe.RLockRet(func() error {
|
||||
user, ok := s.users[s.userID]
|
||||
if !ok {
|
||||
return ErrNoSuchUser
|
||||
|
@ -93,4 +94,10 @@ func (s *smtpSession) Data(r io.Reader) error {
|
|||
|
||||
return user.SendMail(s.authID, s.from, s.to, r)
|
||||
}, s.usersLock)
|
||||
|
||||
if err != nil {
|
||||
logrus.WithField("pkg", "smtp").WithError(err).Error("Send mail failed.")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ type Identifier interface {
|
|||
HasClient() bool
|
||||
SetClient(name, version string)
|
||||
SetPlatform(platform string)
|
||||
SetClientString(client string)
|
||||
GetClientString() string
|
||||
}
|
||||
|
||||
type ProxyController interface {
|
||||
|
|
|
@ -295,6 +295,15 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
|
|||
AddressMode: mode,
|
||||
})
|
||||
|
||||
var splitMode = false
|
||||
for _, user := range bridge.users {
|
||||
if user.GetAddressMode() == vault.SplitMode {
|
||||
splitMode = true
|
||||
break
|
||||
}
|
||||
}
|
||||
bridge.heartbeat.SetSplitMode(splitMode)
|
||||
|
||||
return nil
|
||||
}, bridge.usersLock)
|
||||
}
|
||||
|
@ -399,7 +408,7 @@ func (bridge *Bridge) loadUsers(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
log.Info("Loading connected user")
|
||||
log.WithField("mode", user.AddressMode()).Info("Loading connected user")
|
||||
|
||||
bridge.publish(events.UserLoading{
|
||||
UserID: user.UserID(),
|
||||
|
@ -550,7 +559,7 @@ func (bridge *Bridge) addUserWithVault(
|
|||
// As such, if we find this ID in the context, we should use it to update our user agent.
|
||||
client.AddPreRequestHook(func(_ *resty.Client, r *resty.Request) error {
|
||||
if imapID, ok := imap.GetIMAPIDFromContext(r.Context()); ok {
|
||||
bridge.identifier.SetClient(imapID.Name, imapID.Version)
|
||||
bridge.setUserAgent(imapID.Name, imapID.Version)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -559,8 +568,12 @@ func (bridge *Bridge) addUserWithVault(
|
|||
// Finally, save the user in the bridge.
|
||||
safe.Lock(func() {
|
||||
bridge.users[apiUser.ID] = user
|
||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||
}, bridge.usersLock)
|
||||
|
||||
// As we need at least one user to send heartbeat, try to send it.
|
||||
defer bridge.goHeartbeat()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -614,6 +627,8 @@ func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI,
|
|||
logrus.WithError(err).Error("Failed to logout user")
|
||||
}
|
||||
|
||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||
|
||||
user.Close()
|
||||
}
|
||||
|
||||
|
|
|
@ -451,6 +451,65 @@ func TestBridge_User_DropConn_NoBadEvent(t *testing.T) {
|
|||
}, server.WithListener(dropListener))
|
||||
}
|
||||
|
||||
func TestBridge_User_UpdateDraft(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
// Create a bridge user.
|
||||
_, _, err := s.CreateUser("user", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Initially sync the user.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||
})
|
||||
|
||||
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||
user, err := c.GetUser(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
addrs, err := c.GetAddresses(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
salts, err := c.GetSalts(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyPass, err := salts.SaltForKey(password, user.Keys.Primary().ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, addrKRs, err := proton.Unlock(user, addrs, keyPass, async.NoopPanicHandler{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a draft (generating a "create draft message" event).
|
||||
draft, err := c.CreateDraft(ctx, addrKRs[addrs[0].ID], proton.CreateDraftReq{
|
||||
Message: proton.DraftTemplate{
|
||||
Subject: "subject",
|
||||
Sender: &mail.Address{Name: "sender", Address: addrs[0].Email},
|
||||
Body: "body",
|
||||
MIMEType: rfc822.TextPlain,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, draft.ReplyTos)
|
||||
|
||||
// Process those events
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
userContinueEventProcess(ctx, t, s, bridge)
|
||||
})
|
||||
|
||||
// Update the draft (generating an "update draft message" event).
|
||||
draft2, err := c.UpdateDraft(ctx, draft.ID, addrKRs[addrs[0].ID], proton.UpdateDraftReq{
|
||||
Message: proton.DraftTemplate{
|
||||
Subject: "subject 2",
|
||||
Sender: &mail.Address{Name: "sender", Address: addrs[0].Email},
|
||||
Body: "body 2",
|
||||
MIMEType: rfc822.TextPlain,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, draft2.ReplyTos)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
// Create a bridge user.
|
||||
|
@ -784,6 +843,48 @@ func TestBridge_User_HandleParentLabelRename(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// TBD: GODT-2527.
|
||||
func _TestBridge503DuringEventDoesNotCauseBadEvent(t *testing.T) { //nolint:unused,deadcode
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||
// Create a user.
|
||||
userID, addrID, err := s.CreateUser("user", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create 10 messages for the user.
|
||||
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||
|
||||
var messageIDs []string
|
||||
|
||||
// Create 10 more messages for the user, generating events.
|
||||
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||
})
|
||||
|
||||
mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).MinTimes(1)
|
||||
|
||||
s.AddStatusHook(func(req *http.Request) (int, bool) {
|
||||
if xslices.Index(xslices.Map(messageIDs[0:5], func(messageID string) string {
|
||||
return "/mail/v4/messages/" + messageID
|
||||
}), req.URL.Path) < 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return http.StatusServiceUnavailable, true
|
||||
})
|
||||
|
||||
userContinueEventProcess(ctx, t, s, bridge)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// userLoginAndSync logs in user and waits until user is fully synced.
|
||||
func userLoginAndSync(
|
||||
ctx context.Context,
|
||||
|
|
|
@ -17,69 +17,141 @@
|
|||
|
||||
package certs
|
||||
|
||||
import (
|
||||
"os"
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Security
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Security/Security.h>
|
||||
|
||||
"golang.org/x/sys/execabs"
|
||||
|
||||
int installTrustedCert(char const *bytes, unsigned long long length) {
|
||||
if (length == 0) {
|
||||
return errSecInvalidData;
|
||||
}
|
||||
|
||||
NSData *der = [NSData dataWithBytes:bytes length:length];
|
||||
|
||||
// Step 1. Import the certificate in the keychain.
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der);
|
||||
NSDictionary* addQuery = @{
|
||||
(id)kSecValueRef: (__bridge id) cert,
|
||||
(id)kSecClass: (id)kSecClassCertificate,
|
||||
};
|
||||
|
||||
OSStatus status = SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL);
|
||||
if ((errSecSuccess != status) && (errSecDuplicateItem != status)) {
|
||||
CFRelease(cert);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Step 2. Set the trust for the certificate.
|
||||
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL
|
||||
NSDictionary *trustSettings = @{
|
||||
(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot],
|
||||
(id)kSecTrustSettingsPolicy: (__bridge id) policy,
|
||||
};
|
||||
status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainAdmin, (__bridge CFTypeRef)(trustSettings));
|
||||
CFRelease(policy);
|
||||
CFRelease(cert);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int removeTrustedCert(char const *bytes, unsigned long long length) {
|
||||
if (0 == length) {
|
||||
return errSecInvalidData;
|
||||
}
|
||||
|
||||
NSData *der = [NSData dataWithBytes: bytes length: length];
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der);
|
||||
|
||||
// Step 1. Unset the trust for the certificate.
|
||||
SecPolicyRef policy = SecPolicyCreateSSL(true, NULL);
|
||||
NSDictionary * trustSettings = @{
|
||||
(id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultUnspecified],
|
||||
(id)kSecTrustSettingsPolicy: (__bridge id) policy,
|
||||
};
|
||||
OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainAdmin, (__bridge CFTypeRef)(trustSettings));
|
||||
CFRelease(policy);
|
||||
if (errSecSuccess != status) {
|
||||
CFRelease(cert);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Step 2. Remove the certificate from the keychain.
|
||||
NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate,
|
||||
(id)kSecMatchItemList: @[(__bridge id)cert],
|
||||
(id)kSecMatchLimit: (id)kSecMatchLimitOne,
|
||||
};
|
||||
status = SecItemDelete((__bridge CFDictionaryRef) query);
|
||||
|
||||
CFRelease(cert);
|
||||
return status;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// some of the error codes returned by Apple's Security framework.
|
||||
const (
|
||||
errSecSuccess = 0
|
||||
errAuthorizationCanceled = -60006
|
||||
)
|
||||
|
||||
// certPEMToDER converts a certificate in PEM format to DER format, which is the format required by Apple's Security framework.
|
||||
func certPEMToDER(certPEM []byte) ([]byte, error) {
|
||||
|
||||
block, left := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return []byte{}, errors.New("invalid PEM certificate")
|
||||
}
|
||||
|
||||
if len(left) > 0 {
|
||||
return []byte{}, errors.New("trailing data found at the end of a PEM certificate")
|
||||
}
|
||||
|
||||
return block.Bytes, nil
|
||||
}
|
||||
|
||||
func installCert(certPEM []byte) error {
|
||||
name, err := writeToTempFile(certPEM)
|
||||
certDER, err := certPEMToDER(certPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addTrustedCert(name)
|
||||
p := C.CBytes(certDER)
|
||||
defer C.free(unsafe.Pointer(p))
|
||||
|
||||
errCode := C.installTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER)))
|
||||
switch errCode {
|
||||
case errSecSuccess:
|
||||
return nil
|
||||
case errAuthorizationCanceled:
|
||||
return fmt.Errorf("the user cancelled the authorization dialog")
|
||||
default:
|
||||
return fmt.Errorf("could not install certification into keychain (error %v)", errCode)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallCert(certPEM []byte) error {
|
||||
name, err := writeToTempFile(certPEM)
|
||||
certDER, err := certPEMToDER(certPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return removeTrustedCert(name)
|
||||
}
|
||||
p := C.CBytes(certDER)
|
||||
defer C.free(unsafe.Pointer(p))
|
||||
|
||||
func addTrustedCert(certPath string) error {
|
||||
return execabs.Command( //nolint:gosec
|
||||
"/usr/bin/security",
|
||||
"execute-with-privileges",
|
||||
"/usr/bin/security",
|
||||
"add-trusted-cert",
|
||||
"-d",
|
||||
"-r", "trustRoot",
|
||||
"-p", "ssl",
|
||||
"-k", "/Library/Keychains/System.keychain",
|
||||
certPath,
|
||||
).Run()
|
||||
}
|
||||
|
||||
func removeTrustedCert(certPath string) error {
|
||||
return execabs.Command( //nolint:gosec
|
||||
"/usr/bin/security",
|
||||
"execute-with-privileges",
|
||||
"/usr/bin/security",
|
||||
"remove-trusted-cert",
|
||||
"-d",
|
||||
certPath,
|
||||
).Run()
|
||||
}
|
||||
|
||||
// writeToTempFile writes the given data to a temporary file and returns the path.
|
||||
func writeToTempFile(data []byte) (string, error) {
|
||||
f, err := os.CreateTemp("", "tls")
|
||||
if err != nil {
|
||||
return "", err
|
||||
if errCode := C.removeTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))); errCode != 0 {
|
||||
return fmt.Errorf("could not install certificate from keychain (error %v)", errCode)
|
||||
}
|
||||
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return f.Name(), nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package certs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// This test implies human interactions to enter password and is disabled by default.
|
||||
func _TestTrustedCertsDarwin(t *testing.T) {
|
||||
template, err := NewTLSTemplate()
|
||||
require.NoError(t, err)
|
||||
|
||||
certPEM, _, err := GenerateCert(template)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Error(t, installCert([]byte{0})) // Cannot install an invalid cert.
|
||||
require.Error(t, uninstallCert(certPEM)) // Cannot uninstall a cert that is not installed.
|
||||
require.NoError(t, installCert(certPEM)) // Can install a valid cert.
|
||||
require.NoError(t, installCert(certPEM)) // Can install an already installed cert.
|
||||
require.NoError(t, uninstallCert(certPEM)) // Can uninstall an installed cert.
|
||||
require.Error(t, uninstallCert(certPEM)) // Cannot uninstall an already uninstalled cert.
|
||||
require.NoError(t, installCert(certPEM)) // Can reinstall an uninstalled cert.
|
||||
require.NoError(t, uninstallCert(certPEM)) // Can uninstall a reinstalled cert.
|
||||
}
|
|
@ -39,6 +39,7 @@ void GRPCQtProxy::connectSignals() {
|
|||
connect(this, &GRPCQtProxy::setIsAutostartOnReceived, &settingsTab, &SettingsTab::setIsAutostartOn);
|
||||
connect(this, &GRPCQtProxy::setIsBetaEnabledReceived, &settingsTab, &SettingsTab::setIsBetaEnabled);
|
||||
connect(this, &GRPCQtProxy::setIsAllMailVisibleReceived, &settingsTab, &SettingsTab::setIsAllMailVisible);
|
||||
connect(this, &GRPCQtProxy::setIsTelemetryDisabledReceived, &settingsTab, &SettingsTab::setIsTelemetryDisabled);
|
||||
connect(this, &GRPCQtProxy::setColorSchemeNameReceived, &settingsTab, &SettingsTab::setColorSchemeName);
|
||||
connect(this, &GRPCQtProxy::reportBugReceived, &settingsTab, &SettingsTab::setBugReport);
|
||||
connect(this, &GRPCQtProxy::exportTLSCertificatesReceived, &settingsTab, &SettingsTab::exportTLSCertificates);
|
||||
|
@ -89,6 +90,13 @@ void GRPCQtProxy::setIsAllMailVisible(bool visible) {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] isDisabled Is telemetry disabled?
|
||||
//****************************************************************************************************************************************************
|
||||
void GRPCQtProxy::setIsTelemetryDisabled(bool isDisabled) {
|
||||
emit setIsTelemetryDisabledReceived(isDisabled);
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] name The color scheme.
|
||||
//****************************************************************************************************************************************************
|
||||
|
|
|
@ -41,6 +41,7 @@ public: // member functions.
|
|||
void setIsAutostartOn(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal.
|
||||
void setIsBetaEnabled(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal.
|
||||
void setIsAllMailVisible(bool visible); ///< Forwards a SetIsAllMailVisible call via a Qt signal.
|
||||
void setIsTelemetryDisabled(bool isDisabled); ///< Forwards a SetIsTelemetryDisabled call via a Qt signal.
|
||||
void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal
|
||||
void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
|
||||
QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal.
|
||||
|
@ -62,6 +63,7 @@ signals:
|
|||
void setIsAutostartOnReceived(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal.
|
||||
void setIsBetaEnabledReceived(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal.
|
||||
void setIsAllMailVisibleReceived(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal.
|
||||
void setIsTelemetryDisabledReceived(bool isDisabled); ///< Forwards a SetIsTelemetryDisabled call via a Qt signal.
|
||||
void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal
|
||||
void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
|
||||
QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call
|
||||
|
|
|
@ -192,6 +192,28 @@ Status GRPCService::IsAllMailVisible(ServerContext *, Empty const *request, Bool
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] request The request.
|
||||
/// \return The status for the call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCService::SetIsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *) {
|
||||
app().log().debug(__FUNCTION__);
|
||||
qtProxy_.setIsTelemetryDisabledReceived(request->value());
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[out] response The response.
|
||||
/// \return The status for the call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCService::IsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::BoolValue *response) {
|
||||
app().log().debug(__FUNCTION__);
|
||||
response->set_value(app().mainWindow().settingsTab().isTelemetryDisabled());
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The status for the call.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -820,3 +842,4 @@ void GRPCService::finishLogin() {
|
|||
|
||||
qtProxy_.sendDelayedEvent(newLoginFinishedEvent(user->id(), alreadyExist));
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ public: // member functions.
|
|||
grpc::Status IsBetaEnabled(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::BoolValue *response) override;
|
||||
grpc::Status SetIsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override;
|
||||
grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override;
|
||||
grpc::Status SetIsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override;
|
||||
grpc::Status IsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override;
|
||||
grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
|
||||
grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
|
||||
grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
|
||||
|
|
|
@ -202,6 +202,22 @@ void SettingsTab::setIsAllMailVisible(bool visible) {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return the value for the 'Disabled Telemetry' check.
|
||||
//****************************************************************************************************************************************************
|
||||
bool SettingsTab::isTelemetryDisabled() const {
|
||||
return ui_.checkIsTelemetryDisabled->isChecked();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] isDisabled The new value for the 'Disable Telemetry' check box.
|
||||
//****************************************************************************************************************************************************
|
||||
void SettingsTab::setIsTelemetryDisabled(bool isDisabled) {
|
||||
ui_.checkIsTelemetryDisabled->setChecked(isDisabled);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The delay to apply before sending automatically generated events.
|
||||
//****************************************************************************************************************************************************
|
||||
|
|
|
@ -45,6 +45,7 @@ public: // member functions.
|
|||
bool isAutostartOn() const; ///< Get the value for the 'Autostart' check.
|
||||
bool isBetaEnabled() const; ///< Get the value for the 'Beta Enabled' check.
|
||||
bool isAllMailVisible() const; ///< Get the value for the 'All Mail Visible' check.
|
||||
bool isTelemetryDisabled() const; ///< Get the value for the 'Disable Telemetry' check box.
|
||||
QString colorSchemeName() const; ///< Get the value of the 'Use Dark Theme' checkbox.
|
||||
qint32 eventDelayMs() const; ///< Get the delay for sending automatically generated events.
|
||||
QString logsPath() const; ///< Get the content of the 'Logs Path' edit.
|
||||
|
@ -74,6 +75,7 @@ public slots:
|
|||
void setIsAutostartOn(bool on); ///< Set the value for the 'Autostart' check box.
|
||||
void setIsBetaEnabled(bool enabled); ///< Set the value for the 'Beta Enabled' check box.
|
||||
void setIsAllMailVisible(bool visible); ///< Set the value for the 'All Mail Visible' check box.
|
||||
void setIsTelemetryDisabled(bool isDisabled); ///< Set the value for the 'Disable Telemetry' check box.
|
||||
void setColorSchemeName(QString const &name); ///< Set the value for the 'Use Dark Theme' check box.
|
||||
void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description,
|
||||
bool includeLogs); ///< Set the content of the bug report box.
|
||||
|
|
|
@ -170,6 +170,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkIsTelemetryDisabled">
|
||||
<property name="text">
|
||||
<string>Disable Telemetry</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -114,6 +114,7 @@ add_executable(bridge-gui
|
|||
EventStreamWorker.cpp EventStreamWorker.h
|
||||
LogUtils.cpp LogUtils.h
|
||||
main.cpp
|
||||
TrayIcon.cpp TrayIcon.h
|
||||
Pch.h
|
||||
QMLBackend.cpp QMLBackend.h
|
||||
UserList.cpp UserList.h
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
#include <bridgepp/Log/LogUtils.h>
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
#define HANDLE_EXCEPTION(x) try { x } \
|
||||
|
@ -50,6 +49,9 @@ QMLBackend::QMLBackend()
|
|||
/// \param[in] serviceConfig
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||
trayIcon_.reset(new TrayIcon());
|
||||
this->setNormalTrayIcon();
|
||||
|
||||
connect(this, &QMLBackend::fatalError, &app(), &AppController::onFatalError);
|
||||
|
||||
users_ = new UserList(this);
|
||||
|
@ -100,6 +102,14 @@ bool QMLBackend::waitForEventStreamReaderToFinish(qint32 timeoutMs) {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The list of users
|
||||
//****************************************************************************************************************************************************
|
||||
UserList const &QMLBackend::users() const {
|
||||
return *users_;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The build year as a string (e.g. 2023)
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -335,6 +345,18 @@ bool QMLBackend::isAllMailVisible() const {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'isAllMailVisible' property.
|
||||
//****************************************************************************************************************************************************
|
||||
bool QMLBackend::isTelemetryDisabled() const {
|
||||
HANDLE_EXCEPTION_RETURN_BOOL(
|
||||
bool v;
|
||||
app().grpc().isTelemetryDisabled(v);
|
||||
return v;
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'colorSchemeName' property.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -569,6 +591,17 @@ void QMLBackend::changeIsAllMailVisible(bool isVisible) {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] isDisabled The new state of the 'Is telemetry disabled property'.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::toggleIsTelemetryDisabled(bool isDisabled) {
|
||||
HANDLE_EXCEPTION(
|
||||
app().grpc().setIsTelemetryDisabled(isDisabled);
|
||||
emit isTelemetryDisabledChanged(isDisabled);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] scheme the scheme name
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -838,6 +871,49 @@ void QMLBackend::sendBadEventUserFeedback(QString const &userID, bool doResync)
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::setNormalTrayIcon() {
|
||||
if (trayIcon_) {
|
||||
trayIcon_->setState(TrayIcon::State::Normal, tr("Connected"), ":/qml/icons/ic-connected.svg");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] stateString A string describing the state.
|
||||
/// \param[in] statusIcon The path of the status icon.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::setErrorTrayIcon(QString const &stateString, QString const &statusIcon) {
|
||||
if (trayIcon_) {
|
||||
trayIcon_->setState(TrayIcon::State::Error, stateString, statusIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] stateString A string describing the state.
|
||||
/// \param[in] statusIcon The path of the status icon.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::setWarnTrayIcon(QString const &stateString, QString const &statusIcon) {
|
||||
if (trayIcon_) {
|
||||
trayIcon_->setState(TrayIcon::State::Warn, stateString, statusIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] stateString A string describing the state.
|
||||
/// \param[in] statusIcon The path of the status icon.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::setUpdateTrayIcon(QString const &stateString, QString const &statusIcon) {
|
||||
if (trayIcon_) {
|
||||
trayIcon_->setState(TrayIcon::State::Update, stateString, statusIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] imapPort The IMAP port.
|
||||
/// \param[in] smtpPort The SMTP port.
|
||||
|
@ -892,7 +968,7 @@ void QMLBackend::onLoginAlreadyLoggedIn(QString const &userID) {
|
|||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::onUserBadEvent(QString const &userID, QString const& ) {
|
||||
void QMLBackend::onUserBadEvent(QString const &userID, QString const &) {
|
||||
HANDLE_EXCEPTION(
|
||||
if (badEventDisplayQueue_.contains(userID)) {
|
||||
app().log().error("Received 'bad event' for a user that is already in the queue.");
|
||||
|
@ -921,8 +997,9 @@ void QMLBackend::onIMAPLoginFailed(QString const &username) {
|
|||
if ((!user) || (user->state() != UserState::SignedOut)) { // We want to pop-up only if a signed-out user has been detected
|
||||
return;
|
||||
}
|
||||
if (user->isInIMAPLoginFailureCooldown())
|
||||
if (user->isInIMAPLoginFailureCooldown()) {
|
||||
return;
|
||||
}
|
||||
user->startImapLoginFailureCooldown(60 * 60 * 1000); // 1 hour cooldown during which we will not display this notification to this user again.
|
||||
emit selectUser(user->id());
|
||||
emit imapLoginWhileSignedOut(username);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "MacOS/DockIcon.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "TrayIcon.h"
|
||||
#include "UserList.h"
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/GRPC/GRPCUtils.h>
|
||||
|
@ -43,6 +44,7 @@ public: // member functions.
|
|||
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
|
||||
void init(GRPCConfig const &serviceConfig); ///< Initialize the backend.
|
||||
bool waitForEventStreamReaderToFinish(qint32 timeoutMs); ///< Wait for the event stream reader to finish.
|
||||
UserList const& users() const; ///< Return the list of users
|
||||
|
||||
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
|
||||
Q_INVOKABLE static QString buildYear(); ///< Return the application build year.
|
||||
|
@ -67,6 +69,7 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
|||
Q_PROPERTY(bool isAutostartOn READ isAutostartOn NOTIFY isAutostartOnChanged)
|
||||
Q_PROPERTY(bool isBetaEnabled READ isBetaEnabled NOTIFY isBetaEnabledChanged)
|
||||
Q_PROPERTY(bool isAllMailVisible READ isAllMailVisible NOTIFY isAllMailVisibleChanged)
|
||||
Q_PROPERTY(bool isTelemetryDisabled READ isTelemetryDisabled NOTIFY isTelemetryDisabledChanged)
|
||||
Q_PROPERTY(QString colorSchemeName READ colorSchemeName NOTIFY colorSchemeNameChanged)
|
||||
Q_PROPERTY(QUrl diskCachePath READ diskCachePath NOTIFY diskCachePathChanged)
|
||||
Q_PROPERTY(bool useSSLForIMAP READ useSSLForIMAP WRITE setUseSSLForIMAP NOTIFY useSSLForIMAPChanged)
|
||||
|
@ -99,6 +102,7 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
|||
bool isAutostartOn() const; ///< Getter for the 'isAutostartOn' property.
|
||||
bool isBetaEnabled() const; ///< Getter for the 'isBetaEnabled' property.
|
||||
bool isAllMailVisible() const; ///< Getter for the 'isAllMailVisible' property.
|
||||
bool isTelemetryDisabled() const; ///< Getter for the 'isTelemetryDisabled' property.
|
||||
QString colorSchemeName() const; ///< Getter for the 'colorSchemeName' property.
|
||||
QUrl diskCachePath() const; ///< Getter for the 'diskCachePath' property.
|
||||
void setUseSSLForIMAP(bool value); ///< Setter for the 'useSSLForIMAP' property.
|
||||
|
@ -129,6 +133,7 @@ signals: // Signal used by the Qt property system. Many of them are unused but r
|
|||
void isAutomaticUpdateOnChanged(bool value); ///<Signal for the change of the 'isAutomaticUpdateOn' property.
|
||||
void isBetaEnabledChanged(bool value); ///<Signal for the change of the 'isBetaEnabled' property.
|
||||
void isAllMailVisibleChanged(bool value); ///<Signal for the change of the 'isAllMailVisible' property.
|
||||
void isTelemetryDisabledChanged(bool isDisabled); ///<Signal for the change of the 'isTelemetryDisabled' property.
|
||||
void colorSchemeNameChanged(QString const &scheme); ///<Signal for the change of the 'colorSchemeName' property.
|
||||
void isDoHEnabledChanged(bool value); ///<Signal for the change of the 'isDoHEnabled' property.
|
||||
void logsPathChanged(QUrl const &path); ///<Signal for the change of the 'logsPath' property.
|
||||
|
@ -151,6 +156,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
|||
void toggleAutostart(bool active); ///< Slot for the autostart toggle.
|
||||
void toggleBeta(bool active); ///< Slot for the beta toggle.
|
||||
void changeIsAllMailVisible(bool isVisible); ///< Slot for the changing of 'All Mail' visibility.
|
||||
void toggleIsTelemetryDisabled(bool isDisabled); ///< Slot for toggling telemetry on/off.
|
||||
void changeColorScheme(QString const &scheme); ///< Slot for the change of the theme.
|
||||
void setDiskCachePath(QUrl const &path) const; ///< Slot for the change of the disk cache path.
|
||||
void login(QString const &username, QString const &password) const; ///< Slot for the login button (initial login).
|
||||
|
@ -174,6 +180,10 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
|||
void onVersionChanged(); ///< Slot for the version change signal.
|
||||
void setMailServerSettings(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Forwards a connection mode change request from QML to gRPC
|
||||
void sendBadEventUserFeedback(QString const &userID, bool doResync); ///< Slot the providing user feedback for a bad event.
|
||||
void setNormalTrayIcon(); ///< Set the tray icon to normal.
|
||||
void setErrorTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'error' state.
|
||||
void setWarnTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'warn' state.
|
||||
void setUpdateTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'update' state.
|
||||
|
||||
public slots: // slot for signals received from gRPC that need transformation instead of simple forwarding
|
||||
void onMailServerSettingsChanged(int imapPort, int smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Slot for the ConnectionModeChanged gRPC event.
|
||||
|
@ -233,8 +243,10 @@ signals: // Signals received from the Go backend, to be forwarded to QML
|
|||
void bugReportSendError(); ///< Signal for the 'bugReportSendError' gRPC stream event.
|
||||
void showMainWindow(); ///< Signal for the 'showMainWindow' gRPC stream event.
|
||||
void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event.
|
||||
void showHelp(); ///< Signal for the 'showHelp' event (from the context menu).
|
||||
void showSettings(); ///< Signal for the 'showHelp' event (from the context menu).
|
||||
void selectUser(QString const& userID); ///< Signal emitted in order to selected a user with a given ID in the list.
|
||||
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
||||
void selectUser(QString const); ///< Signal that request the given user account to be displayed.
|
||||
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
||||
|
||||
// This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise.
|
||||
|
@ -257,7 +269,7 @@ private: // data members
|
|||
bool useSSLForIMAP_ { false }; ///< The cached value for useSSLForIMAP.
|
||||
bool useSSLForSMTP_ { false }; ///< The cached value for useSSLForSMTP.
|
||||
QList<QString> badEventDisplayQueue_; ///< THe queue for displaying 'bad event feedback request dialog'.
|
||||
|
||||
std::unique_ptr<TrayIcon> trayIcon_;
|
||||
friend class AppController;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,11 +5,7 @@
|
|||
<file>qml/AccountView.qml</file>
|
||||
<file>qml/Banner.qml</file>
|
||||
<file>qml/Bridge.qml</file>
|
||||
<file>qml/Bridge_test.qml</file>
|
||||
<file>qml/bridgeqml.qmlproject</file>
|
||||
<file>qml/BridgeTest/UserControl.qml</file>
|
||||
<file>qml/BridgeTest/UserList.qml</file>
|
||||
<file>qml/BridgeTest/UserModel.qml</file>
|
||||
<file>qml/BugReportView.qml</file>
|
||||
<file>qml/Configuration.qml</file>
|
||||
<file>qml/ConfigurationItem.qml</file>
|
||||
|
@ -28,6 +24,7 @@
|
|||
<file>qml/icons/ic-connected.svg</file>
|
||||
<file>qml/icons/ic-copy.svg</file>
|
||||
<file>qml/icons/ic-cross-close.svg</file>
|
||||
<file>qml/icons/ic-dot.svg</file>
|
||||
<file>qml/icons/ic-drive.svg</file>
|
||||
<file>qml/icons/ic-exclamation-circle-filled.svg</file>
|
||||
<file>qml/icons/ic-external-link.svg</file>
|
||||
|
@ -104,17 +101,6 @@
|
|||
<file>qml/ConnectionModeSettings.qml</file>
|
||||
<file>qml/SplashScreen.qml</file>
|
||||
<file>qml/Status.qml</file>
|
||||
<file>qml/StatusWindow.qml</file>
|
||||
<file>qml/tests/Buttons.qml</file>
|
||||
<file>qml/tests/ButtonsColumn.qml</file>
|
||||
<file>qml/tests/CheckBoxes.qml</file>
|
||||
<file>qml/tests/ComboBoxes.qml</file>
|
||||
<file>qml/tests/RadioButtons.qml</file>
|
||||
<file>qml/tests/Switches.qml</file>
|
||||
<file>qml/tests/Test.qml</file>
|
||||
<file>qml/tests/TestComponents.qml</file>
|
||||
<file>qml/tests/TextAreas.qml</file>
|
||||
<file>qml/tests/TextFields.qml</file>
|
||||
<file>qml/WelcomeGuide.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "TrayIcon.h"
|
||||
#include "QMLBackend.h"
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
QColor const normalColor(30, 168, 133); /// The normal state color.
|
||||
QColor const errorColor(220, 50, 81); ///< The error state color.
|
||||
QColor const warnColor(255, 153, 0); ///< The warn state color.
|
||||
QColor const updateColor(35, 158, 206); ///< The warn state color.
|
||||
QColor const greyColor(112, 109, 107); ///< The grey color.
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Create a single resolution icon from an image. Throw an exception in case of failure.
|
||||
//****************************************************************************************************************************************************
|
||||
QIcon loadIconFromImage(QString const &path) {
|
||||
QPixmap const pixmap(path);
|
||||
if (pixmap.isNull()) {
|
||||
throw Exception(QString("Could create icon from image '%1'.").arg(path));
|
||||
}
|
||||
return QIcon(pixmap);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Retrieve the color associated with a tray icon state.
|
||||
///
|
||||
/// \param[in] state The state.
|
||||
/// \return The color associated with a given tray icon state.
|
||||
//****************************************************************************************************************************************************
|
||||
QColor stateColor(TrayIcon::State state) {
|
||||
switch (state) {
|
||||
case TrayIcon::State::Normal:
|
||||
return normalColor;
|
||||
case TrayIcon::State::Error:
|
||||
return errorColor;
|
||||
case TrayIcon::State::Warn:
|
||||
return warnColor;
|
||||
case TrayIcon::State::Update:
|
||||
return updateColor;
|
||||
default:
|
||||
app().log().error(QString("Unknown tray icon state %1.").arg(static_cast<qint32>(state)));
|
||||
return normalColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Return the text identifying a state in resource file names.
|
||||
///
|
||||
/// \param[in] state The state.
|
||||
/// \param[in] The text identifying the state in resource file names.
|
||||
//****************************************************************************************************************************************************
|
||||
QString stateText(TrayIcon::State state) {
|
||||
switch (state) {
|
||||
case TrayIcon::State::Normal:
|
||||
return "norm";
|
||||
case TrayIcon::State::Error:
|
||||
return "error";
|
||||
case TrayIcon::State::Warn:
|
||||
return "warn";
|
||||
case TrayIcon::State::Update:
|
||||
return "update";
|
||||
default:
|
||||
app().log().error(QString("Unknown tray icon state %1.").arg(static_cast<qint32>(state)));
|
||||
return "norm";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TrayIcon::TrayIcon()
|
||||
: QSystemTrayIcon()
|
||||
, menu_(new QMenu) {
|
||||
|
||||
this->generateDotIcons();
|
||||
this->setContextMenu(menu_.get());
|
||||
|
||||
connect(menu_.get(), &QMenu::aboutToShow, this, &TrayIcon::onMenuAboutToShow);
|
||||
connect(this, &TrayIcon::selectUser, &app().backend(), &QMLBackend::selectUser);
|
||||
connect(this, &TrayIcon::activated, this, &TrayIcon::onActivated);
|
||||
|
||||
this->show();
|
||||
this->setState(State::Normal, QString(), QString());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::onMenuAboutToShow() {
|
||||
this->refreshContextMenu();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::onUserClicked() {
|
||||
try {
|
||||
auto action = dynamic_cast<QAction *>(this->sender());
|
||||
if (!action) {
|
||||
throw Exception("Could not retrieve context menu action.");
|
||||
}
|
||||
|
||||
QString const &userID = action->data().toString();
|
||||
if (userID.isNull()) {
|
||||
throw Exception("Could not retrieve context menu's selected user.");
|
||||
}
|
||||
|
||||
emit selectUser(userID);
|
||||
} catch (Exception const &e) {
|
||||
app().log().error(e.qwhat());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] reason The icon activation reason.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::onActivated(QSystemTrayIcon::ActivationReason reason) {
|
||||
if ((QSystemTrayIcon::Trigger == reason) && !onMacOS()) {
|
||||
app().backend().showMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::generateDotIcons() {
|
||||
QPixmap dotSVG(":/qml/icons/ic-dot.svg");
|
||||
struct IconColor {
|
||||
QIcon &icon;
|
||||
QColor color;
|
||||
};
|
||||
for (auto pair: QList<IconColor> {{ greenDot_, normalColor }, { greyDot_, greyColor }, { orangeDot_, warnColor }}) {
|
||||
QPixmap p = dotSVG;
|
||||
QPainter painter(&p);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(p.rect(), pair.color);
|
||||
painter.end();
|
||||
pair.icon = QIcon(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] state The state.
|
||||
/// \param[in] stateString A string describing the state.
|
||||
/// \param[in] statusIconPath The status icon path.
|
||||
/// \param[in] statusIconColor The color for the status icon in hex.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::setState(TrayIcon::State state, QString const &stateString, QString const &statusIconPath) {
|
||||
stateString_ = stateString;
|
||||
state_ = state;
|
||||
QString const style = onMacOS() ? "mono" : "color";
|
||||
QString const text = stateText(state);
|
||||
|
||||
|
||||
QIcon icon = loadIconFromImage(QString(":/qml/icons/systray-%1-%2.png").arg(style, text));
|
||||
icon.setIsMask(true);
|
||||
this->setIcon(icon);
|
||||
|
||||
this->generateStatusIcon(statusIconPath, stateColor(state));
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] svgPath The path of the SVG file for the icon.
|
||||
/// \param[in] color The color to apply to the icon.
|
||||
//****************************************************************************************************************************************************
|
||||
void TrayIcon::generateStatusIcon(QString const &svgPath, QColor const &color) {
|
||||
// We use the SVG path as pixmap mask and fill it with the appropriate color
|
||||
QString resourcePath = svgPath;
|
||||
resourcePath.replace(QRegularExpression(R"(^\.\/)"), ":/qml/"); // QML resource path are a bit different from the Qt resources path.
|
||||
QPixmap pixmap(resourcePath);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(pixmap.rect(), color);
|
||||
painter.end();
|
||||
statusIcon_ = QIcon(pixmap);
|
||||
}
|
||||
|
||||
|
||||
//**********************************************************************************************************************
|
||||
//
|
||||
//**********************************************************************************************************************
|
||||
void TrayIcon::refreshContextMenu() {
|
||||
if (!menu_) {
|
||||
app().log().error("Native tray icon context menu is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
menu_->clear();
|
||||
menu_->addAction(statusIcon_, stateString_, &app().backend(), &QMLBackend::showMainWindow);
|
||||
menu_->addSeparator();
|
||||
UserList const &users = app().backend().users();
|
||||
qint32 const userCount = users.count();
|
||||
for (qint32 i = 0; i < userCount; i++) {
|
||||
User const &user = *users.get(i);
|
||||
UserState const state = user.state();
|
||||
auto action = new QAction(user.primaryEmailOrUsername());
|
||||
action->setIcon((UserState::Connected == state) ? greenDot_ : (UserState::Locked == state ? orangeDot_ : greyDot_));
|
||||
action->setData(user.id());
|
||||
connect(action, &QAction::triggered, this, &TrayIcon::onUserClicked);
|
||||
if (i < 10) {
|
||||
action->setShortcut(QKeySequence(QString("Ctrl+%1").arg((i + 1) % 10)));
|
||||
}
|
||||
menu_->addAction(action);
|
||||
}
|
||||
if (userCount) {
|
||||
menu_->addSeparator();
|
||||
}
|
||||
menu_->addAction(tr("&Open Bridge"), QKeySequence("Ctrl+O"), &app().backend(), &QMLBackend::showMainWindow);
|
||||
menu_->addAction(tr("&Help"), QKeySequence("Ctrl+F1"), &app().backend(), &QMLBackend::showHelp);
|
||||
menu_->addAction(tr("&Settings"), QKeySequence("Ctrl+,"), &app().backend(), &QMLBackend::showSettings);
|
||||
menu_->addSeparator();
|
||||
menu_->addAction(tr("&Quit Bridge"), QKeySequence("Ctrl+Q"), &app().backend(), &QMLBackend::quit);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_GUI_NATIVE_TRAY_ICON_H
|
||||
#define BRIDGE_GUI_NATIVE_TRAY_ICON_H
|
||||
|
||||
|
||||
//**********************************************************************************************************************
|
||||
/// \brief A native tray icon.
|
||||
//**********************************************************************************************************************
|
||||
class TrayIcon: public QSystemTrayIcon {
|
||||
Q_OBJECT
|
||||
public: // typedef enum
|
||||
enum class State {
|
||||
Normal,
|
||||
Error,
|
||||
Warn,
|
||||
Update,
|
||||
}; ///< Enumeration for the state.
|
||||
|
||||
public: // data members
|
||||
TrayIcon(); ///< Default constructor.
|
||||
~TrayIcon() override = default; ///< Destructor.
|
||||
TrayIcon(TrayIcon const&) = delete; ///< Disabled copy-constructor.
|
||||
TrayIcon(TrayIcon&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
TrayIcon& operator=(TrayIcon const&) = delete; ///< Disabled assignment operator.
|
||||
TrayIcon& operator=(TrayIcon&&) = delete; ///< Disabled move assignment operator.
|
||||
void setState(State state, QString const& stateString, QString const &statusIconPath); ///< Set the state of the icon
|
||||
|
||||
signals:
|
||||
void selectUser(QString const& userID); ///< Signal for selecting a user with a given userID
|
||||
|
||||
private slots:
|
||||
void onMenuAboutToShow(); ///< Slot called before the context menu is shown.
|
||||
void onUserClicked(); ///< Slot triggered when clicking on a user in the context menu.
|
||||
static void onActivated(QSystemTrayIcon::ActivationReason reason); ///< Slot for the activation of the system tray icon.
|
||||
|
||||
private: // member functions.
|
||||
void generateDotIcons(); ///< generate the colored dot icons used for user status.
|
||||
void generateStatusIcon(QString const &svgPath, QColor const& color); ///< Generate the status icon.
|
||||
void refreshContextMenu(); ///< Refresh the context menu.
|
||||
|
||||
private: // data members
|
||||
State state_ { State::Normal }; ///< The state of the tray icon.
|
||||
QString stateString_; ///< The current state string.
|
||||
std::unique_ptr<QMenu> menu_; ///< The context menu for the tray icon. Not owned by the tray icon.
|
||||
QIcon statusIcon_; ///< The path of the status icon displayed in the context menu.
|
||||
QIcon greenDot_; ///< The green dot icon.
|
||||
QIcon greyDot_; ///< The grey dot icon.
|
||||
QIcon orangeDot_; ///< The orange dot icon.
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //BRIDGE_GUI_NATIVE_TRAY_ICON_H
|
|
@ -27,67 +27,55 @@ Item {
|
|||
property var notifications
|
||||
property var user
|
||||
|
||||
signal showSignIn()
|
||||
signal showSignIn
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _contentWidth: 640
|
||||
property int _topMargin: 32
|
||||
property int _detailsTopMargin: 25
|
||||
property int _bottomMargin: 12
|
||||
property int _detailsMargin: 25
|
||||
property int _spacing: 20
|
||||
property int _lineWidth: 1
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
clip: true
|
||||
property int _lineThickness: 1
|
||||
property bool _connected: root.user ? root.user.state === EUserState.Connected : false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds // Disable the springy effect when scroll reaches top/bottom.
|
||||
color: root.colorScheme.background_weak
|
||||
|
||||
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
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: contentItem.boundsBehavior = Flickable.StopAtBounds
|
||||
|
||||
ColumnLayout {
|
||||
id: topLevelColumnLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: topRectangle
|
||||
id: topArea
|
||||
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
|
||||
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
id: topLayout
|
||||
width: _contentWidth
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: _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
|
||||
RowLayout {
|
||||
// account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: _topMargin
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user ? (root.user.state === EUserState.Connected) : false
|
||||
enabled: _connected
|
||||
}
|
||||
|
||||
Button {
|
||||
|
@ -95,10 +83,11 @@ Item {
|
|||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user ? (root.user.state === EUserState.Connected) : false
|
||||
visible: _connected
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.user.logout()
|
||||
if (!root.user)
|
||||
return;
|
||||
root.user.logout();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,8 +98,9 @@ Item {
|
|||
secondary: true
|
||||
visible: root.user ? (root.user.state === EUserState.SignedOut) : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSignIn()
|
||||
if (!root.user)
|
||||
return;
|
||||
root.showSignIn();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +110,9 @@ Item {
|
|||
icon.source: "/qml/icons/ic-trash.svg"
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.notifications.askDeleteAccount(root.user)
|
||||
if (!root.user)
|
||||
return;
|
||||
root.notifications.askDeleteAccount(root.user);
|
||||
}
|
||||
visible: root.user ? root.user.state !== EUserState.Locked : false
|
||||
}
|
||||
|
@ -129,7 +120,7 @@ Item {
|
|||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: root._lineWidth
|
||||
height: root._lineThickness
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
|
@ -139,12 +130,12 @@ Item {
|
|||
actionText: qsTr("Configure")
|
||||
description: qsTr("Using the mailbox details below (re)configure your client.")
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user ? root.user.state === EUserState.Connected : false
|
||||
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
|
||||
visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1)
|
||||
showSeparator: splitMode.visible
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, user.addresses[0])
|
||||
if (!root.user)
|
||||
return;
|
||||
root.showSetupGuide(root.user, user.addresses[0]);
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
@ -157,15 +148,14 @@ Item {
|
|||
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.state === EUserState.Connected) : false
|
||||
visible: _connected && root.user.addresses.length > 1
|
||||
showSeparator: addressSelector.visible
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
if (!splitMode.checked) {
|
||||
root.notifications.askEnableSplitMode(user);
|
||||
} else {
|
||||
addressSelector.currentIndex = 0
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
addressSelector.currentIndex = 0;
|
||||
root.user.toggleSplitMode(!splitMode.checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,8 +164,8 @@ Item {
|
|||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user ? (root.user.state === EUserState.Connected) : false
|
||||
visible: root.user ? root.user.splitMode : false
|
||||
Layout.bottomMargin: _spacing
|
||||
visible: _connected && root.user.splitMode
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
|
@ -189,60 +179,68 @@ Item {
|
|||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
if (!root.user)
|
||||
return;
|
||||
root.showSetupGuide(root.user, addressSelector.displayText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 0
|
||||
} // just for some extra space before separator
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bottomArea
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: bottomLayout.implicitHeight
|
||||
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.state === EUserState.Connected) : false
|
||||
|
||||
property string currentAddress: addressSelector.displayText
|
||||
id: bottomLayout
|
||||
width: _contentWidth
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: _spacing
|
||||
visible: _connected
|
||||
|
||||
Label {
|
||||
Layout.topMargin: _detailsMargin
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: Backend.hostname
|
||||
port: Backend.imapPort.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security : Backend.useSSLForIMAP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
RowLayout {
|
||||
id: configuration
|
||||
spacing: _spacing
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : Backend.hostname
|
||||
port : Backend.smtpPort.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user ? root.user.password : ""
|
||||
security : Backend.useSSLForSMTP ? "SSL" : "STARTTLS"
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Configuration {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: Backend.hostname
|
||||
port: Backend.imapPort.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname: Backend.hostname
|
||||
port: Backend.smtpPort.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,8 @@ import Notifications
|
|||
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))
|
||||
function bound(num, lowerLimit, upperLimit) {
|
||||
return Math.max(lowerLimit, Math.min(upperLimit, num))
|
||||
}
|
||||
|
||||
property var title: Backend.appname
|
||||
|
@ -38,10 +35,30 @@ QtObject {
|
|||
property Notifications _notifications: Notifications {
|
||||
id: notifications
|
||||
frontendMain: mainWindow
|
||||
frontendStatus: statusWindow
|
||||
frontendTray: trayIcon
|
||||
}
|
||||
|
||||
property NotificationFilter _trayNotificationFilter: NotificationFilter {
|
||||
id: trayNotificationFilter
|
||||
source: root._notifications ? root._notifications.all : undefined
|
||||
onTopmostChanged: {
|
||||
if (topmost) {
|
||||
switch (topmost.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
Backend.setErrorTrayIcon(topmost.brief, topmost.icon)
|
||||
return
|
||||
case Notification.NotificationType.Warning:
|
||||
Backend.setWarnTrayIcon(topmost.brief, topmost.icon)
|
||||
return
|
||||
case Notification.NotificationType.Info:
|
||||
Backend.setUpdateTrayIcon(topmost.brief, topmost.icon)
|
||||
return
|
||||
}
|
||||
}
|
||||
Backend.setNormalTrayIcon()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
property MainWindow _mainWindow: MainWindow {
|
||||
id: mainWindow
|
||||
visible: false
|
||||
|
@ -66,190 +83,6 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
property StatusWindow _statusWindow: StatusWindow {
|
||||
id: statusWindow
|
||||
visible: false
|
||||
|
||||
title: root.title
|
||||
notifications: root._notifications
|
||||
|
||||
onShowMainWindow: {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowHelp: {
|
||||
mainWindow.showHelp()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowSettings: {
|
||||
mainWindow.showSettings()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onSelectUser: function(userID) {
|
||||
mainWindow.selectUser(userID)
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onQuit: {
|
||||
mainWindow.hide()
|
||||
trayIcon.visible = false
|
||||
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 preferably 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 preferably 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 preferably 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 preferably 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 status 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: getTrayIconPath()
|
||||
icon.mask: true // make sure that systems like macOS will use proper color
|
||||
tooltip: `${root.title} v${Backend.version}`
|
||||
onActivated: function(reason) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
property NotificationFilter _systrayfilter: NotificationFilter {
|
||||
source: root._notifications ? root._notifications.all : undefined
|
||||
}
|
||||
|
||||
function getTrayIconPath() {
|
||||
var color = Backend.goos == "darwin" ? "mono" : "color"
|
||||
|
||||
var level = "norm"
|
||||
if (_systrayfilter.topmost) {
|
||||
switch (_systrayfilter.topmost.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
level = "error"
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
level = "warn"
|
||||
break;
|
||||
case Notification.NotificationType.Info:
|
||||
level = "update"
|
||||
break;
|
||||
}
|
||||
}
|
||||
return `qrc:/qml/icons/systray-${color}-${level}.png`
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!Backend) {
|
||||
|
@ -266,7 +99,7 @@ QtObject {
|
|||
var c = Backend.users.count
|
||||
var u = Backend.users.get(0)
|
||||
// DEBUG
|
||||
if (c != 0) {
|
||||
if (c !== 0) {
|
||||
console.log("users non zero", c)
|
||||
console.log("first user", u )
|
||||
}
|
||||
|
@ -290,7 +123,7 @@ QtObject {
|
|||
}
|
||||
|
||||
function setColorScheme() {
|
||||
if (Backend.colorSchemeName == "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
|
||||
if (Backend.colorSchemeName == "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
|
||||
if (Backend.colorSchemeName === "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
|
||||
if (Backend.colorSchemeName === "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import Proton
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var user
|
||||
property var userIndex
|
||||
|
||||
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 ? root.user.state == EUserState.Connected : 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.state = EUserState.Connected
|
||||
user.resetLoginRequests()
|
||||
return
|
||||
} else {
|
||||
user.state = EUserState.SignedOut
|
||||
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: {
|
||||
Backend.loginUsernamePasswordError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "free user error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
Backend.loginFreeUserError()
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "connection error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
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: {
|
||||
Backend.login2FARequested(user.username)
|
||||
user.isLogin2FARequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
Backend.login2FAError("")
|
||||
user.isLogin2FAProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
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: {
|
||||
Backend.login2PasswordRequested("")
|
||||
user.isLogin2PasswordRequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
Backend.login2PasswordError("")
|
||||
|
||||
user.isLogin2PasswordProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
Backend.login2PasswordErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Login Finished"
|
||||
|
||||
onClicked: {
|
||||
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: {
|
||||
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 implicitWidth
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import Proton
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
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.state = EUserState.Connected
|
||||
newUserObject.setupGuideSeen = true // Backend.loginUser.setupGuideSeen
|
||||
|
||||
Backend.loginUser.username = ""
|
||||
Backend.loginUser.state = EUserState.SignedOut
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,982 +0,0 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import QtQml.Models
|
||||
|
||||
import Qt.labs.platform
|
||||
|
||||
import Proton
|
||||
|
||||
import "./BridgeTest"
|
||||
import BridgePreview
|
||||
|
||||
import Notifications
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
x: 10
|
||||
y: 10
|
||||
width: 800
|
||||
height: 800
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.darkStyle
|
||||
|
||||
flags : Qt.Window | Qt.Dialog
|
||||
visible : true
|
||||
title : "Bridge Test GUI"
|
||||
|
||||
// This is needed because on MacOS if first window shown is not transparent -
|
||||
// all other windows of application will not have transparent background (black
|
||||
// instead of transparency). In our case that mean that if BridgeTest will be
|
||||
// shown before StatusWindow - StatusWindow will not have transparent corners.
|
||||
color: "transparent"
|
||||
|
||||
function getCursorPos() {
|
||||
return BridgePreview.getCursorPos()
|
||||
}
|
||||
|
||||
function restart() {
|
||||
root.quit()
|
||||
console.log("Restarting....")
|
||||
root.openBridge()
|
||||
}
|
||||
|
||||
function openBridge() {
|
||||
bridge = bridgeComponent.createObject()
|
||||
var showSetupGuide = false
|
||||
if (showSetupGuide) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooyJenkins@protonmail.com"
|
||||
newUserObject.state = EUserState.Connected
|
||||
newUserObject.setupGuideSeen = false
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function quit() {
|
||||
if (bridge !== undefined && bridge !== null) {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function guiReady() {
|
||||
console.log("Gui Ready")
|
||||
}
|
||||
|
||||
function _log(msg, color) {
|
||||
logTextArea.text += "<p style='color: " + color + ";'>" + msg + "</p>"
|
||||
logTextArea.text += "\n"
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg)
|
||||
_log(msg, root.colorScheme.signal_info)
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.error(msg)
|
||||
_log(msg, root.colorScheme.signal_danger)
|
||||
}
|
||||
|
||||
// No user object should be put in this list until a successful login
|
||||
property var users: UserModel {
|
||||
id: _users
|
||||
|
||||
onRowsInserted: {
|
||||
for (var i = first; i <= last; i++) {
|
||||
_usersTest.insert(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
|
||||
onRowsRemoved: {
|
||||
_usersTest.remove(first + 1, first - last + 1)
|
||||
}
|
||||
|
||||
onRowsMoved: {
|
||||
_usersTest.move(start + 1, row + 1, end - start + 1)
|
||||
}
|
||||
|
||||
onDataChanged: {
|
||||
for (var i = topLeft.row; i <= bottomRight.row; i++) {
|
||||
_usersTest.set(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this list is used on test gui: it contains same users list as users above + fake user to represent login request of new user on pos 0
|
||||
property var usersTest: UserModel {
|
||||
id: _usersTest
|
||||
}
|
||||
|
||||
property var userComponent: Component {
|
||||
id: _userComponent
|
||||
|
||||
QtObject {
|
||||
property string username: ""
|
||||
property bool loggedIn: false
|
||||
property bool splitMode: false
|
||||
|
||||
property bool setupGuideSeen: true
|
||||
|
||||
property var usedBytes: 5350*1024*1024
|
||||
property var totalBytes: 20*1024*1024*1024
|
||||
property string avatarText: "jd"
|
||||
|
||||
property string password: "SMj975NnEYYsqu55GGmlpv"
|
||||
property var addresses: [
|
||||
"jaanedoe@protonmail.com",
|
||||
"jane@pm.me",
|
||||
"jdoe@pm.me"
|
||||
]
|
||||
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
signal login2FARequested()
|
||||
signal login2FAError()
|
||||
signal login2FAErrorAbort()
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError()
|
||||
signal login2PasswordErrorAbort()
|
||||
|
||||
// Test purpose only:
|
||||
property bool isFakeUser: this === root.loginUser
|
||||
|
||||
function userSignal(msg) {
|
||||
if (isFakeUser) {
|
||||
return
|
||||
}
|
||||
|
||||
root.log("<- User (" + username + "): " + msg)
|
||||
}
|
||||
|
||||
function toggleSplitMode(makeActive) {
|
||||
userSignal("toggle split mode "+makeActive)
|
||||
}
|
||||
signal toggleSplitModeFinished()
|
||||
|
||||
function configureAppleMail(address){
|
||||
userSignal("configure apple mail "+address)
|
||||
}
|
||||
|
||||
function logout(){
|
||||
userSignal("logout")
|
||||
loggedIn = false
|
||||
}
|
||||
function remove(){
|
||||
console.log("remove this", users.count)
|
||||
for (var i=0; i<users.count; i++) {
|
||||
if (users.get(i) === this) {
|
||||
users.remove(i,1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
userSignal("loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
userSignal("loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
userSignal("loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
userSignal("login2FARequested")
|
||||
}
|
||||
onLogin2FAError: {
|
||||
userSignal("login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
userSignal("login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
userSignal("login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
userSignal("login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
userSignal("login2PasswordErrorAbort")
|
||||
}
|
||||
|
||||
function resetLoginRequests() {
|
||||
isLoginRequested = false
|
||||
isLogin2FARequested = false
|
||||
isLogin2FAProvided = false
|
||||
isLogin2PasswordRequested = false
|
||||
isLogin2PasswordProvided = false
|
||||
}
|
||||
|
||||
property bool isLoginRequested: false
|
||||
|
||||
property bool isLogin2FARequested: false
|
||||
property bool isLogin2FAProvided: false
|
||||
|
||||
property bool isLogin2PasswordRequested: false
|
||||
property bool isLogin2PasswordProvided: false
|
||||
}
|
||||
}
|
||||
|
||||
// this it fake user used only for representing first login request
|
||||
property var loginUser
|
||||
Component.onCompleted: {
|
||||
var newLoginUser = _userComponent.createObject()
|
||||
root.loginUser = newLoginUser
|
||||
root.loginUser.setupGuideSeen = false
|
||||
_usersTest.append({object: newLoginUser})
|
||||
|
||||
newLoginUser.loginUsernamePasswordError.connect(root.loginUsernamePasswordError)
|
||||
newLoginUser.loginFreeUserError.connect(root.loginFreeUserError)
|
||||
newLoginUser.loginConnectionError.connect(root.loginConnectionError)
|
||||
newLoginUser.login2FARequested.connect(root.login2FARequested)
|
||||
newLoginUser.login2FAError.connect(root.login2FAError)
|
||||
newLoginUser.login2FAErrorAbort.connect(root.login2FAErrorAbort)
|
||||
newLoginUser.login2PasswordRequested.connect(root.login2PasswordRequested)
|
||||
newLoginUser.login2PasswordError.connect(root.login2PasswordError)
|
||||
newLoginUser.login2PasswordErrorAbort.connect(root.login2PasswordErrorAbort)
|
||||
|
||||
|
||||
// add one user on start
|
||||
var hasUserOnStart = false
|
||||
if (hasUserOnStart) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = EUserState.Connected
|
||||
newUserObject.setupGuideSeen = true
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
TabButton {
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "User control"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Log"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Settings signals"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
anchors.top: tabBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
currentIndex: tabBar.currentIndex
|
||||
anchors.margins: 10
|
||||
|
||||
RowLayout {
|
||||
id: globalTab
|
||||
spacing : 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: styleRadioGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Light UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.lightStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.lightStyle) {
|
||||
root.colorSchemeName = "light"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Dark UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.darkStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.darkStyle) {
|
||||
root.colorSchemeName = "dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showOnStartupCheckbox
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show on startup"
|
||||
checked: root.showOnStartup
|
||||
onCheckedChanged: {
|
||||
root.showOnStartup = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showSplashScreen
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show splash screen"
|
||||
checked: root.showSplashScreen
|
||||
onCheckedChanged: {
|
||||
root.showSplashScreen = checked
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Open Bridge"
|
||||
enabled: bridge === undefined || bridge === null
|
||||
onClicked: root.openBridge()
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Close Bridge"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: danger"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyOnlyPaidUsers()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: warning"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUpdateManually()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: success"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUserAdded()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: usersTab
|
||||
UserList {
|
||||
id: usersListView
|
||||
Layout.fillHeight: true
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
}
|
||||
|
||||
UserControl {
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
user: ((root.usersTest.count > usersListView.currentIndex) && usersListView.currentIndex != -1) ? root.usersTest.get(usersListView.currentIndex) : undefined
|
||||
userIndex: usersListView.currentIndex - 1 // -1 because 0 index is fake user
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: notificationsTab
|
||||
spacing: 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 5
|
||||
|
||||
Switch {
|
||||
text: "Internet connection"
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
onCheckedChanged: {
|
||||
checked ? root.internetOn() : root.internetOff()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual ready"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualReady("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForce("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForceError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update is latest version"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateIsLatestVersion()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send OK"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendSuccess()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
text: "Bug report send error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache anavailable"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheUnavailable()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache can't move"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheCantMove()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache location change success"
|
||||
onClicked: {
|
||||
root.cacheLocationChangeSuccess()
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Disk full"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.diskFull()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "No keychain"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.notifyHasNoKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Rebuild keychain"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.notifyRebuildKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Address changed"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.addressChanged("p@v.el")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Address changed + Logout"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.addressChangedLogout("p@v.el")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: logTextArea
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
//readOnly: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: settingsTab
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {colorScheme : root.colorScheme ; text : "GOOS : "}
|
||||
Button {colorScheme : root.colorScheme ; text : "Linux" ; onClicked : root.goos = "linux" ; enabled: root.goos != "linux"}
|
||||
Button {colorScheme : root.colorScheme ; text : "Windows" ; onClicked : root.goos = "windows" ; enabled: root.goos != "windows"}
|
||||
Button {colorScheme : root.colorScheme ; text : "macOS" ; onClicked : root.goos = "darwin" ; enabled: root.goos != "darwin"}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Automatic updates:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutomaticUpdateOn; onClicked: root.isAutomaticUpdateOn = !root.isAutomaticUpdateOn}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Autostart:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutostartOn; onClicked: root.isAutostartOn = !root.isAutostartOn}
|
||||
Button {colorScheme: root.colorScheme; text: "Toggle finished"; onClicked: root.toggleAutostartFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Beta:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isBetaEnabled; onClicked: root.isBetaEnabled = !root.isBetaEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "All Mail disabled:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAllMailVisible; onClicked: root.isAllMailVisible = !root.isAllMailVisible}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "IMAP"
|
||||
text: root.portIMAP
|
||||
onEditingFinished: root.portIMAP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "SMTP"
|
||||
text: root.portSMTP
|
||||
onEditingFinished: root.portSMTP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changePortFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "SMTP using SSL:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.useSSLForSMTP; onClicked: root.useSSLForSMTP = !root.useSSLForSMTP}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Local cache:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDiskCacheEnabled; onClicked: root.isDiskCacheEnabled = !root.isDiskCacheEnabled}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "Path"
|
||||
text: root.diskCachePath.toString().replace("file://", "")
|
||||
implicitWidth: 160
|
||||
onEditingFinished: {
|
||||
root.diskCachePath = Qt.resolvedUrl("file://"+text)
|
||||
}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changeLocalCacheFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Reset:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.resetFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Check update:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.checkUpdatesFinished()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Bridge bridge
|
||||
|
||||
property string goos: "darwin"
|
||||
|
||||
property bool showOnStartup: true // this actually needs to be false, but since we use Bridge_test for testing purpose - lets default this to true just for convenience
|
||||
property bool dockIconVisible: false
|
||||
|
||||
// this signals are used only when trying to login with new user (i.e. not in users model)
|
||||
signal loginUsernamePasswordError(string errorMsg)
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError(string errorMsg)
|
||||
signal login2FARequested(string username)
|
||||
signal login2FAError(string errorMsg)
|
||||
signal login2FAErrorAbort(string errorMsg)
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError(string errorMsg)
|
||||
signal login2PasswordErrorAbort(string errorMsg)
|
||||
signal loginFinished(int index)
|
||||
signal loginAlreadyLoggedIn(int index)
|
||||
|
||||
signal internetOff()
|
||||
signal internetOn()
|
||||
|
||||
signal updateManualReady(var version)
|
||||
signal updateManualRestartNeeded()
|
||||
signal updateManualError()
|
||||
signal updateForce(var version)
|
||||
signal updateForceError()
|
||||
signal updateSilentRestartNeeded()
|
||||
signal updateSilentError()
|
||||
signal updateIsLatestVersion()
|
||||
function checkUpdates(){
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
function installUpdate() {
|
||||
console.log("manuall install update triggered")
|
||||
}
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
// Qt.resolvedUrl("file:///C:/Users/user/AppData/Roaming/protonmail/bridge-v3/cache/c11/messages")
|
||||
property url diskCachePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
signal cacheUnavailable()
|
||||
signal cacheCantMove()
|
||||
signal cacheLocationChangeSuccess()
|
||||
signal diskFull()
|
||||
function changeLocalCache(enableDiskCache, diskCachePath) {
|
||||
console.debug("-> disk cache", enableDiskCache, diskCachePath)
|
||||
}
|
||||
signal changeLocalCacheFinished()
|
||||
|
||||
|
||||
// Settings
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
var callback = function () {
|
||||
root.isAutomaticUpdateOn = makeItActive;
|
||||
console.debug("-> CHANGED silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
}
|
||||
atimer.onTriggered.connect(callback)
|
||||
atimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: atimer
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
function toggleAutostart(makeItActive) {
|
||||
console.debug("-> autostart", makeItActive, root.isAutostartOn)
|
||||
}
|
||||
signal toggleAutostartFinished()
|
||||
|
||||
property bool isBetaEnabled : false
|
||||
function toggleBeta(makeItActive){
|
||||
console.debug("-> beta", makeItActive, root.isBetaEnabled)
|
||||
root.isBetaEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isDoHEnabled : true
|
||||
function toggleDoH(makeItActive){
|
||||
console.debug("-> DoH", makeItActive, root.isDoHEnabled)
|
||||
root.isDoHEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isAllMailVisible : true
|
||||
function changeIsAllMailVisible(isVisible){
|
||||
console.debug("-> All Mail Visible", isVisible, root.isAllMailVisible)
|
||||
root.isAllMailVisible = isVisible
|
||||
}
|
||||
|
||||
|
||||
property bool useSSLForSMTP: false
|
||||
function toggleUseSSLForSMTP(makeItActive){
|
||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLForSMTP)
|
||||
}
|
||||
signal toggleUseSSLFinished()
|
||||
|
||||
property string hostname: "127.0.0.1"
|
||||
property int portIMAP: 1143
|
||||
property int portSMTP: 1025
|
||||
function changePorts(imapPort, smtpPort){
|
||||
console.debug("-> ports", imapPort, smtpPort)
|
||||
}
|
||||
function isPortFree(port){
|
||||
if (port == portIMAP) return false
|
||||
if (port == portSMTP) return false
|
||||
if (port == 12345) return false
|
||||
return true
|
||||
}
|
||||
signal changePortFinished()
|
||||
signal imapPortStartupError()
|
||||
signal smtpPortStartupError()
|
||||
|
||||
function triggerReset() {
|
||||
console.debug("-> trigger reset")
|
||||
}
|
||||
signal resetFinished()
|
||||
|
||||
property string version: "2.0.X-BridePreview"
|
||||
property url logsPath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url licensePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url releaseNotesLink: Qt.resolvedUrl("https://proton.me/download/bridge/early_releases.html")
|
||||
property url dependencyLicensesLink: Qt.resolvedUrl("https://github.com/ProtonMail/proton-bridge/v3/blob/master/COPYING_NOTES.md#dependencies")
|
||||
property url landingPageLink: Qt.resolvedUrl("https://proton.me/mail/bridge#download")
|
||||
|
||||
property string colorSchemeName: "light"
|
||||
function changeColorScheme(newScheme){
|
||||
root.colorSchemeName = newScheme
|
||||
}
|
||||
|
||||
|
||||
property string currentEmailClient: "" // "Apple Mail 14.0"
|
||||
function updateCurrentMailClient(){
|
||||
currentEmailClient = "Apple Mail 14.0"
|
||||
}
|
||||
|
||||
function reportBug(description,address,emailClient,includeLogs){
|
||||
console.log("report bug")
|
||||
console.log(" description",description)
|
||||
console.log(" address",address)
|
||||
console.log(" emailClient",emailClient)
|
||||
console.log(" includeLogs",includeLogs)
|
||||
}
|
||||
signal reportBugFinished()
|
||||
signal bugReportSendSuccess()
|
||||
signal bugReportSendError()
|
||||
|
||||
property var availableKeychain: ["gnome-keyring", "pass", "macos-keychain", "windows-credentials"]
|
||||
property string currentKeychain: availableKeychain[0]
|
||||
function changeKeychain(wantedKeychain){
|
||||
console.log("Changing keychain from", root.currentKeychain, "to", wantedKeychain)
|
||||
root.currentKeychain = wantedKeychain
|
||||
root.changeKeychainFinished()
|
||||
}
|
||||
signal changeKeychainFinished()
|
||||
signal notifyHasNoKeychain()
|
||||
signal notifyRebuildKeychain()
|
||||
|
||||
signal noActiveKeyForRecipient(string email)
|
||||
signal showMainWindow()
|
||||
|
||||
signal addressChanged(string address)
|
||||
signal addressChangedLogout(string address)
|
||||
signal userDisconnected(string username)
|
||||
signal apiCertIssue()
|
||||
|
||||
property bool showSplashScreen: false
|
||||
|
||||
|
||||
function login(username, password) {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
function login2FA(username, code) {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
function login2Password(username, password) {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
function loginAbort(username) {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.debug("<- loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
console.debug("<- loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
console.debug("<- loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
console.debug("<- login2FARequested", username)
|
||||
}
|
||||
onLogin2FAError: {
|
||||
console.debug("<- login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
console.debug("<- login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
console.debug("<- login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
console.debug("<- login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.debug("<- login2PasswordErrorAbort")
|
||||
}
|
||||
onLoginFinished: {
|
||||
console.debug("<- loginFinished", index)
|
||||
}
|
||||
onLoginAlreadyLoggedIn: {
|
||||
console.debug("<- loginAlreadyLoggedIn", index)
|
||||
}
|
||||
|
||||
onInternetOff: {
|
||||
console.debug("<- internetOff")
|
||||
}
|
||||
onInternetOn: {
|
||||
console.debug("<- internetOn")
|
||||
}
|
||||
|
||||
Component {
|
||||
id: bridgeComponent
|
||||
|
||||
Bridge {
|
||||
backend: root
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
Qt.quit()
|
||||
}
|
||||
}
|
|
@ -61,8 +61,6 @@ Rectangle {
|
|||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Item{}
|
||||
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username }
|
||||
|
|
|
@ -169,6 +169,19 @@ SettingsView {
|
|||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: telemetry
|
||||
Layout.fillWidth: true
|
||||
checked: !Backend.isTelemetryDisabled
|
||||
colorScheme: root.colorScheme
|
||||
description: qsTr("Help us improve Proton services by sending anonymous usage statistics.")
|
||||
text: qsTr("Collect usage diagnostics")
|
||||
type: SettingsItem.Toggle
|
||||
visible: root._isAdvancedShown
|
||||
|
||||
onClicked: Backend.toggleIsTelemetryDisabled(telemetry.checked)
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: ports
|
||||
visible: root._isAdvancedShown
|
||||
|
|
|
@ -41,7 +41,7 @@ SettingsView {
|
|||
actionIcon: "/qml/icons/ic-external-link.svg"
|
||||
description: qsTr("Get help setting up your client with our instructions and FAQs.")
|
||||
type: SettingsItem.PrimaryButton
|
||||
onClicked: {Qt.openUrlExternally("https://proton.me/support/mail")}
|
||||
onClicked: {Qt.openUrlExternally("https://proton.me/support/bridge")}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
|
|
@ -24,29 +24,20 @@ import QtQuick.Controls
|
|||
import Proton
|
||||
import Notifications
|
||||
|
||||
import "tests"
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
width: 960
|
||||
height: 576
|
||||
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
visible: true
|
||||
|
||||
minimumHeight: contentLayout.implicitHeight
|
||||
minimumWidth: contentLayout.implicitWidth
|
||||
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
property int _defaultWidth: 1080
|
||||
property int _defaultHeight: 780
|
||||
width: _defaultWidth
|
||||
height: _defaultHeight
|
||||
minimumWidth: _defaultWidth
|
||||
|
||||
property var notifications
|
||||
|
||||
// This is needed because on MacOS if first window shown is not transparent -
|
||||
// all other windows of application will not have transparent background (black
|
||||
// instead of transparency). In our case that mean that if MainWindow will be
|
||||
// shown before StatusWindow - StatusWindow will not have transparent corners.
|
||||
color: "transparent"
|
||||
|
||||
// show Setup Guide on every new user
|
||||
Connections {
|
||||
target: Backend.users
|
||||
|
@ -86,10 +77,6 @@ ApplicationWindow {
|
|||
root.showAndRise()
|
||||
}
|
||||
|
||||
function onSelectUser(userID) {
|
||||
root.selectUser(userID)
|
||||
}
|
||||
|
||||
function onLoginFinished(index, wasSignedOut) {
|
||||
var user = Backend.users.get(index)
|
||||
if (user && !wasSignedOut) {
|
||||
|
@ -97,6 +84,21 @@ ApplicationWindow {
|
|||
}
|
||||
console.debug("Login finished", index)
|
||||
}
|
||||
|
||||
function onShowHelp() {
|
||||
root.showHelp()
|
||||
root.showAndRise()
|
||||
}
|
||||
|
||||
function onShowSettings() {
|
||||
root.showSettings()
|
||||
root.showAndRise()
|
||||
}
|
||||
|
||||
function onSelectUser(userID) {
|
||||
contentWrapper.selectUser(userID)
|
||||
root.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
|
|
|
@ -24,8 +24,6 @@ QtObject {
|
|||
id: root
|
||||
|
||||
property MainWindow frontendMain
|
||||
property StatusWindow frontendStatus
|
||||
property SystemTrayIcon frontendTray
|
||||
|
||||
signal askEnableBeta()
|
||||
signal askEnableSplitMode(var user)
|
||||
|
@ -140,7 +138,7 @@ QtObject {
|
|||
|
||||
property Notification imapPortChangeError: Notification {
|
||||
description: qsTr("The IMAP port could not be changed.")
|
||||
brief: qsTr("IMAP port change error")
|
||||
brief: qsTr("IMAP port error")
|
||||
icon: "./icons/ic-alert.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Connection
|
||||
|
@ -156,7 +154,7 @@ QtObject {
|
|||
|
||||
property Notification smtpPortChangeError: Notification {
|
||||
description: qsTr("The SMTP port could not be changed.")
|
||||
brief: qsTr("SMTP port change error")
|
||||
brief: qsTr("SMTP port error")
|
||||
icon: "./icons/ic-alert.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Connection
|
||||
|
@ -172,7 +170,7 @@ QtObject {
|
|||
|
||||
property Notification imapConnectionModeChangeError: Notification {
|
||||
description: qsTr("The IMAP connection mode could not be changed.")
|
||||
brief: qsTr("IMAP Connection mode change error")
|
||||
brief: qsTr("IMAP Connection mode error")
|
||||
icon: "./icons/ic-alert.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Connection
|
||||
|
@ -196,7 +194,7 @@ QtObject {
|
|||
|
||||
property Notification smtpConnectionModeChangeError: Notification {
|
||||
description: qsTr("The SMTP connection mode could not be changed.")
|
||||
brief: qsTr("SMTP Connection mode change error")
|
||||
brief: qsTr("SMTP Connection mode error")
|
||||
icon: "./icons/ic-alert.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Connection
|
||||
|
@ -227,7 +225,7 @@ QtObject {
|
|||
var link = Backend.releaseNotesLink
|
||||
return `${descr} <a href="${link}">${text}</a>`
|
||||
}
|
||||
brief: qsTr("Update available.")
|
||||
brief: qsTr("Update available")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Update | Notifications.Group.Dialogs
|
||||
|
@ -514,7 +512,7 @@ QtObject {
|
|||
// login
|
||||
property Notification loginConnectionError: Notification {
|
||||
description: qsTr("Bridge is not able to contact the server, please check your internet connection.")
|
||||
brief: description
|
||||
brief: qsTr("Connection error")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -538,7 +536,7 @@ QtObject {
|
|||
|
||||
property Notification onlyPaidUsers: Notification {
|
||||
description: qsTr("Bridge is exclusive to our paid plans. Upgrade your account to use Bridge.")
|
||||
brief: description
|
||||
brief: qsTr("Upgrade your account")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -562,7 +560,7 @@ QtObject {
|
|||
|
||||
property Notification alreadyLoggedIn: Notification {
|
||||
description: qsTr("This account is already signed in.")
|
||||
brief: description
|
||||
brief: qsTr("Already signed in")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Info
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -587,7 +585,7 @@ QtObject {
|
|||
// Bug reports
|
||||
property Notification bugReportSendSuccess: Notification {
|
||||
description: qsTr("Thank you for the report. We'll get back to you as soon as we can.")
|
||||
brief: description
|
||||
brief: qsTr("Report sent")
|
||||
icon: "./icons/ic-info-circle-filled.svg"
|
||||
type: Notification.NotificationType.Success
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -611,7 +609,7 @@ QtObject {
|
|||
|
||||
property Notification bugReportSendError: Notification {
|
||||
description: qsTr("Report could not be sent. Try again or email us directly.")
|
||||
brief: description
|
||||
brief: qsTr("Error sending report")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -634,8 +632,8 @@ QtObject {
|
|||
// Cache
|
||||
property Notification cacheUnavailable: Notification {
|
||||
title: qsTr("Cache location is unavailable")
|
||||
description: qsTr("Check the directory or change it in your settings.")
|
||||
brief: qsTr("The current cache location is unavailable. Check the directory or change it in your settings.")
|
||||
description: qsTr("The current cache location is unavailable. Check the directory or change it in your settings.")
|
||||
brief: title
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
@ -725,7 +723,7 @@ QtObject {
|
|||
// Other
|
||||
property Notification accountChanged: Notification {
|
||||
description: qsTr("The address list for .... account has changed. You need to reconfigure your email client.")
|
||||
brief: qsTr("The address list for your account has changed. Reconfigure your email client.")
|
||||
brief: qsTr("Address list changed")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -742,7 +740,7 @@ QtObject {
|
|||
property Notification diskFull: Notification {
|
||||
title: qsTr("Your disk is almost full")
|
||||
description: qsTr("Quit Bridge and free disk space or disable the local cache (not recommended).")
|
||||
brief: qsTr("Your disk is almost full. Free disk space or disable the local cache.")
|
||||
brief: title
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||
|
@ -948,8 +946,8 @@ QtObject {
|
|||
|
||||
property Notification noKeychain: Notification {
|
||||
title: qsTr("No keychain available")
|
||||
description: qsTr("Bridge is not able to detect a supported password manager (pass or secret-service). Please install and setup supported password manager and restart the application.")
|
||||
brief: title
|
||||
description: qsTr("Bridge is not able to detect a supported password manager (pass or secret-service). Please install and setup supported password manager and restart the application.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Dialogs | Notifications.Group.Configuration
|
||||
|
@ -982,13 +980,13 @@ QtObject {
|
|||
|
||||
property Notification rebuildKeychain: Notification {
|
||||
title: qsTr("Your macOS keychain might be corrupted")
|
||||
description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.")
|
||||
brief: title
|
||||
description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Dialogs | Notifications.Group.Configuration
|
||||
|
||||
property var supportLink: "https://proton.me/support/mail"
|
||||
property var supportLink: "https://proton.me/support/bridge"
|
||||
|
||||
|
||||
Connections {
|
||||
|
@ -1014,8 +1012,8 @@ QtObject {
|
|||
|
||||
property Notification addressChanged: Notification {
|
||||
title: qsTr("Address list changes")
|
||||
brief: title
|
||||
description: qsTr("The address list for your account has changed. You might need to reconfigure your email client.")
|
||||
brief: description
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Configuration
|
||||
|
@ -1047,11 +1045,11 @@ QtObject {
|
|||
|
||||
property Notification apiCertIssue: Notification {
|
||||
title: qsTr("Unable to establish a \nsecure connection to \nProton servers")
|
||||
brief: qsTr("Cannot establish secure connection")
|
||||
description: qsTr("Bridge cannot verify the authenticity of Proton servers on your current network due to a TLS certificate error. " +
|
||||
"Start Bridge again after ensuring your connection is secure and/or connecting to a VPN. Learn more about TLS pinning " +
|
||||
"<a href=\"https://proton.me/blog/tls-ssl-certificate#Extra-security-precautions-taken-by-ProtonMail\">here</a>.")
|
||||
|
||||
brief: title
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Dialogs | Notifications.Group.Connection
|
||||
|
@ -1078,6 +1076,7 @@ QtObject {
|
|||
|
||||
property Notification noActiveKeyForRecipient: Notification {
|
||||
title: qsTr("Unable to send \nencrypted message")
|
||||
brief: title
|
||||
description: "#PlaceholderText#"
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
|
@ -1174,8 +1173,9 @@ QtObject {
|
|||
}
|
||||
|
||||
property Notification genericError: Notification {
|
||||
title: "#PlaceholderText#"
|
||||
description: "#PlaceholderText#"
|
||||
title: ""
|
||||
brief: title
|
||||
description: ""
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Dialogs
|
||||
|
@ -1201,7 +1201,7 @@ QtObject {
|
|||
|
||||
property Notification genericQuestion: Notification {
|
||||
title: ""
|
||||
brief: ""
|
||||
brief: title
|
||||
description: ""
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Dialogs
|
||||
|
|
|
@ -1,352 +0,0 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import Proton
|
||||
import Notifications
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
height: contentLayout.implicitHeight
|
||||
width: contentLayout.implicitWidth
|
||||
|
||||
flags: (Qt.platform.os === "linux" ? Qt.Tool : 0) | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint | Qt.WindowStaysOnTopHint | Qt.WA_TranslucentBackground
|
||||
color: "transparent"
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
property var notifications
|
||||
|
||||
signal showMainWindow()
|
||||
signal showHelp()
|
||||
signal showSettings()
|
||||
signal selectUser(string userID)
|
||||
signal quit()
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
function enableHoverOnOpenBridgeButton() {
|
||||
openBridgeButton.hoverEnabled = true
|
||||
mouseArea.positionChanged.disconnect(enableHoverOnOpenBridgeButton)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) { // GODT-1479 To avoid a visual glitch where the 'Open bridge button' would appear hovered when the status windows opens,
|
||||
// we've disabled hover on it when it was last closed. Re-enabling hover here will not work on all platforms. so we temporarily connect
|
||||
// mouse move event over the window's mouseArea to a function that will re-enable hover on the open bridge button.
|
||||
openBridgeButton.focus = false
|
||||
mouseArea.positionChanged.connect(enableHoverOnOpenBridgeButton)
|
||||
} else {
|
||||
menu.close()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
Layout.minimumHeight: 201
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
Layout.minimumWidth: 448
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
implicitHeight: 12
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height * 2
|
||||
radius: ProtonStyle.dialog_radius
|
||||
|
||||
color: {
|
||||
if (!statusItem.activeNotification) {
|
||||
return root.colorScheme.signal_success
|
||||
}
|
||||
|
||||
switch (statusItem.activeNotification.type) {
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
|
||||
spacing: 8
|
||||
|
||||
Status {
|
||||
id: statusItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
|
||||
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.Update | Notifications.Group.Configuration
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
visible: statusItem.activeNotification && statusItem.activeNotification.action.length > 0
|
||||
action: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 ? statusItem.activeNotification.action[0] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.maximumHeight: accountListView.count ?
|
||||
accountListView.contentHeight / accountListView.count * 3 + accountListView.anchors.topMargin + accountListView.anchors.bottomMargin :
|
||||
Number.POSITIVE_INFINITY
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
clip: true
|
||||
|
||||
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
ListView {
|
||||
id: accountListView
|
||||
|
||||
model: Backend.users
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 24
|
||||
anchors.rightMargin: 24
|
||||
|
||||
interactive: contentHeight > parent.height
|
||||
snapMode: ListView.SnapToItem
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
spacing: 4
|
||||
|
||||
delegate: Item {
|
||||
id: viewItem
|
||||
width: ListView.view.width
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
property var user: Backend.users.get(index)
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
user: viewItem.user
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.topMargin: 12
|
||||
Layout.bottomMargin: 12
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
visible: viewItem.user ? (viewItem.user.state === EUserState.SignedOut) : false
|
||||
text: qsTr("Sign in")
|
||||
onClicked: {
|
||||
root.selectUser(viewItem.user.id) // selectUser will show login screen if user is in SignedOut state.
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: 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
|
||||
|
||||
// background:
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height * 2
|
||||
radius: ProtonStyle.dialog_radius
|
||||
|
||||
color: root.colorScheme.background_weak
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 0
|
||||
|
||||
Button {
|
||||
id: openBridgeButton
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
text: qsTr("Open Bridge")
|
||||
|
||||
borderless: true
|
||||
labelType: Label.LabelType.Caption_semibold
|
||||
|
||||
onClicked: {
|
||||
// GODT-1479: we disable hover for the button to avoid a visual glitch where the button is
|
||||
// wrongly hovered when re-opening the status window after clicking
|
||||
hoverEnabled = false;
|
||||
root.showMainWindow()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
secondary: true
|
||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||
borderless: true
|
||||
checkable: true
|
||||
|
||||
onClicked: {
|
||||
menu.open()
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: menu
|
||||
colorScheme: root.colorScheme
|
||||
modal: true
|
||||
|
||||
y: 0 - height
|
||||
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Help")
|
||||
onClicked: {
|
||||
root.showHelp()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Settings")
|
||||
onClicked: {
|
||||
root.showSettings()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Quit Bridge")
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.quit()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
parent.checked = false
|
||||
}
|
||||
onOpened: {
|
||||
parent.checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) root.close()
|
||||
}
|
||||
|
||||
function showAndRise() {
|
||||
root.show()
|
||||
root.raise()
|
||||
if (!root.active) {
|
||||
root.requestActivate()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.533333,0,0,0.533333,238.933,238.933)">
|
||||
<circle cx="512" cy="512" r="480" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 593 B |
|
@ -160,3 +160,41 @@ target_link_libraries(bridgepp
|
|||
)
|
||||
|
||||
target_precompile_headers(bridgepp PRIVATE Pch.h)
|
||||
|
||||
#*****************************************************************************************************************************************************
|
||||
# GoogleTest
|
||||
#*****************************************************************************************************************************************************
|
||||
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW) # avoid warning DOWNLOAD_EXTRACT_TIMESTAMP
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/b796f7d44681514f58a683a3a71ff17c94edb0c1.zip
|
||||
)
|
||||
|
||||
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
enable_testing()
|
||||
|
||||
#*****************************************************************************************************************************************************
|
||||
# Tests
|
||||
#*****************************************************************************************************************************************************
|
||||
add_executable(bridgepp-test
|
||||
Test/TestBridgeUtils.cpp
|
||||
Test/TestException.cpp
|
||||
Test/TestWorker.cpp Test/TestWorker.h)
|
||||
add_dependencies(bridgepp-test bridgepp)
|
||||
target_precompile_headers(bridgepp-test PRIVATE Pch.h)
|
||||
target_link_libraries(bridgepp-test
|
||||
GTest::gtest_main
|
||||
bridgepp
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(bridgepp-test)
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(BridgeUtils, OS) {
|
||||
#ifdef Q_OS_MACOS
|
||||
EXPECT_EQ(os(), OS::MacOS);
|
||||
EXPECT_FALSE(onLinux());
|
||||
EXPECT_TRUE(onMacOS());
|
||||
EXPECT_FALSE(onWindows());
|
||||
EXPECT_EQ(goos(), "darwin");
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
EXPECT_EQ(os(), OS::Windows);
|
||||
EXPECT_FALSE(onLinux());
|
||||
EXPECT_FALSE(onMacOS());
|
||||
EXPECT_TRUE(onWindows());
|
||||
EXPECT_EQ(goos(), "windows");
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
EXPECT_EQ(os(), OS::Linux);
|
||||
EXPECT_TRUE(onLinux());
|
||||
EXPECT_FALSE(onMacOS());
|
||||
EXPECT_FALSE(onWindows());
|
||||
EXPECT_EQ(goos(), "linux");
|
||||
return;
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(false); // should be unreachable.
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(BridgeUtils, UserFolders) {
|
||||
typedef QString (*dirFunction)();
|
||||
QList<dirFunction> functions = { userConfigDir, userCacheDir, userDataDir, sentryCacheDir };
|
||||
QString path;
|
||||
for (dirFunction f: functions) {
|
||||
EXPECT_NO_THROW(path = f());
|
||||
EXPECT_FALSE(path.isEmpty());
|
||||
EXPECT_TRUE(QDir(path).exists());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(BridgeUtils, Random) {
|
||||
qint32 repeatCount = 1000;
|
||||
qint32 const maxValue = 5;
|
||||
for (qint32 i = 0; i < repeatCount; ++i) {
|
||||
qint64 n = 0;
|
||||
EXPECT_NO_THROW(n = randN(maxValue));
|
||||
EXPECT_TRUE((n >= 0) && (n < maxValue));
|
||||
QString name;
|
||||
EXPECT_NO_THROW(name = randomFirstName());
|
||||
EXPECT_FALSE(name.isEmpty());
|
||||
EXPECT_NO_THROW(name = randomLastName());
|
||||
EXPECT_FALSE(name.isEmpty());
|
||||
EXPECT_NO_THROW(randomUser());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(BridgeUtils, ElideLongString) {
|
||||
std::function const test = [](QString const &input, qint32 maxLength, QString const &expected) -> bool {
|
||||
QString output;
|
||||
EXPECT_NO_THROW(output = elideLongString(input, maxLength));
|
||||
return output == expected;
|
||||
};
|
||||
|
||||
EXPECT_TRUE(test( "", 0, ""));
|
||||
EXPECT_TRUE(test("1234", 4, "1234"));
|
||||
EXPECT_TRUE(test("123", 2, "..."));
|
||||
EXPECT_TRUE(test("1234567890", 8, "12...90"));
|
||||
EXPECT_TRUE(test("1234567890", 10, "1234567890"));
|
||||
EXPECT_TRUE(test("1234567890", 100, "1234567890"));
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
QString const testQWhat = "What";
|
||||
QString const testDetails = "Some details";
|
||||
QString const testFunction = "function";
|
||||
QByteArray const testAttachment = QString("Some data").toLocal8Bit();
|
||||
Exception const testException(testQWhat, testDetails, testFunction, testAttachment);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(Exceptions, ExceptionConstructor) {
|
||||
// Default exception
|
||||
Exception const emptyException;
|
||||
EXPECT_TRUE(emptyException.qwhat().isEmpty());
|
||||
EXPECT_EQ(strlen(emptyException.what()), 0);
|
||||
EXPECT_EQ(emptyException.attachment().size(), 0);
|
||||
EXPECT_TRUE(emptyException.details().isEmpty());
|
||||
EXPECT_TRUE(emptyException.detailedWhat().isEmpty());
|
||||
|
||||
// Fully detailed exception
|
||||
EXPECT_EQ(testException.qwhat(), testQWhat);
|
||||
EXPECT_EQ(QString::fromLocal8Bit(testException.what()), testQWhat);
|
||||
EXPECT_EQ(testException.details(), testDetails);
|
||||
EXPECT_EQ(testException.attachment(), testAttachment);
|
||||
QString const detailed = testException.detailedWhat();
|
||||
EXPECT_TRUE(detailed.contains(testQWhat));
|
||||
EXPECT_TRUE(detailed.contains(testFunction));
|
||||
EXPECT_TRUE(detailed.contains(testDetails));
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(Exceptions, ExceptionCopyMoveConstructors) {
|
||||
Exception const e(testQWhat, testDetails, testFunction, testAttachment);
|
||||
|
||||
// Check copy-constructor
|
||||
Exception eCopied(e);
|
||||
EXPECT_EQ(eCopied.qwhat(), testQWhat);
|
||||
EXPECT_EQ(eCopied.details(), testDetails);
|
||||
EXPECT_EQ(eCopied.function(), testFunction);
|
||||
EXPECT_EQ(eCopied.attachment(), testAttachment);
|
||||
|
||||
// Check move-constructor
|
||||
Exception eMoved(std::move(eCopied));
|
||||
EXPECT_EQ(eMoved.qwhat(), testQWhat);
|
||||
EXPECT_EQ(eMoved.details(), testDetails);
|
||||
EXPECT_EQ(eMoved.function(), testFunction);
|
||||
EXPECT_EQ(eMoved.attachment(), testAttachment);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(Exceptions, ExceptionThrow) {
|
||||
std::function t = []() { throw testException; };
|
||||
EXPECT_THROW(t(), Exception);
|
||||
EXPECT_THROW(t(), std::exception);
|
||||
bool caught = false;
|
||||
try {
|
||||
t();
|
||||
} catch (Exception const &e) {
|
||||
caught = true;
|
||||
EXPECT_EQ(e.detailedWhat(), testException.detailedWhat());
|
||||
}
|
||||
EXPECT_TRUE(caught);
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// clazy:excludeall=lambda-in-connect
|
||||
|
||||
#include "TestWorker.h"
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
qint32 dummyArgc = 1; ///< A dummy int value because QCoreApplication constructor requires a reference to it.
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Workers::Workers()
|
||||
: testing::Test()
|
||||
, app_(dummyArgc, nullptr) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void Workers::SetUp() {
|
||||
Test::SetUp();
|
||||
|
||||
EXPECT_NO_THROW(worker_ = new TestWorker);
|
||||
|
||||
QObject::connect(worker_, &TestWorker::started, [&]() { results_.started = true; });
|
||||
QObject::connect(worker_, &TestWorker::finished, [&]() { results_.finished = true; });
|
||||
QObject::connect(worker_, &TestWorker::finished, &loop_, &QEventLoop::quit);
|
||||
QObject::connect(worker_, &TestWorker::error, [&] { results_.error = true; });
|
||||
QObject::connect(worker_, &TestWorker::error, &loop_, &QEventLoop::quit);
|
||||
QObject::connect(worker_, &TestWorker::error, [&] { results_.error = true; });
|
||||
QObject::connect(worker_, &TestWorker::error, &loop_, &QEventLoop::quit);
|
||||
QObject::connect(worker_, &TestWorker::cancelled, [&] { results_.cancelled = true; });
|
||||
QObject::connect(worker_, &TestWorker::cancelled, &loop_, &QEventLoop::quit);
|
||||
|
||||
overseer_ = std::make_unique<Overseer>(worker_, nullptr);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void Workers::TearDown() {
|
||||
EXPECT_NO_FATAL_FAILURE(overseer_.reset());
|
||||
Test::TearDown();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] lifetimeMs The lifetime of the worker in milliseconds.
|
||||
/// \param[in] willSucceed Will the worker succeed (emit finished) or fail (emit error).
|
||||
//****************************************************************************************************************************************************
|
||||
TestWorker::TestWorker()
|
||||
: Worker(nullptr) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] lifetimeMs The lifetime of the worker in milliseconds.
|
||||
//****************************************************************************************************************************************************
|
||||
void TestWorker::setLifetime(qint64 lifetimeMs) {
|
||||
lifetimeMs_ = lifetimeMs;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] willSucceed Will the worker succeed?
|
||||
//****************************************************************************************************************************************************
|
||||
void TestWorker::setWillSucceed(bool willSucceed) {
|
||||
willSucceed_ = willSucceed;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void TestWorker::run() {
|
||||
emit started();
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
while (true) {
|
||||
if (cancelled_.loadRelaxed()) {
|
||||
emit cancelled();
|
||||
return;
|
||||
}
|
||||
if (timer.elapsed() >= lifetimeMs_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (willSucceed_) {
|
||||
emit finished();
|
||||
} else {
|
||||
emit error(QString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void TestWorker::cancel() {
|
||||
cancelled_.storeRelaxed(1);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST_F(Workers, SuccessfulWorker) {
|
||||
worker_->setLifetime(10);
|
||||
worker_->setWillSucceed(true);
|
||||
|
||||
EXPECT_NO_THROW(overseer_->startWorker(false));
|
||||
EXPECT_NO_THROW(loop_.exec());
|
||||
|
||||
EXPECT_TRUE(results_.started);
|
||||
EXPECT_TRUE(results_.finished);
|
||||
EXPECT_FALSE(results_.error);
|
||||
EXPECT_FALSE(results_.cancelled);
|
||||
|
||||
EXPECT_TRUE(overseer_->worker() != nullptr); // overseer started without autorelease.
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST_F(Workers, ErrorWorker) {
|
||||
worker_->setLifetime(10);
|
||||
worker_->setWillSucceed(false);
|
||||
|
||||
EXPECT_NO_THROW(overseer_->startWorker(true));
|
||||
EXPECT_NO_THROW(loop_.exec());
|
||||
|
||||
EXPECT_TRUE(results_.started);
|
||||
EXPECT_FALSE(results_.finished);
|
||||
EXPECT_TRUE(results_.error);
|
||||
EXPECT_FALSE(results_.cancelled);
|
||||
|
||||
EXPECT_TRUE(overseer_->worker() == nullptr); // overseer started with autorelease.
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST_F(Workers, CancelledWorker) {
|
||||
worker_->setLifetime(10000);
|
||||
worker_->setWillSucceed(true);
|
||||
EXPECT_NO_THROW(overseer_->startWorker(false));
|
||||
EXPECT_NO_THROW(QTimer::singleShot(10, [&]() { worker_->cancel(); }));
|
||||
|
||||
EXPECT_NO_THROW(loop_.exec());
|
||||
|
||||
EXPECT_TRUE(results_.started);
|
||||
EXPECT_FALSE(results_.finished);
|
||||
EXPECT_FALSE(results_.error);
|
||||
EXPECT_TRUE(results_.cancelled);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST_F(Workers, Wait) {
|
||||
worker_->setLifetime(10000);
|
||||
worker_->setWillSucceed(true);
|
||||
overseer_->startWorker(true);
|
||||
|
||||
bool isFinished = false;
|
||||
EXPECT_NO_THROW(isFinished = overseer_->isFinished());
|
||||
EXPECT_FALSE(isFinished);
|
||||
|
||||
EXPECT_NO_THROW(isFinished = overseer_->wait(10));
|
||||
EXPECT_FALSE(isFinished);
|
||||
|
||||
worker_->cancel();
|
||||
|
||||
EXPECT_NO_THROW(isFinished = overseer_->wait(10000));
|
||||
EXPECT_TRUE(isFinished);
|
||||
|
||||
EXPECT_NO_THROW(isFinished = overseer_->isFinished());
|
||||
EXPECT_TRUE(isFinished);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_GUI_TEST_WORKER_H
|
||||
#define BRIDGE_GUI_TEST_WORKER_H
|
||||
|
||||
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Test worker class.
|
||||
///
|
||||
/// This worker simply waits:
|
||||
/// - For a specified amount of time and will succeed (emit finished()) or fail (emit error()) based on its parameters.
|
||||
/// - to be cancelled (and will emit cancelled in that case).
|
||||
//****************************************************************************************************************************************************
|
||||
class TestWorker : public bridgepp::Worker {
|
||||
Q_OBJECT
|
||||
public: // member functions.
|
||||
TestWorker(); ///< Default constructor.
|
||||
TestWorker(TestWorker const &) = delete; ///< Disabled copy-constructor.
|
||||
TestWorker(TestWorker &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~TestWorker() override = default; ///< Destructor.
|
||||
TestWorker &operator=(TestWorker const &) = delete; ///< Disabled assignment operator.
|
||||
TestWorker &operator=(TestWorker &&) = delete; ///< Disabled move assignment operator.
|
||||
void setLifetime(qint64 lifetimeMs); ///< Set the lifetime of the worker.
|
||||
void setWillSucceed(bool willSucceed); ///< Set if the worker will succeed.
|
||||
void run() override; ///< Run the worker.
|
||||
void cancel(); ///< Cancel the worker.
|
||||
|
||||
private: // data members
|
||||
qint64 lifetimeMs_ { 10 }; ///< The lifetime of the worker in milliseconds.
|
||||
bool willSucceed_ { true }; ///< Will the worker succeed?
|
||||
QAtomicInteger<char> cancelled_; ///< Has the worker been cancelled.
|
||||
};
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Fixture class for worker tests.
|
||||
//****************************************************************************************************************************************************
|
||||
class Workers : public testing::Test {
|
||||
public: // member functions.
|
||||
Workers(); ///< Default constructor.
|
||||
Workers(Workers const &) = delete; ///< Disabled copy-constructor.
|
||||
Workers(Workers &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~Workers() = default; ///< Destructor.
|
||||
Workers &operator=(Workers const &) = delete; ///< Disabled assignment operator.
|
||||
Workers &operator=(Workers &&) = delete; ///< Disabled move assignment operator.
|
||||
|
||||
protected: // member functions.
|
||||
void SetUp() override; ///< Setup the fixture.
|
||||
void TearDown() override; ///< Tear down the fixture.
|
||||
|
||||
protected: // data type
|
||||
struct Results {
|
||||
bool started { false };
|
||||
bool finished { false };
|
||||
bool error { false };
|
||||
bool cancelled { false };
|
||||
}; ///< Test results data type
|
||||
|
||||
protected: // data members
|
||||
QCoreApplication app_; ///< The Qt application required for event loop.
|
||||
bridgepp::UPOverseer overseer_; ///< The overseer for the worker.
|
||||
TestWorker *worker_ { nullptr }; ///< The worker.
|
||||
QEventLoop loop_; ///< The event loop.
|
||||
Results results_; ///< The test results.
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_GUI_TEST_WORKER_H
|
|
@ -20,7 +20,7 @@
|
|||
#define BRIDGE_PP_TESTER_BRIDGE_UTILS_H
|
||||
|
||||
|
||||
#include <bridgepp/User/User.h>
|
||||
#include "User/User.h"
|
||||
|
||||
|
||||
namespace bridgepp {
|
||||
|
|
|
@ -87,6 +87,14 @@ QString Exception::details() const noexcept {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The function that threw the exception.
|
||||
//****************************************************************************************************************************************************
|
||||
QString Exception::function() const noexcept {
|
||||
return function_;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The attachment for the exception.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -109,4 +117,5 @@ QString Exception::detailedWhat() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
} // namespace bridgepp
|
||||
|
|
|
@ -42,6 +42,7 @@ public: // member functions
|
|||
QString qwhat() const noexcept; ///< Return the description of the exception as a QString
|
||||
const char *what() const noexcept override; ///< Return the description of the exception as C style string
|
||||
QString details() const noexcept; ///< Return the details for the exception
|
||||
QString function() const noexcept; ///< Return the function that threw the exception.
|
||||
QByteArray attachment() const noexcept; ///< Return the attachment for the exception.
|
||||
QString detailedWhat() const; ///< Return the detailed description of the message (i.e. including the function name and the details).
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include "GRPCClient.h"
|
||||
#include "GRPCUtils.h"
|
||||
#include "GRPCErrors.h"
|
||||
#include "../BridgeUtils.h"
|
||||
#include "../Exception/Exception.h"
|
||||
#include "../ProcessMonitor.h"
|
||||
#include "../Log/LogUtils.h"
|
||||
|
@ -295,6 +294,24 @@ grpc::Status GRPCClient::isAllMailVisible(bool &outIsVisible) {
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[out] outIsDisabled The value for the property
|
||||
/// \return The status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::isTelemetryDisabled(bool &outIsDisabled) {
|
||||
return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTelemetryDisabled, outIsDisabled), __FUNCTION__);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[out] isDisabled The new value for the property
|
||||
/// \return The status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::setIsTelemetryDisabled(bool isDisabled) {
|
||||
return this->logGRPCCallStatus(this->setBool(&Bridge::Stub::SetIsTelemetryDisabled, isDisabled), __FUNCTION__);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] isVisible The new value for the property.
|
||||
/// \return The status for the gRPC call.
|
||||
|
|
|
@ -71,6 +71,8 @@ public: // member functions.
|
|||
grpc::Status setIsBetaEnabled(bool enabled); ///< Performs the 'setIsBetaEnabled' gRPC call.
|
||||
grpc::Status isAllMailVisible(bool &outIsVisible); ///< Performs the "isAllMailVisible" gRPC call.
|
||||
grpc::Status setIsAllMailVisible(bool isVisible); ///< Performs the 'setIsAllMailVisible' gRPC call.
|
||||
grpc::Status isTelemetryDisabled(bool &outIsDisabled); ///< Performs the 'setIsTelemetryDisabled' gRPC call.
|
||||
grpc::Status setIsTelemetryDisabled(bool isDisabled); ///< Performs the 'isTelemetryDisabled' gRPC call.
|
||||
grpc::Status colorSchemeName(QString &outName); ///< Performs the "colorSchemeName' gRPC call.
|
||||
grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call.
|
||||
grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call.
|
||||
|
|
|
@ -34,6 +34,8 @@ static const char* Bridge_method_names[] = {
|
|||
"/grpc.Bridge/IsBetaEnabled",
|
||||
"/grpc.Bridge/SetIsAllMailVisible",
|
||||
"/grpc.Bridge/IsAllMailVisible",
|
||||
"/grpc.Bridge/SetIsTelemetryDisabled",
|
||||
"/grpc.Bridge/IsTelemetryDisabled",
|
||||
"/grpc.Bridge/GoOs",
|
||||
"/grpc.Bridge/TriggerReset",
|
||||
"/grpc.Bridge/Version",
|
||||
|
@ -98,49 +100,51 @@ Bridge::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, co
|
|||
, rpcmethod_IsBetaEnabled_(Bridge_method_names[9], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsAllMailVisible_(Bridge_method_names[10], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsAllMailVisible_(Bridge_method_names[11], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GoOs_(Bridge_method_names[12], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_TriggerReset_(Bridge_method_names[13], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Version_(Bridge_method_names[14], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LogsPath_(Bridge_method_names[15], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LicensePath_(Bridge_method_names[16], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ReleaseNotesPageLink_(Bridge_method_names[17], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_DependencyLicensesLink_(Bridge_method_names[18], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LandingPageLink_(Bridge_method_names[19], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetColorSchemeName_(Bridge_method_names[20], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ColorSchemeName_(Bridge_method_names[21], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CurrentEmailClient_(Bridge_method_names[22], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ReportBug_(Bridge_method_names[23], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ExportTLSCertificates_(Bridge_method_names[24], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ForceLauncher_(Bridge_method_names[25], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetMainExecutable_(Bridge_method_names[26], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login_(Bridge_method_names[27], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login2FA_(Bridge_method_names[28], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login2Passwords_(Bridge_method_names[29], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LoginAbort_(Bridge_method_names[30], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CheckUpdate_(Bridge_method_names[31], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_InstallUpdate_(Bridge_method_names[32], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsAutomaticUpdateOn_(Bridge_method_names[33], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsAutomaticUpdateOn_(Bridge_method_names[34], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_DiskCachePath_(Bridge_method_names[35], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetDiskCachePath_(Bridge_method_names[36], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsDoHEnabled_(Bridge_method_names[37], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsDoHEnabled_(Bridge_method_names[38], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_MailServerSettings_(Bridge_method_names[39], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetMailServerSettings_(Bridge_method_names[40], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Hostname_(Bridge_method_names[41], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsPortFree_(Bridge_method_names[42], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_AvailableKeychains_(Bridge_method_names[43], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetCurrentKeychain_(Bridge_method_names[44], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CurrentKeychain_(Bridge_method_names[45], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GetUserList_(Bridge_method_names[46], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GetUser_(Bridge_method_names[47], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetUserSplitMode_(Bridge_method_names[48], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SendBadEventUserFeedback_(Bridge_method_names[49], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LogoutUser_(Bridge_method_names[50], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_RemoveUser_(Bridge_method_names[51], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ConfigureUserAppleMail_(Bridge_method_names[52], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_RunEventStream_(Bridge_method_names[53], options.suffix_for_stats(),::grpc::internal::RpcMethod::SERVER_STREAMING, channel)
|
||||
, rpcmethod_StopEventStream_(Bridge_method_names[54], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsTelemetryDisabled_(Bridge_method_names[12], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsTelemetryDisabled_(Bridge_method_names[13], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GoOs_(Bridge_method_names[14], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_TriggerReset_(Bridge_method_names[15], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Version_(Bridge_method_names[16], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LogsPath_(Bridge_method_names[17], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LicensePath_(Bridge_method_names[18], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ReleaseNotesPageLink_(Bridge_method_names[19], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_DependencyLicensesLink_(Bridge_method_names[20], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LandingPageLink_(Bridge_method_names[21], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetColorSchemeName_(Bridge_method_names[22], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ColorSchemeName_(Bridge_method_names[23], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CurrentEmailClient_(Bridge_method_names[24], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ReportBug_(Bridge_method_names[25], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ExportTLSCertificates_(Bridge_method_names[26], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ForceLauncher_(Bridge_method_names[27], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetMainExecutable_(Bridge_method_names[28], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login_(Bridge_method_names[29], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login2FA_(Bridge_method_names[30], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Login2Passwords_(Bridge_method_names[31], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LoginAbort_(Bridge_method_names[32], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CheckUpdate_(Bridge_method_names[33], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_InstallUpdate_(Bridge_method_names[34], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsAutomaticUpdateOn_(Bridge_method_names[35], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsAutomaticUpdateOn_(Bridge_method_names[36], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_DiskCachePath_(Bridge_method_names[37], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetDiskCachePath_(Bridge_method_names[38], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetIsDoHEnabled_(Bridge_method_names[39], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsDoHEnabled_(Bridge_method_names[40], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_MailServerSettings_(Bridge_method_names[41], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetMailServerSettings_(Bridge_method_names[42], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_Hostname_(Bridge_method_names[43], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_IsPortFree_(Bridge_method_names[44], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_AvailableKeychains_(Bridge_method_names[45], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetCurrentKeychain_(Bridge_method_names[46], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_CurrentKeychain_(Bridge_method_names[47], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GetUserList_(Bridge_method_names[48], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_GetUser_(Bridge_method_names[49], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SetUserSplitMode_(Bridge_method_names[50], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_SendBadEventUserFeedback_(Bridge_method_names[51], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_LogoutUser_(Bridge_method_names[52], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_RemoveUser_(Bridge_method_names[53], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_ConfigureUserAppleMail_(Bridge_method_names[54], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
, rpcmethod_RunEventStream_(Bridge_method_names[55], options.suffix_for_stats(),::grpc::internal::RpcMethod::SERVER_STREAMING, channel)
|
||||
, rpcmethod_StopEventStream_(Bridge_method_names[56], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel)
|
||||
{}
|
||||
|
||||
::grpc::Status Bridge::Stub::CheckTokens(::grpc::ClientContext* context, const ::google::protobuf::StringValue& request, ::google::protobuf::StringValue* response) {
|
||||
|
@ -419,6 +423,52 @@ void Bridge::Stub::async::IsAllMailVisible(::grpc::ClientContext* context, const
|
|||
return result;
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Stub::SetIsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::BoolValue& request, ::google::protobuf::Empty* response) {
|
||||
return ::grpc::internal::BlockingUnaryCall< ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_SetIsTelemetryDisabled_, context, request, response);
|
||||
}
|
||||
|
||||
void Bridge::Stub::async::SetIsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::BoolValue* request, ::google::protobuf::Empty* response, std::function<void(::grpc::Status)> f) {
|
||||
::grpc::internal::CallbackUnaryCall< ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetIsTelemetryDisabled_, context, request, response, std::move(f));
|
||||
}
|
||||
|
||||
void Bridge::Stub::async::SetIsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::BoolValue* request, ::google::protobuf::Empty* response, ::grpc::ClientUnaryReactor* reactor) {
|
||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_SetIsTelemetryDisabled_, context, request, response, reactor);
|
||||
}
|
||||
|
||||
::grpc::ClientAsyncResponseReader< ::google::protobuf::Empty>* Bridge::Stub::PrepareAsyncSetIsTelemetryDisabledRaw(::grpc::ClientContext* context, const ::google::protobuf::BoolValue& request, ::grpc::CompletionQueue* cq) {
|
||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_SetIsTelemetryDisabled_, context, request);
|
||||
}
|
||||
|
||||
::grpc::ClientAsyncResponseReader< ::google::protobuf::Empty>* Bridge::Stub::AsyncSetIsTelemetryDisabledRaw(::grpc::ClientContext* context, const ::google::protobuf::BoolValue& request, ::grpc::CompletionQueue* cq) {
|
||||
auto* result =
|
||||
this->PrepareAsyncSetIsTelemetryDisabledRaw(context, request, cq);
|
||||
result->StartCall();
|
||||
return result;
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Stub::IsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::Empty& request, ::google::protobuf::BoolValue* response) {
|
||||
return ::grpc::internal::BlockingUnaryCall< ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_IsTelemetryDisabled_, context, request, response);
|
||||
}
|
||||
|
||||
void Bridge::Stub::async::IsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::Empty* request, ::google::protobuf::BoolValue* response, std::function<void(::grpc::Status)> f) {
|
||||
::grpc::internal::CallbackUnaryCall< ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_IsTelemetryDisabled_, context, request, response, std::move(f));
|
||||
}
|
||||
|
||||
void Bridge::Stub::async::IsTelemetryDisabled(::grpc::ClientContext* context, const ::google::protobuf::Empty* request, ::google::protobuf::BoolValue* response, ::grpc::ClientUnaryReactor* reactor) {
|
||||
::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_IsTelemetryDisabled_, context, request, response, reactor);
|
||||
}
|
||||
|
||||
::grpc::ClientAsyncResponseReader< ::google::protobuf::BoolValue>* Bridge::Stub::PrepareAsyncIsTelemetryDisabledRaw(::grpc::ClientContext* context, const ::google::protobuf::Empty& request, ::grpc::CompletionQueue* cq) {
|
||||
return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_IsTelemetryDisabled_, context, request);
|
||||
}
|
||||
|
||||
::grpc::ClientAsyncResponseReader< ::google::protobuf::BoolValue>* Bridge::Stub::AsyncIsTelemetryDisabledRaw(::grpc::ClientContext* context, const ::google::protobuf::Empty& request, ::grpc::CompletionQueue* cq) {
|
||||
auto* result =
|
||||
this->PrepareAsyncIsTelemetryDisabledRaw(context, request, cq);
|
||||
result->StartCall();
|
||||
return result;
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Stub::GoOs(::grpc::ClientContext* context, const ::google::protobuf::Empty& request, ::google::protobuf::StringValue* response) {
|
||||
return ::grpc::internal::BlockingUnaryCall< ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_GoOs_, context, request, response);
|
||||
}
|
||||
|
@ -1525,22 +1575,22 @@ Bridge::Service::Service() {
|
|||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[12],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->GoOs(ctx, req, resp);
|
||||
const ::google::protobuf::BoolValue* req,
|
||||
::google::protobuf::Empty* resp) {
|
||||
return service->SetIsTelemetryDisabled(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[13],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::Empty* resp) {
|
||||
return service->TriggerReset(ctx, req, resp);
|
||||
::google::protobuf::BoolValue* resp) {
|
||||
return service->IsTelemetryDisabled(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[14],
|
||||
|
@ -1550,17 +1600,17 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->Version(ctx, req, resp);
|
||||
return service->GoOs(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[15],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->LogsPath(ctx, req, resp);
|
||||
::google::protobuf::Empty* resp) {
|
||||
return service->TriggerReset(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[16],
|
||||
|
@ -1570,7 +1620,7 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->LicensePath(ctx, req, resp);
|
||||
return service->Version(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[17],
|
||||
|
@ -1580,7 +1630,7 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->ReleaseNotesPageLink(ctx, req, resp);
|
||||
return service->LogsPath(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[18],
|
||||
|
@ -1590,7 +1640,7 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->DependencyLicensesLink(ctx, req, resp);
|
||||
return service->LicensePath(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[19],
|
||||
|
@ -1600,17 +1650,17 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->LandingPageLink(ctx, req, resp);
|
||||
return service->ReleaseNotesPageLink(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[20],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::StringValue* req,
|
||||
::google::protobuf::Empty* resp) {
|
||||
return service->SetColorSchemeName(ctx, req, resp);
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->DependencyLicensesLink(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[21],
|
||||
|
@ -1620,11 +1670,31 @@ Bridge::Service::Service() {
|
|||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->ColorSchemeName(ctx, req, resp);
|
||||
return service->LandingPageLink(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[22],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::StringValue* req,
|
||||
::google::protobuf::Empty* resp) {
|
||||
return service->SetColorSchemeName(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[23],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
const ::google::protobuf::Empty* req,
|
||||
::google::protobuf::StringValue* resp) {
|
||||
return service->ColorSchemeName(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[24],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
::grpc::ServerContext* ctx,
|
||||
|
@ -1633,7 +1703,7 @@ Bridge::Service::Service() {
|
|||
return service->CurrentEmailClient(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[23],
|
||||
Bridge_method_names[25],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::ReportBugRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1643,7 +1713,7 @@ Bridge::Service::Service() {
|
|||
return service->ReportBug(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[24],
|
||||
Bridge_method_names[26],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1653,7 +1723,7 @@ Bridge::Service::Service() {
|
|||
return service->ExportTLSCertificates(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[25],
|
||||
Bridge_method_names[27],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1663,7 +1733,7 @@ Bridge::Service::Service() {
|
|||
return service->ForceLauncher(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[26],
|
||||
Bridge_method_names[28],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1673,7 +1743,7 @@ Bridge::Service::Service() {
|
|||
return service->SetMainExecutable(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[27],
|
||||
Bridge_method_names[29],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::LoginRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1683,7 +1753,7 @@ Bridge::Service::Service() {
|
|||
return service->Login(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[28],
|
||||
Bridge_method_names[30],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::LoginRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1693,7 +1763,7 @@ Bridge::Service::Service() {
|
|||
return service->Login2FA(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[29],
|
||||
Bridge_method_names[31],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::LoginRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1703,7 +1773,7 @@ Bridge::Service::Service() {
|
|||
return service->Login2Passwords(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[30],
|
||||
Bridge_method_names[32],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::LoginAbortRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1713,7 +1783,7 @@ Bridge::Service::Service() {
|
|||
return service->LoginAbort(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[31],
|
||||
Bridge_method_names[33],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1723,7 +1793,7 @@ Bridge::Service::Service() {
|
|||
return service->CheckUpdate(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[32],
|
||||
Bridge_method_names[34],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1733,7 +1803,7 @@ Bridge::Service::Service() {
|
|||
return service->InstallUpdate(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[33],
|
||||
Bridge_method_names[35],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1743,7 +1813,7 @@ Bridge::Service::Service() {
|
|||
return service->SetIsAutomaticUpdateOn(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[34],
|
||||
Bridge_method_names[36],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1753,7 +1823,7 @@ Bridge::Service::Service() {
|
|||
return service->IsAutomaticUpdateOn(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[35],
|
||||
Bridge_method_names[37],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1763,7 +1833,7 @@ Bridge::Service::Service() {
|
|||
return service->DiskCachePath(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[36],
|
||||
Bridge_method_names[38],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1773,7 +1843,7 @@ Bridge::Service::Service() {
|
|||
return service->SetDiskCachePath(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[37],
|
||||
Bridge_method_names[39],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::BoolValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1783,7 +1853,7 @@ Bridge::Service::Service() {
|
|||
return service->SetIsDoHEnabled(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[38],
|
||||
Bridge_method_names[40],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1793,7 +1863,7 @@ Bridge::Service::Service() {
|
|||
return service->IsDoHEnabled(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[39],
|
||||
Bridge_method_names[41],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::grpc::ImapSmtpSettings, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1803,7 +1873,7 @@ Bridge::Service::Service() {
|
|||
return service->MailServerSettings(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[40],
|
||||
Bridge_method_names[42],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::ImapSmtpSettings, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1813,7 +1883,7 @@ Bridge::Service::Service() {
|
|||
return service->SetMailServerSettings(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[41],
|
||||
Bridge_method_names[43],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1823,7 +1893,7 @@ Bridge::Service::Service() {
|
|||
return service->Hostname(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[42],
|
||||
Bridge_method_names[44],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Int32Value, ::google::protobuf::BoolValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1833,7 +1903,7 @@ Bridge::Service::Service() {
|
|||
return service->IsPortFree(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[43],
|
||||
Bridge_method_names[45],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::grpc::AvailableKeychainsResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1843,7 +1913,7 @@ Bridge::Service::Service() {
|
|||
return service->AvailableKeychains(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[44],
|
||||
Bridge_method_names[46],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1853,7 +1923,7 @@ Bridge::Service::Service() {
|
|||
return service->SetCurrentKeychain(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[45],
|
||||
Bridge_method_names[47],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::StringValue, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1863,7 +1933,7 @@ Bridge::Service::Service() {
|
|||
return service->CurrentKeychain(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[46],
|
||||
Bridge_method_names[48],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::grpc::UserListResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1873,7 +1943,7 @@ Bridge::Service::Service() {
|
|||
return service->GetUserList(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[47],
|
||||
Bridge_method_names[49],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::grpc::User, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1883,7 +1953,7 @@ Bridge::Service::Service() {
|
|||
return service->GetUser(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[48],
|
||||
Bridge_method_names[50],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::UserSplitModeRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1893,7 +1963,7 @@ Bridge::Service::Service() {
|
|||
return service->SetUserSplitMode(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[49],
|
||||
Bridge_method_names[51],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::UserBadEventFeedbackRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1903,7 +1973,7 @@ Bridge::Service::Service() {
|
|||
return service->SendBadEventUserFeedback(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[50],
|
||||
Bridge_method_names[52],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1913,7 +1983,7 @@ Bridge::Service::Service() {
|
|||
return service->LogoutUser(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[51],
|
||||
Bridge_method_names[53],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::StringValue, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1923,7 +1993,7 @@ Bridge::Service::Service() {
|
|||
return service->RemoveUser(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[52],
|
||||
Bridge_method_names[54],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::grpc::ConfigureAppleMailRequest, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1933,7 +2003,7 @@ Bridge::Service::Service() {
|
|||
return service->ConfigureUserAppleMail(ctx, req, resp);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[53],
|
||||
Bridge_method_names[55],
|
||||
::grpc::internal::RpcMethod::SERVER_STREAMING,
|
||||
new ::grpc::internal::ServerStreamingHandler< Bridge::Service, ::grpc::EventStreamRequest, ::grpc::StreamEvent>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -1943,7 +2013,7 @@ Bridge::Service::Service() {
|
|||
return service->RunEventStream(ctx, req, writer);
|
||||
}, this)));
|
||||
AddMethod(new ::grpc::internal::RpcServiceMethod(
|
||||
Bridge_method_names[54],
|
||||
Bridge_method_names[56],
|
||||
::grpc::internal::RpcMethod::NORMAL_RPC,
|
||||
new ::grpc::internal::RpcMethodHandler< Bridge::Service, ::google::protobuf::Empty, ::google::protobuf::Empty, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(
|
||||
[](Bridge::Service* service,
|
||||
|
@ -2041,6 +2111,20 @@ Bridge::Service::~Service() {
|
|||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Service::SetIsTelemetryDisabled(::grpc::ServerContext* context, const ::google::protobuf::BoolValue* request, ::google::protobuf::Empty* response) {
|
||||
(void) context;
|
||||
(void) request;
|
||||
(void) response;
|
||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Service::IsTelemetryDisabled(::grpc::ServerContext* context, const ::google::protobuf::Empty* request, ::google::protobuf::BoolValue* response) {
|
||||
(void) context;
|
||||
(void) request;
|
||||
(void) response;
|
||||
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "");
|
||||
}
|
||||
|
||||
::grpc::Status Bridge::Service::GoOs(::grpc::ServerContext* context, const ::google::protobuf::Empty* request, ::google::protobuf::StringValue* response) {
|
||||
(void) context;
|
||||
(void) request;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1673,7 +1673,7 @@ const char descriptor_table_protodef_bridge_2eproto[] PROTOBUF_SECTION_VARIABLE(
|
|||
"!SMTP_CONNECTION_MODE_CHANGE_ERROR\020\005*S\n\t"
|
||||
"ErrorCode\022\021\n\rUNKNOWN_ERROR\020\000\022\031\n\025TLS_CERT"
|
||||
"_EXPORT_ERROR\020\001\022\030\n\024TLS_KEY_EXPORT_ERROR\020"
|
||||
"\0022\360\035\n\006Bridge\022I\n\013CheckTokens\022\034.google.pro"
|
||||
"\0022\211\037\n\006Bridge\022I\n\013CheckTokens\022\034.google.pro"
|
||||
"tobuf.StringValue\032\034.google.protobuf.Stri"
|
||||
"ngValue\022\?\n\013AddLogEntry\022\030.grpc.AddLogEntr"
|
||||
"yRequest\032\026.google.protobuf.Empty\022:\n\010GuiR"
|
||||
|
@ -1693,84 +1693,88 @@ const char descriptor_table_protodef_bridge_2eproto[] PROTOBUF_SECTION_VARIABLE(
|
|||
"\n\023SetIsAllMailVisible\022\032.google.protobuf."
|
||||
"BoolValue\032\026.google.protobuf.Empty\022F\n\020IsA"
|
||||
"llMailVisible\022\026.google.protobuf.Empty\032\032."
|
||||
"google.protobuf.BoolValue\022<\n\004GoOs\022\026.goog"
|
||||
"le.protobuf.Empty\032\034.google.protobuf.Stri"
|
||||
"ngValue\022>\n\014TriggerReset\022\026.google.protobu"
|
||||
"f.Empty\032\026.google.protobuf.Empty\022\?\n\007Versi"
|
||||
"on\022\026.google.protobuf.Empty\032\034.google.prot"
|
||||
"obuf.StringValue\022@\n\010LogsPath\022\026.google.pr"
|
||||
"otobuf.Empty\032\034.google.protobuf.StringVal"
|
||||
"ue\022C\n\013LicensePath\022\026.google.protobuf.Empt"
|
||||
"y\032\034.google.protobuf.StringValue\022L\n\024Relea"
|
||||
"seNotesPageLink\022\026.google.protobuf.Empty\032"
|
||||
"\034.google.protobuf.StringValue\022N\n\026Depende"
|
||||
"ncyLicensesLink\022\026.google.protobuf.Empty\032"
|
||||
"\034.google.protobuf.StringValue\022G\n\017Landing"
|
||||
"PageLink\022\026.google.protobuf.Empty\032\034.googl"
|
||||
"e.protobuf.StringValue\022J\n\022SetColorScheme"
|
||||
"Name\022\034.google.protobuf.StringValue\032\026.goo"
|
||||
"gle.protobuf.Empty\022G\n\017ColorSchemeName\022\026."
|
||||
"google.protobuf.Empty\032\034.google.protobuf."
|
||||
"StringValue\022J\n\022CurrentEmailClient\022\026.goog"
|
||||
"le.protobuf.Empty\032\034.google.protobuf.Stri"
|
||||
"ngValue\022;\n\tReportBug\022\026.grpc.ReportBugReq"
|
||||
"uest\032\026.google.protobuf.Empty\022M\n\025ExportTL"
|
||||
"SCertificates\022\034.google.protobuf.StringVa"
|
||||
"lue\032\026.google.protobuf.Empty\022E\n\rForceLaun"
|
||||
"cher\022\034.google.protobuf.StringValue\032\026.goo"
|
||||
"gle.protobuf.Empty\022I\n\021SetMainExecutable\022"
|
||||
"\034.google.protobuf.StringValue\032\026.google.p"
|
||||
"rotobuf.Empty\0223\n\005Login\022\022.grpc.LoginReque"
|
||||
"st\032\026.google.protobuf.Empty\0226\n\010Login2FA\022\022"
|
||||
".grpc.LoginRequest\032\026.google.protobuf.Emp"
|
||||
"ty\022=\n\017Login2Passwords\022\022.grpc.LoginReques"
|
||||
"t\032\026.google.protobuf.Empty\022=\n\nLoginAbort\022"
|
||||
"\027.grpc.LoginAbortRequest\032\026.google.protob"
|
||||
"uf.Empty\022=\n\013CheckUpdate\022\026.google.protobu"
|
||||
"f.Empty\032\026.google.protobuf.Empty\022\?\n\rInsta"
|
||||
"llUpdate\022\026.google.protobuf.Empty\032\026.googl"
|
||||
"e.protobuf.Empty\022L\n\026SetIsAutomaticUpdate"
|
||||
"On\022\032.google.protobuf.BoolValue\032\026.google."
|
||||
"protobuf.Empty\022I\n\023IsAutomaticUpdateOn\022\026."
|
||||
"google.protobuf.Empty\032\032.google.protobuf."
|
||||
"BoolValue\022E\n\rDiskCachePath\022\026.google.prot"
|
||||
"google.protobuf.BoolValue\022L\n\026SetIsTeleme"
|
||||
"tryDisabled\022\032.google.protobuf.BoolValue\032"
|
||||
"\026.google.protobuf.Empty\022I\n\023IsTelemetryDi"
|
||||
"sabled\022\026.google.protobuf.Empty\032\032.google."
|
||||
"protobuf.BoolValue\022<\n\004GoOs\022\026.google.prot"
|
||||
"obuf.Empty\032\034.google.protobuf.StringValue"
|
||||
"\022H\n\020SetDiskCachePath\022\034.google.protobuf.S"
|
||||
"tringValue\032\026.google.protobuf.Empty\022E\n\017Se"
|
||||
"tIsDoHEnabled\022\032.google.protobuf.BoolValu"
|
||||
"e\032\026.google.protobuf.Empty\022B\n\014IsDoHEnable"
|
||||
"d\022\026.google.protobuf.Empty\032\032.google.proto"
|
||||
"buf.BoolValue\022D\n\022MailServerSettings\022\026.go"
|
||||
"ogle.protobuf.Empty\032\026.grpc.ImapSmtpSetti"
|
||||
"ngs\022G\n\025SetMailServerSettings\022\026.grpc.Imap"
|
||||
"SmtpSettings\032\026.google.protobuf.Empty\022@\n\010"
|
||||
"Hostname\022\026.google.protobuf.Empty\032\034.googl"
|
||||
"e.protobuf.StringValue\022E\n\nIsPortFree\022\033.g"
|
||||
"oogle.protobuf.Int32Value\032\032.google.proto"
|
||||
"buf.BoolValue\022N\n\022AvailableKeychains\022\026.go"
|
||||
"ogle.protobuf.Empty\032 .grpc.AvailableKeyc"
|
||||
"hainsResponse\022J\n\022SetCurrentKeychain\022\034.go"
|
||||
"ogle.protobuf.StringValue\032\026.google.proto"
|
||||
"buf.Empty\022G\n\017CurrentKeychain\022\026.google.pr"
|
||||
"otobuf.Empty\032\034.google.protobuf.StringVal"
|
||||
"ue\022=\n\013GetUserList\022\026.google.protobuf.Empt"
|
||||
"y\032\026.grpc.UserListResponse\0223\n\007GetUser\022\034.g"
|
||||
"oogle.protobuf.StringValue\032\n.grpc.User\022F"
|
||||
"\n\020SetUserSplitMode\022\032.grpc.UserSplitModeR"
|
||||
"equest\032\026.google.protobuf.Empty\022U\n\030SendBa"
|
||||
"dEventUserFeedback\022!.grpc.UserBadEventFe"
|
||||
"edbackRequest\032\026.google.protobuf.Empty\022B\n"
|
||||
"\nLogoutUser\022\034.google.protobuf.StringValu"
|
||||
"e\032\026.google.protobuf.Empty\022B\n\nRemoveUser\022"
|
||||
"\034.google.protobuf.StringValue\032\026.google.p"
|
||||
"rotobuf.Empty\022Q\n\026ConfigureUserAppleMail\022"
|
||||
"\037.grpc.ConfigureAppleMailRequest\032\026.googl"
|
||||
"e.protobuf.Empty\022\?\n\016RunEventStream\022\030.grp"
|
||||
"c.EventStreamRequest\032\021.grpc.StreamEvent0"
|
||||
"\001\022A\n\017StopEventStream\022\026.google.protobuf.E"
|
||||
"mpty\032\026.google.protobuf.EmptyB6Z4github.c"
|
||||
"om/ProtonMail/proton-bridge/v3/internal/"
|
||||
"grpcb\006proto3"
|
||||
"\022>\n\014TriggerReset\022\026.google.protobuf.Empty"
|
||||
"\032\026.google.protobuf.Empty\022\?\n\007Version\022\026.go"
|
||||
"ogle.protobuf.Empty\032\034.google.protobuf.St"
|
||||
"ringValue\022@\n\010LogsPath\022\026.google.protobuf."
|
||||
"Empty\032\034.google.protobuf.StringValue\022C\n\013L"
|
||||
"icensePath\022\026.google.protobuf.Empty\032\034.goo"
|
||||
"gle.protobuf.StringValue\022L\n\024ReleaseNotes"
|
||||
"PageLink\022\026.google.protobuf.Empty\032\034.googl"
|
||||
"e.protobuf.StringValue\022N\n\026DependencyLice"
|
||||
"nsesLink\022\026.google.protobuf.Empty\032\034.googl"
|
||||
"e.protobuf.StringValue\022G\n\017LandingPageLin"
|
||||
"k\022\026.google.protobuf.Empty\032\034.google.proto"
|
||||
"buf.StringValue\022J\n\022SetColorSchemeName\022\034."
|
||||
"google.protobuf.StringValue\032\026.google.pro"
|
||||
"tobuf.Empty\022G\n\017ColorSchemeName\022\026.google."
|
||||
"protobuf.Empty\032\034.google.protobuf.StringV"
|
||||
"alue\022J\n\022CurrentEmailClient\022\026.google.prot"
|
||||
"obuf.Empty\032\034.google.protobuf.StringValue"
|
||||
"\022;\n\tReportBug\022\026.grpc.ReportBugRequest\032\026."
|
||||
"google.protobuf.Empty\022M\n\025ExportTLSCertif"
|
||||
"icates\022\034.google.protobuf.StringValue\032\026.g"
|
||||
"oogle.protobuf.Empty\022E\n\rForceLauncher\022\034."
|
||||
"google.protobuf.StringValue\032\026.google.pro"
|
||||
"tobuf.Empty\022I\n\021SetMainExecutable\022\034.googl"
|
||||
"e.protobuf.StringValue\032\026.google.protobuf"
|
||||
".Empty\0223\n\005Login\022\022.grpc.LoginRequest\032\026.go"
|
||||
"ogle.protobuf.Empty\0226\n\010Login2FA\022\022.grpc.L"
|
||||
"oginRequest\032\026.google.protobuf.Empty\022=\n\017L"
|
||||
"ogin2Passwords\022\022.grpc.LoginRequest\032\026.goo"
|
||||
"gle.protobuf.Empty\022=\n\nLoginAbort\022\027.grpc."
|
||||
"LoginAbortRequest\032\026.google.protobuf.Empt"
|
||||
"y\022=\n\013CheckUpdate\022\026.google.protobuf.Empty"
|
||||
"\032\026.google.protobuf.Empty\022\?\n\rInstallUpdat"
|
||||
"e\022\026.google.protobuf.Empty\032\026.google.proto"
|
||||
"buf.Empty\022L\n\026SetIsAutomaticUpdateOn\022\032.go"
|
||||
"ogle.protobuf.BoolValue\032\026.google.protobu"
|
||||
"f.Empty\022I\n\023IsAutomaticUpdateOn\022\026.google."
|
||||
"protobuf.Empty\032\032.google.protobuf.BoolVal"
|
||||
"ue\022E\n\rDiskCachePath\022\026.google.protobuf.Em"
|
||||
"pty\032\034.google.protobuf.StringValue\022H\n\020Set"
|
||||
"DiskCachePath\022\034.google.protobuf.StringVa"
|
||||
"lue\032\026.google.protobuf.Empty\022E\n\017SetIsDoHE"
|
||||
"nabled\022\032.google.protobuf.BoolValue\032\026.goo"
|
||||
"gle.protobuf.Empty\022B\n\014IsDoHEnabled\022\026.goo"
|
||||
"gle.protobuf.Empty\032\032.google.protobuf.Boo"
|
||||
"lValue\022D\n\022MailServerSettings\022\026.google.pr"
|
||||
"otobuf.Empty\032\026.grpc.ImapSmtpSettings\022G\n\025"
|
||||
"SetMailServerSettings\022\026.grpc.ImapSmtpSet"
|
||||
"tings\032\026.google.protobuf.Empty\022@\n\010Hostnam"
|
||||
"e\022\026.google.protobuf.Empty\032\034.google.proto"
|
||||
"buf.StringValue\022E\n\nIsPortFree\022\033.google.p"
|
||||
"rotobuf.Int32Value\032\032.google.protobuf.Boo"
|
||||
"lValue\022N\n\022AvailableKeychains\022\026.google.pr"
|
||||
"otobuf.Empty\032 .grpc.AvailableKeychainsRe"
|
||||
"sponse\022J\n\022SetCurrentKeychain\022\034.google.pr"
|
||||
"otobuf.StringValue\032\026.google.protobuf.Emp"
|
||||
"ty\022G\n\017CurrentKeychain\022\026.google.protobuf."
|
||||
"Empty\032\034.google.protobuf.StringValue\022=\n\013G"
|
||||
"etUserList\022\026.google.protobuf.Empty\032\026.grp"
|
||||
"c.UserListResponse\0223\n\007GetUser\022\034.google.p"
|
||||
"rotobuf.StringValue\032\n.grpc.User\022F\n\020SetUs"
|
||||
"erSplitMode\022\032.grpc.UserSplitModeRequest\032"
|
||||
"\026.google.protobuf.Empty\022U\n\030SendBadEventU"
|
||||
"serFeedback\022!.grpc.UserBadEventFeedbackR"
|
||||
"equest\032\026.google.protobuf.Empty\022B\n\nLogout"
|
||||
"User\022\034.google.protobuf.StringValue\032\026.goo"
|
||||
"gle.protobuf.Empty\022B\n\nRemoveUser\022\034.googl"
|
||||
"e.protobuf.StringValue\032\026.google.protobuf"
|
||||
".Empty\022Q\n\026ConfigureUserAppleMail\022\037.grpc."
|
||||
"ConfigureAppleMailRequest\032\026.google.proto"
|
||||
"buf.Empty\022\?\n\016RunEventStream\022\030.grpc.Event"
|
||||
"StreamRequest\032\021.grpc.StreamEvent0\001\022A\n\017St"
|
||||
"opEventStream\022\026.google.protobuf.Empty\032\026."
|
||||
"google.protobuf.EmptyB6Z4github.com/Prot"
|
||||
"onMail/proton-bridge/v3/internal/grpcb\006p"
|
||||
"roto3"
|
||||
;
|
||||
static const ::_pbi::DescriptorTable* const descriptor_table_bridge_2eproto_deps[2] = {
|
||||
&::descriptor_table_google_2fprotobuf_2fempty_2eproto,
|
||||
|
@ -1778,7 +1782,7 @@ static const ::_pbi::DescriptorTable* const descriptor_table_bridge_2eproto_deps
|
|||
};
|
||||
static ::_pbi::once_flag descriptor_table_bridge_2eproto_once;
|
||||
const ::_pbi::DescriptorTable descriptor_table_bridge_2eproto = {
|
||||
false, false, 10532, descriptor_table_protodef_bridge_2eproto,
|
||||
false, false, 10685, descriptor_table_protodef_bridge_2eproto,
|
||||
"bridge.proto",
|
||||
&descriptor_table_bridge_2eproto_once, descriptor_table_bridge_2eproto_deps, 2, 64,
|
||||
schemas, file_default_instances, TableStruct_bridge_2eproto::offsets,
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
|
||||
#include "LogUtils.h"
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include "../BridgeUtils.h"
|
||||
#include "../Exception/Exception.h"
|
||||
|
||||
|
||||
namespace bridgepp {
|
||||
|
|
|
@ -289,6 +289,23 @@ func New(
|
|||
})
|
||||
fe.AddCmd(badEventCmd)
|
||||
|
||||
// Telemetry commands
|
||||
telemetryCmd := &ishell.Cmd{
|
||||
Name: "telemetry",
|
||||
Help: "choose whether usage diagnostics are collected or not",
|
||||
}
|
||||
telemetryCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "enable",
|
||||
Help: "Usage diagnostics collection will be enabled",
|
||||
Func: fe.enableTelemetry,
|
||||
})
|
||||
telemetryCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "disable",
|
||||
Help: "Usage diagnostics collection will be disabled",
|
||||
Func: fe.disableTelemetry,
|
||||
})
|
||||
fe.AddCmd(telemetryCmd)
|
||||
|
||||
go fe.watchEvents(eventCh)
|
||||
|
||||
go func() {
|
||||
|
|
|
@ -40,7 +40,7 @@ func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
|||
}
|
||||
|
||||
func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||
f.Println("More instructions about the Bridge can be found at\n\n https://protonmail.com/bridge")
|
||||
f.Println("More instructions about the Bridge can be found at\n\n https://proton.me/mail/bridge")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||
|
@ -195,6 +195,38 @@ func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableTelemetry(_ *ishell.Context) {
|
||||
if !f.bridge.GetTelemetryDisabled() {
|
||||
f.Println("Usage diagnostics collection is enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Usage diagnostics collection is disabled right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to enable usage diagnostics collection") {
|
||||
if err := f.bridge.SetTelemetryDisabled(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableTelemetry(_ *ishell.Context) {
|
||||
if f.bridge.GetTelemetryDisabled() {
|
||||
f.Println("Usage diagnostics collection is disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Usage diagnostics collection is enabled right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to disable usage diagnostics collection") {
|
||||
if err := f.bridge.SetTelemetryDisabled(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
|
||||
if gluonDir := f.bridge.GetGluonCacheDir(); gluonDir != "" {
|
||||
f.Println("The current message cache location is:", gluonDir)
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc v3.21.3
|
||||
// source: bridge.proto
|
||||
|
||||
package grpc
|
||||
|
@ -4803,8 +4803,8 @@ var file_bridge_proto_rawDesc = []byte{
|
|||
0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x4c, 0x53,
|
||||
0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52,
|
||||
0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f,
|
||||
0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x32, 0xf0,
|
||||
0x1d, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, 0x68, 0x65,
|
||||
0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x32, 0x89,
|
||||
0x1f, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, 0x68, 0x65,
|
||||
0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
|
||||
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
|
@ -4855,199 +4855,208 @@ var file_bridge_proto_rawDesc = []byte{
|
|||
0x6c, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f,
|
||||
0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52,
|
||||
0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67,
|
||||
0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x54,
|
||||
0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||
0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65,
|
||||
0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
||||
0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a,
|
||||
0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a,
|
||||
0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40,
|
||||
0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74,
|
||||
0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e,
|
||||
0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14,
|
||||
0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65,
|
||||
0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65,
|
||||
0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73,
|
||||
0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61,
|
||||
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
|
||||
0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53,
|
||||
0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||
0x47, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72,
|
||||
0x65, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63,
|
||||
0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61,
|
||||
0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75,
|
||||
0x67, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42,
|
||||
0x75, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65,
|
||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12,
|
||||
0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f,
|
||||
0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69,
|
||||
0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a,
|
||||
0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72,
|
||||
0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x12, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65,
|
||||
0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61,
|
||||
0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72,
|
||||
0x12, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75,
|
||||
0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x4c,
|
||||
0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x12, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69,
|
||||
0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||
0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||
0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d,
|
||||
0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a,
|
||||
0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||
0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67,
|
||||
0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41,
|
||||
0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e,
|
||||
0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d,
|
||||
0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a,
|
||||
0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49,
|
||||
0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f,
|
||||
0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61,
|
||||
0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||
0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a,
|
||||
0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74,
|
||||
0x68, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73,
|
||||
0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f,
|
||||
0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42,
|
||||
0x0a, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x12, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70,
|
||||
0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d,
|
||||
0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74,
|
||||
0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x12, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65,
|
||||
0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76,
|
||||
0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61,
|
||||
0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
||||
0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68,
|
||||
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65,
|
||||
0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e,
|
||||
0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e,
|
||||
0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73,
|
||||
0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x12, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48,
|
||||
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x12, 0x4d,
|
||||
0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
|
||||
0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0a,
|
||||
0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74,
|
||||
0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
|
||||
0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73,
|
||||
0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33,
|
||||
0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x79, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||
0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55,
|
||||
0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70,
|
||||
0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55,
|
||||
0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||
0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61,
|
||||
0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55,
|
||||
0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73,
|
||||
0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x1a, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10,
|
||||
0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65,
|
||||
0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69,
|
||||
0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b,
|
||||
0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53,
|
||||
0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46,
|
||||
0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55,
|
||||
0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62,
|
||||
0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72,
|
||||
0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x4c,
|
||||
0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69,
|
||||
0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
||||
0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e,
|
||||
0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70,
|
||||
0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65,
|
||||
0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a,
|
||||
0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
|
||||
0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65,
|
||||
0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41,
|
||||
0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x6e, 0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d,
|
||||
0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2d, 0x62, 0x72, 0x69, 0x64, 0x67,
|
||||
0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72,
|
||||
0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -5214,106 +5223,110 @@ var file_bridge_proto_depIdxs = []int32{
|
|||
72, // 69: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty
|
||||
73, // 70: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue
|
||||
72, // 71: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty
|
||||
72, // 72: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty
|
||||
72, // 73: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty
|
||||
72, // 74: grpc.Bridge.Version:input_type -> google.protobuf.Empty
|
||||
72, // 75: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty
|
||||
72, // 76: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty
|
||||
72, // 77: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty
|
||||
72, // 78: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty
|
||||
72, // 79: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty
|
||||
71, // 80: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue
|
||||
72, // 81: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty
|
||||
72, // 82: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty
|
||||
9, // 83: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest
|
||||
71, // 84: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
||||
71, // 85: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue
|
||||
71, // 86: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue
|
||||
10, // 87: grpc.Bridge.Login:input_type -> grpc.LoginRequest
|
||||
10, // 88: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest
|
||||
10, // 89: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest
|
||||
11, // 90: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest
|
||||
72, // 91: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty
|
||||
72, // 92: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty
|
||||
73, // 93: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue
|
||||
72, // 94: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty
|
||||
72, // 95: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty
|
||||
71, // 96: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue
|
||||
73, // 97: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue
|
||||
72, // 98: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty
|
||||
72, // 99: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty
|
||||
12, // 100: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings
|
||||
72, // 101: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty
|
||||
74, // 102: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value
|
||||
72, // 103: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty
|
||||
71, // 104: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue
|
||||
72, // 105: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty
|
||||
72, // 106: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty
|
||||
71, // 107: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue
|
||||
15, // 108: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest
|
||||
16, // 109: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest
|
||||
71, // 110: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue
|
||||
71, // 111: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue
|
||||
18, // 112: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest
|
||||
19, // 113: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
||||
72, // 114: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
||||
71, // 115: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
||||
72, // 116: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
||||
8, // 117: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
||||
72, // 118: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
||||
72, // 119: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
||||
73, // 120: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
||||
72, // 121: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
||||
73, // 122: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
||||
72, // 123: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
||||
73, // 124: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
||||
72, // 125: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
||||
73, // 126: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
||||
71, // 127: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
||||
72, // 128: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
||||
71, // 129: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
||||
71, // 130: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
||||
71, // 131: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
||||
71, // 132: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
||||
71, // 133: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
||||
71, // 134: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
||||
72, // 135: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
||||
71, // 136: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
||||
71, // 137: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
||||
72, // 138: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
||||
72, // 139: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
||||
72, // 140: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
||||
72, // 141: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
||||
72, // 142: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
||||
72, // 143: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
||||
72, // 144: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
||||
72, // 145: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
||||
72, // 146: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
||||
72, // 147: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
||||
72, // 148: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
||||
73, // 149: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
||||
71, // 150: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
||||
72, // 151: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
||||
72, // 152: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
||||
73, // 153: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
||||
12, // 154: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
||||
72, // 155: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
||||
71, // 156: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
||||
73, // 157: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
||||
13, // 158: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
||||
72, // 159: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
||||
71, // 160: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
||||
17, // 161: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
||||
14, // 162: grpc.Bridge.GetUser:output_type -> grpc.User
|
||||
72, // 163: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
||||
72, // 164: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
||||
72, // 165: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
||||
72, // 166: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
||||
72, // 167: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
||||
20, // 168: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
||||
72, // 169: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
||||
115, // [115:170] is the sub-list for method output_type
|
||||
60, // [60:115] is the sub-list for method input_type
|
||||
73, // 72: grpc.Bridge.SetIsTelemetryDisabled:input_type -> google.protobuf.BoolValue
|
||||
72, // 73: grpc.Bridge.IsTelemetryDisabled:input_type -> google.protobuf.Empty
|
||||
72, // 74: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty
|
||||
72, // 75: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty
|
||||
72, // 76: grpc.Bridge.Version:input_type -> google.protobuf.Empty
|
||||
72, // 77: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty
|
||||
72, // 78: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty
|
||||
72, // 79: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty
|
||||
72, // 80: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty
|
||||
72, // 81: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty
|
||||
71, // 82: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue
|
||||
72, // 83: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty
|
||||
72, // 84: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty
|
||||
9, // 85: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest
|
||||
71, // 86: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue
|
||||
71, // 87: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue
|
||||
71, // 88: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue
|
||||
10, // 89: grpc.Bridge.Login:input_type -> grpc.LoginRequest
|
||||
10, // 90: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest
|
||||
10, // 91: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest
|
||||
11, // 92: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest
|
||||
72, // 93: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty
|
||||
72, // 94: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty
|
||||
73, // 95: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue
|
||||
72, // 96: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty
|
||||
72, // 97: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty
|
||||
71, // 98: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue
|
||||
73, // 99: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue
|
||||
72, // 100: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty
|
||||
72, // 101: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty
|
||||
12, // 102: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings
|
||||
72, // 103: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty
|
||||
74, // 104: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value
|
||||
72, // 105: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty
|
||||
71, // 106: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue
|
||||
72, // 107: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty
|
||||
72, // 108: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty
|
||||
71, // 109: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue
|
||||
15, // 110: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest
|
||||
16, // 111: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest
|
||||
71, // 112: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue
|
||||
71, // 113: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue
|
||||
18, // 114: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest
|
||||
19, // 115: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest
|
||||
72, // 116: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty
|
||||
71, // 117: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue
|
||||
72, // 118: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty
|
||||
8, // 119: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse
|
||||
72, // 120: grpc.Bridge.Quit:output_type -> google.protobuf.Empty
|
||||
72, // 121: grpc.Bridge.Restart:output_type -> google.protobuf.Empty
|
||||
73, // 122: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue
|
||||
72, // 123: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty
|
||||
73, // 124: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue
|
||||
72, // 125: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty
|
||||
73, // 126: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue
|
||||
72, // 127: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty
|
||||
73, // 128: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue
|
||||
72, // 129: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty
|
||||
73, // 130: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue
|
||||
71, // 131: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue
|
||||
72, // 132: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty
|
||||
71, // 133: grpc.Bridge.Version:output_type -> google.protobuf.StringValue
|
||||
71, // 134: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue
|
||||
71, // 135: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue
|
||||
71, // 136: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue
|
||||
71, // 137: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue
|
||||
71, // 138: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue
|
||||
72, // 139: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty
|
||||
71, // 140: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue
|
||||
71, // 141: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue
|
||||
72, // 142: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty
|
||||
72, // 143: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty
|
||||
72, // 144: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty
|
||||
72, // 145: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty
|
||||
72, // 146: grpc.Bridge.Login:output_type -> google.protobuf.Empty
|
||||
72, // 147: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty
|
||||
72, // 148: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty
|
||||
72, // 149: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty
|
||||
72, // 150: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty
|
||||
72, // 151: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty
|
||||
72, // 152: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty
|
||||
73, // 153: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue
|
||||
71, // 154: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue
|
||||
72, // 155: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty
|
||||
72, // 156: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty
|
||||
73, // 157: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue
|
||||
12, // 158: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings
|
||||
72, // 159: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty
|
||||
71, // 160: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue
|
||||
73, // 161: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue
|
||||
13, // 162: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse
|
||||
72, // 163: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty
|
||||
71, // 164: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue
|
||||
17, // 165: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse
|
||||
14, // 166: grpc.Bridge.GetUser:output_type -> grpc.User
|
||||
72, // 167: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty
|
||||
72, // 168: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty
|
||||
72, // 169: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty
|
||||
72, // 170: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty
|
||||
72, // 171: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty
|
||||
20, // 172: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent
|
||||
72, // 173: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty
|
||||
117, // [117:174] is the sub-list for method output_type
|
||||
60, // [60:117] is the sub-list for method input_type
|
||||
60, // [60:60] is the sub-list for extension type_name
|
||||
60, // [60:60] is the sub-list for extension extendee
|
||||
0, // [0:60] is the sub-list for field type_name
|
||||
|
|
|
@ -42,6 +42,8 @@ service Bridge {
|
|||
rpc IsBetaEnabled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
rpc SetIsAllMailVisible(google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
rpc IsAllMailVisible(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
rpc SetIsTelemetryDisabled(google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
rpc IsTelemetryDisabled(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
rpc GoOs(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
rpc TriggerReset(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc Version(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -214,6 +214,23 @@ func (s *Service) IsAllMailVisible(ctx context.Context, _ *emptypb.Empty) (*wrap
|
|||
return wrapperspb.Bool(s.bridge.GetShowAllMail()), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetIsTelemetryDisabled(_ context.Context, isDisabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||
s.log.WithField("isEnabled", isDisabled.Value).Debug("SetIsTelemetryDisabled")
|
||||
|
||||
if err := s.bridge.SetTelemetryDisabled(isDisabled.Value); err != nil {
|
||||
s.log.WithError(err).Error("Failed to set telemetry status")
|
||||
return nil, status.Errorf(codes.Internal, "failed to set telemetry status: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) IsTelemetryDisabled(_ context.Context, _ *emptypb.Empty) (*wrapperspb.BoolValue, error) {
|
||||
s.log.Debug("IsTelemetryDisabled")
|
||||
|
||||
return wrapperspb.Bool(s.bridge.GetTelemetryDisabled()), nil
|
||||
}
|
||||
|
||||
func (s *Service) GoOs(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||
s.log.Debug("GoOs") // TO-DO We can probably get rid of this and use QSysInfo::product name
|
||||
|
||||
|
|
|
@ -15,14 +15,18 @@
|
|||
// 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
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
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)
|
||||
}
|
||||
package sentry
|
||||
|
||||
import "os"
|
||||
|
||||
func GetSystemLang() string {
|
||||
lang := os.Getenv("LC_ALL")
|
||||
if lang == "" {
|
||||
lang = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
return lang
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLocaleUser = "GetUserDefaultLocaleName" // https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename
|
||||
defaultLocaleSystem = "GetSystemDefaultLocaleName" // https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getsystemdefaultlocalename
|
||||
localeNameMaxLength = 85 // https://learn.microsoft.com/en-us/windows/win32/intl/locale-name-constants
|
||||
)
|
||||
|
||||
func getLocale(dll *syscall.DLL, procName string) (string, error) {
|
||||
proc, err := dll.FindProc(procName)
|
||||
if err != nil {
|
||||
return "errProc", err
|
||||
}
|
||||
|
||||
b := make([]uint16, localeNameMaxLength)
|
||||
|
||||
r, _, err := proc.Call(uintptr(unsafe.Pointer(&b[0])), uintptr(localeNameMaxLength))
|
||||
if r == 0 || err != nil {
|
||||
return "errCall", err
|
||||
}
|
||||
|
||||
return syscall.UTF16ToString(b), nil
|
||||
}
|
||||
|
||||
func GetSystemLang() string {
|
||||
dll, err := syscall.LoadDLL("kernel32")
|
||||
if err != nil {
|
||||
return "errDll"
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = dll.Release()
|
||||
}()
|
||||
|
||||
if lang, err := getLocale(dll, defaultLocaleUser); err == nil {
|
||||
return lang
|
||||
}
|
||||
|
||||
lang, _ := getLocale(dll, defaultLocaleSystem)
|
||||
|
||||
return lang
|
||||
}
|
|
@ -18,7 +18,6 @@
|
|||
package sentry
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -29,6 +28,7 @@ import (
|
|||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gluon/reporter"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -50,7 +50,7 @@ func init() { //nolint:gochecknoinits
|
|||
Release: constants.AppVersion(appVersion),
|
||||
BeforeSend: EnhanceSentryEvent,
|
||||
Transport: sentrySyncTransport,
|
||||
ServerName: getProtectedHostname(),
|
||||
ServerName: GetProtectedHostname(),
|
||||
Environment: constants.BuildEnv,
|
||||
MaxBreadcrumbs: 50,
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func init() { //nolint:gochecknoinits
|
|||
|
||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.SetFingerprint([]string{"{{ default }}"})
|
||||
scope.SetUser(sentry.User{ID: getProtectedHostname()})
|
||||
scope.SetUser(sentry.User{ID: GetProtectedHostname()})
|
||||
})
|
||||
|
||||
sentry.Logger = log.New(
|
||||
|
@ -81,12 +81,17 @@ type Identifier interface {
|
|||
GetUserAgent() string
|
||||
}
|
||||
|
||||
func getProtectedHostname() string {
|
||||
func GetProtectedHostname() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(hostname)))
|
||||
return algo.HashBase64SHA256(hostname)
|
||||
}
|
||||
|
||||
func GetTimeZone() string {
|
||||
zone, offset := time.Now().Zone()
|
||||
return fmt.Sprintf("%s%+d", zone, offset/3600)
|
||||
}
|
||||
|
||||
// NewReporter creates new sentry reporter with appName and appVersion to report.
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func NewHeartbeat(manager HeartbeatManager, imapPort, smtpPort int, cacheDir, keychain string) Heartbeat {
|
||||
heartbeat := Heartbeat{
|
||||
log: logrus.WithField("pkg", "telemetry"),
|
||||
manager: manager,
|
||||
metrics: HeartbeatData{
|
||||
MeasurementGroup: "bridge.any.usage",
|
||||
Event: "bridge_heartbeat",
|
||||
},
|
||||
defaultIMAPPort: imapPort,
|
||||
defaultSMTPPort: smtpPort,
|
||||
defaultCache: cacheDir,
|
||||
defaultKeychain: keychain,
|
||||
}
|
||||
return heartbeat
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetRollout(val float64) {
|
||||
heartbeat.metrics.Dimensions.Rollout = strconv.Itoa(int(val * 100))
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetNbAccount(val int) {
|
||||
heartbeat.metrics.Values.NbAccount = val
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetAutoUpdate(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.AutoUpdate = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.AutoUpdate = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetAutoStart(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.AutoStart = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.AutoStart = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetBeta(val updater.Channel) {
|
||||
if val == updater.EarlyChannel {
|
||||
heartbeat.metrics.Dimensions.Beta = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.Beta = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetDoh(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.Doh = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.Doh = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetSplitMode(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.SplitMode = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.SplitMode = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetShowAllMail(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.ShowAllMail = dimensionON
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.ShowAllMail = dimensionOFF
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetIMAPConnectionMode(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.IMAPConnectionMode = dimensionSSL
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.IMAPConnectionMode = dimensionStartTLS
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetSMTPConnectionMode(val bool) {
|
||||
if val {
|
||||
heartbeat.metrics.Dimensions.SMTPConnectionMode = dimensionSSL
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.SMTPConnectionMode = dimensionStartTLS
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetIMAPPort(val int) {
|
||||
if val == heartbeat.defaultIMAPPort {
|
||||
heartbeat.metrics.Dimensions.IMAPPort = dimensionDefault
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.IMAPPort = dimensionCustom
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetSMTPPort(val int) {
|
||||
if val == heartbeat.defaultSMTPPort {
|
||||
heartbeat.metrics.Dimensions.SMTPPort = dimensionDefault
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.SMTPPort = dimensionCustom
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetCacheLocation(val string) {
|
||||
if val == heartbeat.defaultCache {
|
||||
heartbeat.metrics.Dimensions.CacheLocation = dimensionDefault
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.CacheLocation = dimensionCustom
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetKeyChainPref(val string) {
|
||||
if val == heartbeat.defaultKeychain {
|
||||
heartbeat.metrics.Dimensions.KeychainPref = dimensionDefault
|
||||
} else {
|
||||
heartbeat.metrics.Dimensions.KeychainPref = dimensionCustom
|
||||
}
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) SetPrevVersion(val string) {
|
||||
heartbeat.metrics.Dimensions.PrevVersion = val
|
||||
}
|
||||
|
||||
func (heartbeat *Heartbeat) TrySending() {
|
||||
if heartbeat.manager.IsTelemetryAvailable() {
|
||||
lastSent := heartbeat.manager.GetLastHeartbeatSent()
|
||||
now := time.Now()
|
||||
if now.Year() > lastSent.Year() || (now.Year() == lastSent.Year() && now.YearDay() > lastSent.YearDay()) {
|
||||
if !heartbeat.manager.SendHeartbeat(&heartbeat.metrics) {
|
||||
heartbeat.log.WithFields(logrus.Fields{
|
||||
"metrics": heartbeat.metrics,
|
||||
}).Error("Failed to send heartbeat")
|
||||
return
|
||||
}
|
||||
heartbeat.log.WithFields(logrus.Fields{
|
||||
"metrics": heartbeat.metrics,
|
||||
}).Info("Heartbeat sent")
|
||||
|
||||
if err := heartbeat.manager.SetLastHeartbeatSent(now); err != nil {
|
||||
heartbeat.log.WithError(err).Warn("Cannot save last heartbeat sent to the vault.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package telemetry_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
func TestHeartbeat_default_heartbeat(t *testing.T) {
|
||||
withHeartbeat(t, 1143, 1025, "/tmp", "defaultKeychain", func(hb *telemetry.Heartbeat, mock *mocks.MockHeartbeatManager) {
|
||||
data := telemetry.HeartbeatData{
|
||||
MeasurementGroup: "bridge.any.usage",
|
||||
Event: "bridge_heartbeat",
|
||||
Values: telemetry.HeartbeatValues{
|
||||
NbAccount: 1,
|
||||
},
|
||||
Dimensions: telemetry.HeartbeatDimensions{
|
||||
AutoUpdate: "on",
|
||||
AutoStart: "on",
|
||||
Beta: "off",
|
||||
Doh: "off",
|
||||
SplitMode: "off",
|
||||
ShowAllMail: "off",
|
||||
IMAPConnectionMode: "ssl",
|
||||
SMTPConnectionMode: "ssl",
|
||||
IMAPPort: "default",
|
||||
SMTPPort: "default",
|
||||
CacheLocation: "default",
|
||||
KeychainPref: "default",
|
||||
PrevVersion: "1.2.3",
|
||||
Rollout: "10",
|
||||
},
|
||||
}
|
||||
|
||||
mock.EXPECT().IsTelemetryAvailable().Return(true)
|
||||
mock.EXPECT().GetLastHeartbeatSent().Return(time.Date(2022, 6, 4, 0, 0, 0, 0, time.UTC))
|
||||
mock.EXPECT().SendHeartbeat(&data).Return(true)
|
||||
mock.EXPECT().SetLastHeartbeatSent(gomock.Any()).Return(nil)
|
||||
|
||||
hb.TrySending()
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeartbeat_already_sent_heartbeat(t *testing.T) {
|
||||
withHeartbeat(t, 1143, 1025, "/tmp", "defaultKeychain", func(hb *telemetry.Heartbeat, mock *mocks.MockHeartbeatManager) {
|
||||
mock.EXPECT().IsTelemetryAvailable().Return(true)
|
||||
mock.EXPECT().GetLastHeartbeatSent().Return(time.Now().Truncate(24 * time.Hour))
|
||||
|
||||
hb.TrySending()
|
||||
})
|
||||
}
|
||||
|
||||
func withHeartbeat(t *testing.T, imap, smtp int, cache, keychain string, tests func(hb *telemetry.Heartbeat, mock *mocks.MockHeartbeatManager)) {
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
|
||||
manager := mocks.NewMockHeartbeatManager(ctl)
|
||||
heartbeat := telemetry.NewHeartbeat(manager, imap, smtp, cache, keychain)
|
||||
|
||||
heartbeat.SetRollout(0.1)
|
||||
heartbeat.SetNbAccount(1)
|
||||
heartbeat.SetSplitMode(false)
|
||||
heartbeat.SetAutoStart(true)
|
||||
heartbeat.SetAutoUpdate(true)
|
||||
heartbeat.SetBeta("stable")
|
||||
heartbeat.SetDoh(false)
|
||||
heartbeat.SetShowAllMail(false)
|
||||
heartbeat.SetIMAPConnectionMode(true)
|
||||
heartbeat.SetSMTPConnectionMode(true)
|
||||
heartbeat.SetIMAPPort(1143)
|
||||
heartbeat.SetSMTPPort(1025)
|
||||
heartbeat.SetCacheLocation("/tmp")
|
||||
heartbeat.SetKeyChainPref("defaultKeychain")
|
||||
heartbeat.SetPrevVersion("1.2.3")
|
||||
|
||||
tests(&heartbeat, manager)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/proton-bridge/v3/internal/telemetry (interfaces: HeartbeatManager)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
telemetry "github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockHeartbeatManager is a mock of HeartbeatManager interface.
|
||||
type MockHeartbeatManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHeartbeatManagerMockRecorder
|
||||
}
|
||||
|
||||
// MockHeartbeatManagerMockRecorder is the mock recorder for MockHeartbeatManager.
|
||||
type MockHeartbeatManagerMockRecorder struct {
|
||||
mock *MockHeartbeatManager
|
||||
}
|
||||
|
||||
// NewMockHeartbeatManager creates a new mock instance.
|
||||
func NewMockHeartbeatManager(ctrl *gomock.Controller) *MockHeartbeatManager {
|
||||
mock := &MockHeartbeatManager{ctrl: ctrl}
|
||||
mock.recorder = &MockHeartbeatManagerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockHeartbeatManager) EXPECT() *MockHeartbeatManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetLastHeartbeatSent mocks base method.
|
||||
func (m *MockHeartbeatManager) GetLastHeartbeatSent() time.Time {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLastHeartbeatSent")
|
||||
ret0, _ := ret[0].(time.Time)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetLastHeartbeatSent indicates an expected call of GetLastHeartbeatSent.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) GetLastHeartbeatSent() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).GetLastHeartbeatSent))
|
||||
}
|
||||
|
||||
// IsTelemetryAvailable mocks base method.
|
||||
func (m *MockHeartbeatManager) IsTelemetryAvailable() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsTelemetryAvailable")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsTelemetryAvailable indicates an expected call of IsTelemetryAvailable.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) IsTelemetryAvailable() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsTelemetryAvailable", reflect.TypeOf((*MockHeartbeatManager)(nil).IsTelemetryAvailable))
|
||||
}
|
||||
|
||||
// SendHeartbeat mocks base method.
|
||||
func (m *MockHeartbeatManager) SendHeartbeat(arg0 *telemetry.HeartbeatData) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendHeartbeat", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendHeartbeat indicates an expected call of SendHeartbeat.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) SendHeartbeat(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendHeartbeat", reflect.TypeOf((*MockHeartbeatManager)(nil).SendHeartbeat), arg0)
|
||||
}
|
||||
|
||||
// SetLastHeartbeatSent mocks base method.
|
||||
func (m *MockHeartbeatManager) SetLastHeartbeatSent(arg0 time.Time) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLastHeartbeatSent", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetLastHeartbeatSent indicates an expected call of SetLastHeartbeatSent.
|
||||
func (mr *MockHeartbeatManagerMockRecorder) SetLastHeartbeatSent(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastHeartbeatSent", reflect.TypeOf((*MockHeartbeatManager)(nil).SetLastHeartbeatSent), arg0)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
dimensionON = "on"
|
||||
dimensionOFF = "off"
|
||||
dimensionDefault = "default"
|
||||
dimensionCustom = "custom"
|
||||
dimensionSSL = "ssl"
|
||||
dimensionStartTLS = "starttls"
|
||||
)
|
||||
|
||||
type HeartbeatManager interface {
|
||||
IsTelemetryAvailable() bool
|
||||
SendHeartbeat(heartbeat *HeartbeatData) bool
|
||||
GetLastHeartbeatSent() time.Time
|
||||
SetLastHeartbeatSent(time.Time) error
|
||||
}
|
||||
|
||||
type HeartbeatValues struct {
|
||||
NbAccount int `json:"nb_account"`
|
||||
}
|
||||
|
||||
type HeartbeatDimensions struct {
|
||||
AutoUpdate string `json:"auto_update"`
|
||||
AutoStart string `json:"auto_start"`
|
||||
Beta string `json:"beta"`
|
||||
Doh string `json:"doh"`
|
||||
SplitMode string `json:"split_mode"`
|
||||
ShowAllMail string `json:"show_all_mail"`
|
||||
IMAPConnectionMode string `json:"imap_connection_mode"`
|
||||
SMTPConnectionMode string `json:"smtp_connection_mode"`
|
||||
IMAPPort string `json:"imap_port"`
|
||||
SMTPPort string `json:"smtp_port"`
|
||||
CacheLocation string `json:"cache_location"`
|
||||
KeychainPref string `json:"keychain_pref"`
|
||||
PrevVersion string `json:"prev_version"`
|
||||
Rollout string `json:"rollout"`
|
||||
}
|
||||
|
||||
type HeartbeatData struct {
|
||||
MeasurementGroup string
|
||||
Event string
|
||||
Values HeartbeatValues
|
||||
Dimensions HeartbeatDimensions
|
||||
}
|
||||
|
||||
type Heartbeat struct {
|
||||
log *logrus.Entry
|
||||
manager HeartbeatManager
|
||||
metrics HeartbeatData
|
||||
|
||||
defaultIMAPPort int
|
||||
defaultSMTPPort int
|
||||
defaultCache string
|
||||
defaultKeychain string
|
||||
}
|
|
@ -400,32 +400,8 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
|
|||
}
|
||||
|
||||
if mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
|
||||
var metadata []proton.MessageMetadata
|
||||
|
||||
// There's currently no limit on how many IDs we can filter on,
|
||||
// but to be nice to API, let's chunk it by 150.
|
||||
for _, messageIDs := range xslices.Chunk(messageIDs, 150) {
|
||||
m, err := conn.client.GetMessageMetadata(ctx, proton.MessageFilter{
|
||||
ID: mapTo[imap.MessageID, string](messageIDs),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If a message is not preset in any other label other than AllMail, AllDrafts and AllSent, it can be
|
||||
// permanently deleted.
|
||||
m = xslices.Filter(m, func(m proton.MessageMetadata) bool {
|
||||
labelsThatMatter := xslices.Filter(m.LabelIDs, func(id string) bool {
|
||||
return id != proton.AllDraftsLabel && id != proton.AllMailLabel && id != proton.AllSentLabel
|
||||
})
|
||||
return len(labelsThatMatter) == 0
|
||||
})
|
||||
|
||||
metadata = append(metadata, m...)
|
||||
}
|
||||
|
||||
if err := conn.client.DeleteMessage(ctx, xslices.Map(metadata, func(m proton.MessageMetadata) string {
|
||||
return m.ID
|
||||
if err := conn.client.DeleteMessage(ctx, xslices.Map(messageIDs, func(m imap.MessageID) string {
|
||||
return string(m)
|
||||
})...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -626,7 +602,7 @@ func (conn *imapConnector) createDraft(ctx context.Context, literal []byte, addr
|
|||
return proton.Message{}, fmt.Errorf("failed to create parser: %w", err)
|
||||
}
|
||||
|
||||
message, err := message.ParseWithParser(parser)
|
||||
message, err := message.ParseWithParser(parser, true)
|
||||
if err != nil {
|
||||
return proton.Message{}, fmt.Errorf("failed to parse message: %w", err)
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ func (user *User) sendMail(authID string, from string, to []string, r io.Reader)
|
|||
}
|
||||
|
||||
// Parse the message we want to send (after we have attached the public key).
|
||||
message, err := message.ParseWithParser(parser)
|
||||
message, err := message.ParseWithParser(parser, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse message: %w", err)
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ func (user *User) syncMessages(
|
|||
syncStartTime := time.Now()
|
||||
defer func() { logrus.WithField("duration", time.Since(syncStartTime)).Info("Message sync completed") }()
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
user.log.WithFields(logrus.Fields{
|
||||
"messages": len(messageIDs),
|
||||
"numCPU": runtime.NumCPU(),
|
||||
}).Info("Starting message sync")
|
||||
|
|
|
@ -119,6 +119,12 @@ func New(
|
|||
return nil, fmt.Errorf("failed to get labels: %w", err)
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"userID": apiUser.ID,
|
||||
"numAddr": len(apiAddrs),
|
||||
"numLabels": len(apiLabels),
|
||||
}).Info("Creating user object")
|
||||
|
||||
// Create the user object.
|
||||
user := &User{
|
||||
log: logrus.WithField("userID", apiUser.ID),
|
||||
|
@ -591,6 +597,36 @@ func (user *User) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// IsTelemetryEnabled check if the telemetry is enabled or disabled for this user.
|
||||
func (user *User) IsTelemetryEnabled(ctx context.Context) bool {
|
||||
settings, err := user.client.GetUserSettings(ctx)
|
||||
if err != nil {
|
||||
user.log.WithError(err).Error("Failed to retrieve API user Settings")
|
||||
return false
|
||||
}
|
||||
return settings.Telemetry == proton.SettingEnabled
|
||||
}
|
||||
|
||||
// SendTelemetry send telemetry request.
|
||||
func (user *User) SendTelemetry(ctx context.Context, data []byte) error {
|
||||
var req proton.SendStatsReq
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
user.log.WithError(err).Error("Failed to build telemetry request.")
|
||||
if err := user.reporter.ReportMessageWithContext("Failed to build telemetry request.", reporter.Context{
|
||||
"error": err,
|
||||
}); err != nil {
|
||||
logrus.WithError(err).Error("Failed to report telemetry request build error")
|
||||
}
|
||||
return err
|
||||
}
|
||||
err := user.client.SendDataEvent(ctx, req)
|
||||
if err != nil {
|
||||
user.log.WithError(err).Error("Failed to send telemetry.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initUpdateCh initializes the user's update channels in the given address mode.
|
||||
// It is assumed that user.apiAddrs and user.updateCh are already locked.
|
||||
func (user *User) initUpdateCh(mode vault.AddressMode) {
|
||||
|
|
|
@ -80,6 +80,23 @@ func TestUser_AddressMode(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestUser_Telemetry(t *testing.T) {
|
||||
withAPI(t, context.Background(), func(ctx context.Context, s *server.Server, m *proton.Manager) {
|
||||
withAccount(t, s, "username", "password", []string{}, func(string, []string) {
|
||||
withUser(t, ctx, s, m, "username", "password", func(user *User) {
|
||||
// By default, user should have Telemetry enabled.
|
||||
telemetry := user.IsTelemetryEnabled(ctx)
|
||||
require.Equal(t, true, telemetry)
|
||||
|
||||
user.client.Close()
|
||||
// If telemetry cannot be retrieved it is disabled.
|
||||
telemetry = user.IsTelemetryEnabled(ctx)
|
||||
require.Equal(t, false, telemetry)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func withAPI(_ testing.TB, ctx context.Context, fn func(context.Context, *server.Server, *proton.Manager)) { //nolint:revive
|
||||
server := server.New()
|
||||
defer server.Close()
|
||||
|
|
|
@ -44,6 +44,20 @@ func (ua *UserAgent) SetClient(name, version string) {
|
|||
ua.client = fmt.Sprintf("%v/%v", name, regexp.MustCompile(`(.*) \((.*)\)`).ReplaceAllString(version, "$1-$2"))
|
||||
}
|
||||
|
||||
func (ua *UserAgent) SetClientString(client string) {
|
||||
ua.lock.Lock()
|
||||
defer ua.lock.Unlock()
|
||||
|
||||
ua.client = client
|
||||
}
|
||||
|
||||
func (ua *UserAgent) GetClientString() string {
|
||||
ua.lock.RLock()
|
||||
defer ua.lock.RUnlock()
|
||||
|
||||
return ua.client
|
||||
}
|
||||
|
||||
func (ua *UserAgent) HasClient() bool {
|
||||
ua.lock.RLock()
|
||||
defer ua.lock.RUnlock()
|
||||
|
|
|
@ -20,6 +20,7 @@ package vault
|
|||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
|
@ -184,6 +185,18 @@ func (vault *Vault) SetAutoUpdate(autoUpdate bool) error {
|
|||
})
|
||||
}
|
||||
|
||||
// GetTelemetryDisabled checks whether telemetry is disabled.
|
||||
func (vault *Vault) GetTelemetryDisabled() bool {
|
||||
return vault.get().Settings.TelemetryDisabled
|
||||
}
|
||||
|
||||
// SetTelemetryDisabled sets whether telemetry is disabled.
|
||||
func (vault *Vault) SetTelemetryDisabled(telemetryDisabled bool) error {
|
||||
return vault.mod(func(data *Data) {
|
||||
data.Settings.TelemetryDisabled = telemetryDisabled
|
||||
})
|
||||
}
|
||||
|
||||
// GetLastVersion returns the last version of the bridge that was run.
|
||||
func (vault *Vault) GetLastVersion() *semver.Version {
|
||||
return semver.MustParse(vault.get().Settings.LastVersion)
|
||||
|
@ -225,3 +238,34 @@ func (vault *Vault) SetMaxSyncMemory(maxMemory uint64) error {
|
|||
data.Settings.MaxSyncMemory = maxMemory
|
||||
})
|
||||
}
|
||||
|
||||
// GetLastUserAgent returns the last user agent recorded by bridge.
|
||||
func (vault *Vault) GetLastUserAgent() string {
|
||||
v := vault.get().Settings.LastUserAgent
|
||||
|
||||
// Handle case where there may be no value.
|
||||
if len(v) == 0 {
|
||||
v = DefaultUserAgent
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// SetLastUserAgent store the last user agent recorded by bridge.
|
||||
func (vault *Vault) SetLastUserAgent(userAgent string) error {
|
||||
return vault.mod(func(data *Data) {
|
||||
data.Settings.LastUserAgent = userAgent
|
||||
})
|
||||
}
|
||||
|
||||
// GetLastHeartbeatSent returns the last time heartbeat was sent.
|
||||
func (vault *Vault) GetLastHeartbeatSent() time.Time {
|
||||
return vault.get().Settings.LastHeartbeatSent
|
||||
}
|
||||
|
||||
// SetLastHeartbeatSent store the last time heartbeat was sent.
|
||||
func (vault *Vault) SetLastHeartbeatSent(timestamp time.Time) error {
|
||||
return vault.mod(func(data *Data) {
|
||||
data.Settings.LastHeartbeatSent = timestamp
|
||||
})
|
||||
}
|
||||
|
|
|
@ -153,6 +153,20 @@ func TestVault_Settings_ShowAllMail(t *testing.T) {
|
|||
require.Equal(t, false, s.GetShowAllMail())
|
||||
}
|
||||
|
||||
func TestVault_Settings_TelemetryDisabled(t *testing.T) {
|
||||
// create a new test vault.
|
||||
s := newVault(t)
|
||||
|
||||
// Check the default show all mail setting.
|
||||
require.Equal(t, false, s.GetTelemetryDisabled())
|
||||
|
||||
// Modify the show all mail setting.
|
||||
require.NoError(t, s.SetTelemetryDisabled(true))
|
||||
|
||||
// Check the new show all mail setting.
|
||||
require.Equal(t, true, s.GetTelemetryDisabled())
|
||||
}
|
||||
|
||||
func TestVault_Settings_Autostart(t *testing.T) {
|
||||
// create a new test vault.
|
||||
s := newVault(t)
|
||||
|
@ -216,3 +230,11 @@ func TestVault_Settings_MaxSyncMemory(t *testing.T) {
|
|||
// Check the default first start value.
|
||||
require.Equal(t, vault.DefaultMaxSyncMemory, s.GetMaxSyncMemory())
|
||||
}
|
||||
|
||||
func TestVault_Settings_LastUserAgent(t *testing.T) {
|
||||
// create a new test vault.
|
||||
s := newVault(t)
|
||||
|
||||
// Check the default first start value.
|
||||
require.Equal(t, vault.DefaultUserAgent, s.GetLastUserAgent())
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ package vault
|
|||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v3/pkg/ports"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
|
@ -35,23 +37,29 @@ type Settings struct {
|
|||
UpdateChannel updater.Channel
|
||||
UpdateRollout float64
|
||||
|
||||
ColorScheme string
|
||||
ProxyAllowed bool
|
||||
ShowAllMail bool
|
||||
Autostart bool
|
||||
AutoUpdate bool
|
||||
ColorScheme string
|
||||
ProxyAllowed bool
|
||||
ShowAllMail bool
|
||||
Autostart bool
|
||||
AutoUpdate bool
|
||||
TelemetryDisabled bool
|
||||
|
||||
LastVersion string
|
||||
FirstStart bool
|
||||
|
||||
MaxSyncMemory uint64
|
||||
|
||||
LastUserAgent string
|
||||
|
||||
LastHeartbeatSent time.Time
|
||||
|
||||
// **WARNING**: These entry can't be removed until they vault has proper migration support.
|
||||
SyncWorkers int
|
||||
SyncAttPool int
|
||||
}
|
||||
|
||||
const DefaultMaxSyncMemory = 2 * 1024 * uint64(1024*1024)
|
||||
const DefaultUserAgent = "UnknownClient/0.0.1"
|
||||
|
||||
func GetDefaultSyncWorkerCount() int {
|
||||
const minSyncWorkers = 16
|
||||
|
@ -67,23 +75,26 @@ func GetDefaultSyncWorkerCount() int {
|
|||
|
||||
func newDefaultSettings(gluonDir string) Settings {
|
||||
syncWorkers := GetDefaultSyncWorkerCount()
|
||||
imapPort := ports.FindFreePortFrom(1143)
|
||||
smtpPort := ports.FindFreePortFrom(1025, imapPort)
|
||||
|
||||
return Settings{
|
||||
GluonDir: gluonDir,
|
||||
|
||||
IMAPPort: 1143,
|
||||
SMTPPort: 1025,
|
||||
IMAPPort: imapPort,
|
||||
SMTPPort: smtpPort,
|
||||
IMAPSSL: false,
|
||||
SMTPSSL: false,
|
||||
|
||||
UpdateChannel: updater.DefaultUpdateChannel,
|
||||
UpdateRollout: rand.Float64(), //nolint:gosec
|
||||
|
||||
ColorScheme: "",
|
||||
ProxyAllowed: false,
|
||||
ShowAllMail: true,
|
||||
Autostart: true,
|
||||
AutoUpdate: true,
|
||||
ColorScheme: "",
|
||||
ProxyAllowed: false,
|
||||
ShowAllMail: true,
|
||||
Autostart: true,
|
||||
AutoUpdate: true,
|
||||
TelemetryDisabled: false,
|
||||
|
||||
LastVersion: "0.0.0",
|
||||
FirstStart: true,
|
||||
|
@ -91,5 +102,8 @@ func newDefaultSettings(gluonDir string) Settings {
|
|||
MaxSyncMemory: DefaultMaxSyncMemory,
|
||||
SyncWorkers: syncWorkers,
|
||||
SyncAttPool: syncWorkers,
|
||||
|
||||
LastUserAgent: DefaultUserAgent,
|
||||
LastHeartbeatSent: time.Time{},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func init() { //nolint:gochecknoinits
|
|||
Helpers[MacOSKeychain] = newMacOSHelper
|
||||
|
||||
// Use MacOSKeychain by default.
|
||||
defaultHelper = MacOSKeychain
|
||||
DefaultHelper = MacOSKeychain
|
||||
}
|
||||
|
||||
func parseError(original error) error {
|
||||
|
|
|
@ -48,14 +48,14 @@ func init() { //nolint:gochecknoinits
|
|||
Helpers[Pass] = newPassHelper
|
||||
}
|
||||
|
||||
defaultHelper = SecretServiceDBus
|
||||
DefaultHelper = SecretServiceDBus
|
||||
|
||||
// If Pass is available, use it by default.
|
||||
// Otherwise, if SecretService is available, use it by default.
|
||||
if _, ok := Helpers[Pass]; ok {
|
||||
defaultHelper = Pass
|
||||
DefaultHelper = Pass
|
||||
} else if _, ok := Helpers[SecretService]; ok {
|
||||
defaultHelper = SecretService
|
||||
DefaultHelper = SecretService
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ func init() { //nolint:gochecknoinits
|
|||
Helpers[WindowsCredentials] = newWinCredHelper
|
||||
|
||||
// Use WindowsCredentials by default.
|
||||
defaultHelper = WindowsCredentials
|
||||
DefaultHelper = WindowsCredentials
|
||||
}
|
||||
|
||||
func newWinCredHelper(string) (credentials.Helper, error) {
|
||||
|
|
|
@ -42,8 +42,8 @@ var (
|
|||
// Helpers holds all discovered keychain helpers. It is populated in init().
|
||||
Helpers map[string]helperConstructor //nolint:gochecknoglobals
|
||||
|
||||
// defaultHelper is the default helper to use if the user hasn't yet set a preference.
|
||||
defaultHelper string //nolint:gochecknoglobals
|
||||
// DefaultHelper is the default helper to use if the user hasn't yet set a preference.
|
||||
DefaultHelper string //nolint:gochecknoglobals
|
||||
)
|
||||
|
||||
// NewKeychain creates a new native keychain.
|
||||
|
@ -55,7 +55,7 @@ func NewKeychain(preferred, keychainName string) (*Keychain, error) {
|
|||
|
||||
// If the preferred keychain is unsupported, fallback to the default one.
|
||||
if _, ok := Helpers[preferred]; !ok {
|
||||
preferred = defaultHelper
|
||||
preferred = DefaultHelper
|
||||
}
|
||||
|
||||
// Load the user's preferred keychain helper.
|
||||
|
|
|
@ -441,7 +441,7 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header {
|
|||
hdr.Set("From", msg.Sender.String())
|
||||
}
|
||||
|
||||
if len(msg.ReplyTos) > 0 {
|
||||
if len(msg.ReplyTos) > 0 && !msg.IsDraft() {
|
||||
if !(len(msg.ReplyTos) == 1 && addressEmpty(msg.ReplyTos[0])) {
|
||||
hdr.Set("Reply-To", toAddressList(msg.ReplyTos))
|
||||
}
|
||||
|
|
|
@ -431,7 +431,7 @@ func TestBuildSignedPlainEncryptedMessageWithPubKey(t *testing.T) {
|
|||
section(t, res).
|
||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||
expectContentType(is(`multipart/signed`)).
|
||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||
expectContentTypeParam(`micalg`, is(`SHA-256`)).
|
||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||
|
||||
section(t, res, 1).
|
||||
|
@ -439,7 +439,7 @@ func TestBuildSignedPlainEncryptedMessageWithPubKey(t *testing.T) {
|
|||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||
expectHeader(`Subject`, is(`simple plaintext body`)).
|
||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||
expectHeader(`To`, is("\"InfernalBridgeTester@proton.me\" <InfernalbridgeTester@proton.me>")).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||
|
||||
section(t, res, 1, 1).
|
||||
|
@ -477,7 +477,7 @@ func TestBuildSignedHTMLEncryptedMessageWithPubKey(t *testing.T) {
|
|||
section(t, res).
|
||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||
expectContentType(is(`multipart/signed`)).
|
||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||
expectContentTypeParam(`micalg`, is(`SHA-256`)).
|
||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||
|
||||
section(t, res, 1).
|
||||
|
@ -485,7 +485,7 @@ func TestBuildSignedHTMLEncryptedMessageWithPubKey(t *testing.T) {
|
|||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||
expectHeader(`Subject`, is(`simple html body`)).
|
||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||
expectHeader(`To`, is("\"InfernalBridgeTester@proton.me\" <InfernalbridgeTester@proton.me>")).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||
|
||||
section(t, res, 1, 1).
|
||||
|
@ -524,7 +524,7 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T)
|
|||
section(t, res).
|
||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||
expectContentType(is(`multipart/signed`)).
|
||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||
expectContentTypeParam(`micalg`, is(`SHA-256`)).
|
||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||
|
||||
section(t, res, 1).
|
||||
|
@ -532,8 +532,8 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T)
|
|||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||
expectHeader(`Subject`, is(`Alternative`)).
|
||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 3).pubKey(), section(t, res, 2).signature()))
|
||||
expectHeader(`To`, is("\"InfernalBridgeTester@proton.me\" <InfernalbridgeTester@proton.me>")).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||
|
||||
section(t, res, 1, 1).
|
||||
expectContentType(is(`multipart/mixed`))
|
||||
|
@ -549,17 +549,11 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T)
|
|||
|
||||
section(t, res, 1, 1, 1, 2).
|
||||
expectContentType(is(`text/html`)).
|
||||
expectBody(contains(`This <font color="#ee24cc">Rich</font> formated text`)).
|
||||
expectBody(contains(`This Rich formated text`)).
|
||||
expectBody(contains(`What kind of shoes do ninjas wear`)).
|
||||
expectBody(contains(`How does a penguin build its house`))
|
||||
|
||||
section(t, res, 1, 1, 2).
|
||||
expectContentType(is(`application/pdf`)).
|
||||
expectTransferEncoding(is(`base64`)).
|
||||
expectContentTypeParam(`name`, is(`minimal.pdf`)).
|
||||
expectContentDispositionParam(`filename`, is(`minimal.pdf`))
|
||||
|
||||
section(t, res, 1, 1, 3).
|
||||
expectContentType(is(`application/pgp-keys`)).
|
||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||
expectContentDisposition(is(`attachment`)).
|
||||
|
@ -587,16 +581,16 @@ func TestBuildSignedEmbeddedMessageRFC822EncryptedMessageWithPubKey(t *testing.T
|
|||
section(t, res).
|
||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||
expectContentType(is(`multipart/signed`)).
|
||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||
expectContentTypeParam(`micalg`, is(`SHA-256`)).
|
||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||
|
||||
section(t, res, 1).
|
||||
expectContentType(is(`multipart/mixed`)).
|
||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||
expectHeader(`Subject`, is(`Fwd: HTML with attachment external PGP`)).
|
||||
expectHeader(`Subject`, is(`Fwd: simple html body`)).
|
||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||
expectHeader(`To`, is("\"InfernalBridgeTester@proton.me\" <InfernalbridgeTester@proton.me>")).
|
||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 3).pubKey(), section(t, res, 2).signature()))
|
||||
|
||||
section(t, res, 1, 1).
|
||||
expectContentType(is(`multipart/mixed`))
|
||||
|
@ -605,17 +599,17 @@ func TestBuildSignedEmbeddedMessageRFC822EncryptedMessageWithPubKey(t *testing.T
|
|||
expectContentType(is(`text/plain`))
|
||||
|
||||
section(t, res, 1, 1, 2).
|
||||
expectContentType(is(`message/rfc822`)).
|
||||
expectContentTypeParam(`name`, is(`simple html body.eml`)).
|
||||
expectContentDisposition(is(`attachment`)).
|
||||
expectContentDispositionParam(`filename`, is(`simple html body.eml`))
|
||||
|
||||
section(t, res, 1, 1, 3).
|
||||
expectContentType(is(`application/pgp-keys`)).
|
||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||
expectContentDisposition(is(`attachment`)).
|
||||
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
||||
|
||||
section(t, res, 1, 1, 3).
|
||||
expectContentType(is(`message/rfc822`)).
|
||||
expectContentTypeParam(`name`, is(`HTML with attachment external PGP.eml`)).
|
||||
expectContentDisposition(is(`attachment`)).
|
||||
expectContentDispositionParam(`filename`, is(`HTML with attachment external PGP.eml`))
|
||||
|
||||
section(t, res, 2).
|
||||
expectContentType(is(`application/pgp-signature`)).
|
||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||
|
|
|
@ -73,6 +73,15 @@ type Attachment struct {
|
|||
|
||||
// Parse parses an RFC822 message.
|
||||
func Parse(r io.Reader) (m Message, err error) {
|
||||
return parseIOReaderImpl(r, false)
|
||||
}
|
||||
|
||||
// ParseAndAllowInvalidAddressLists parses an RFC822 message and allows email address lists to be invalid.
|
||||
func ParseAndAllowInvalidAddressLists(r io.Reader) (m Message, err error) {
|
||||
return parseIOReaderImpl(r, true)
|
||||
}
|
||||
|
||||
func parseIOReaderImpl(r io.Reader, allowInvalidAddressLists bool) (m Message, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic while parsing message: %v", r)
|
||||
|
@ -84,21 +93,21 @@ func Parse(r io.Reader) (m Message, err error) {
|
|||
return Message{}, errors.Wrap(err, "failed to create new parser")
|
||||
}
|
||||
|
||||
return parse(p)
|
||||
return parse(p, allowInvalidAddressLists)
|
||||
}
|
||||
|
||||
// ParseWithParser parses an RFC822 message using an existing parser.
|
||||
func ParseWithParser(p *parser.Parser) (m Message, err error) {
|
||||
func ParseWithParser(p *parser.Parser, allowInvalidAddressLists bool) (m Message, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic while parsing message: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
return parse(p)
|
||||
return parse(p, allowInvalidAddressLists)
|
||||
}
|
||||
|
||||
func parse(p *parser.Parser) (Message, error) {
|
||||
func parse(p *parser.Parser, allowInvalidAddressLists bool) (Message, error) {
|
||||
if err := convertEncodedTransferEncoding(p); err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to convert encoded transfer encoding")
|
||||
}
|
||||
|
@ -107,7 +116,7 @@ func parse(p *parser.Parser) (Message, error) {
|
|||
return Message{}, errors.Wrap(err, "failed to convert foreign encodings")
|
||||
}
|
||||
|
||||
m, err := parseMessageHeader(p.Root().Header)
|
||||
m, err := parseMessageHeader(p.Root().Header, allowInvalidAddressLists)
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse message header")
|
||||
}
|
||||
|
@ -433,7 +442,7 @@ func getPlainBody(part *parser.Part) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
func parseMessageHeader(h message.Header) (Message, error) {
|
||||
func parseMessageHeader(h message.Header, allowInvalidAddressLists bool) (Message, error) {
|
||||
var m Message
|
||||
|
||||
for fields := h.Fields(); fields.Next(); {
|
||||
|
@ -451,7 +460,11 @@ func parseMessageHeader(h message.Header) (Message, error) {
|
|||
case "from":
|
||||
sender, err := rfc5322.ParseAddressList(fields.Value())
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse from")
|
||||
if !allowInvalidAddressLists {
|
||||
return Message{}, errors.Wrap(err, "failed to parse from")
|
||||
}
|
||||
|
||||
logrus.WithError(err).Warn("failed to parse from")
|
||||
}
|
||||
|
||||
if len(sender) > 0 {
|
||||
|
@ -461,7 +474,11 @@ func parseMessageHeader(h message.Header) (Message, error) {
|
|||
case "to":
|
||||
toList, err := rfc5322.ParseAddressList(fields.Value())
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse to")
|
||||
if !allowInvalidAddressLists {
|
||||
return Message{}, errors.Wrap(err, "failed to parse to")
|
||||
}
|
||||
|
||||
logrus.WithError(err).Warn("failed to parse to")
|
||||
}
|
||||
|
||||
m.ToList = toList
|
||||
|
@ -469,7 +486,11 @@ func parseMessageHeader(h message.Header) (Message, error) {
|
|||
case "reply-to":
|
||||
replyTos, err := rfc5322.ParseAddressList(fields.Value())
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse reply-to")
|
||||
if !allowInvalidAddressLists {
|
||||
return Message{}, errors.Wrap(err, "failed to parse reply-to")
|
||||
}
|
||||
|
||||
logrus.WithError(err).Warn("failed to parse reply-to")
|
||||
}
|
||||
|
||||
m.ReplyTos = replyTos
|
||||
|
@ -477,7 +498,11 @@ func parseMessageHeader(h message.Header) (Message, error) {
|
|||
case "cc":
|
||||
ccList, err := rfc5322.ParseAddressList(fields.Value())
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse cc")
|
||||
if !allowInvalidAddressLists {
|
||||
return Message{}, errors.Wrap(err, "failed to parse cc")
|
||||
}
|
||||
|
||||
logrus.WithError(err).Warn("failed to parse cc")
|
||||
}
|
||||
|
||||
m.CCList = ccList
|
||||
|
@ -485,7 +510,11 @@ func parseMessageHeader(h message.Header) (Message, error) {
|
|||
case "bcc":
|
||||
bccList, err := rfc5322.ParseAddressList(fields.Value())
|
||||
if err != nil {
|
||||
return Message{}, errors.Wrap(err, "failed to parse bcc")
|
||||
if !allowInvalidAddressLists {
|
||||
return Message{}, errors.Wrap(err, "failed to parse bcc")
|
||||
}
|
||||
|
||||
logrus.WithError(err).Warn("failed to parse bcc")
|
||||
}
|
||||
|
||||
m.BCCList = bccList
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
|
@ -444,7 +445,7 @@ func TestParseWithAttachedPublicKey(t *testing.T) {
|
|||
p, err := parser.New(f)
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := ParseWithParser(p)
|
||||
m, err := ParseWithParser(p, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
p.AttachPublicKey("publickey", "publickeyname")
|
||||
|
@ -636,6 +637,32 @@ func TestParseIcsAttachment(t *testing.T) {
|
|||
assert.Equal(t, string(m.Attachments[0].Data), "This is an ics calendar invite")
|
||||
}
|
||||
|
||||
func TestParseAllowInvalidAddress(t *testing.T) {
|
||||
const literal = `To: foo
|
||||
From: bar
|
||||
BCC: fff
|
||||
CC: FFF
|
||||
Reply-To: AAA
|
||||
Subject: Test
|
||||
`
|
||||
|
||||
// This will fail as the addresses are not valid.
|
||||
{
|
||||
_, err := Parse(strings.NewReader(literal))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// This will work as invalid addresses will be ignored.
|
||||
m, err := ParseAndAllowInvalidAddressLists(strings.NewReader(literal))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, m.ToList)
|
||||
assert.Empty(t, m.Sender)
|
||||
assert.Empty(t, m.CCList)
|
||||
assert.Empty(t, m.BCCList)
|
||||
assert.Empty(t, m.ReplyTos)
|
||||
}
|
||||
|
||||
func TestParsePanic(t *testing.T) {
|
||||
var err error
|
||||
|
||||
|
|
|
@ -1,212 +1,252 @@
|
|||
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||
protocol="application/pgp-signature";
|
||||
boundary="Rrmlds5vN3IeeCVjbnepHmuVgyROSBjsS"
|
||||
|
||||
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||
--Rrmlds5vN3IeeCVjbnepHmuVgyROSBjsS
|
||||
Content-Type: multipart/mixed; boundary="avFkF0LAPYPXcFHcnsgGmACbGIPeVDdYc";
|
||||
protected-headers="v1"
|
||||
Subject: Fwd: HTML with attachment external PGP
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: schizofrenic@pm.me
|
||||
Message-ID: <7c04869b-c470-116f-b8e5-8b4fd5e1195d@gmail.com>
|
||||
References: <LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA@cp7-web-042.plabs.ch>
|
||||
In-Reply-To: <LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA@cp7-web-042.plabs.ch>
|
||||
|
||||
--avFkF0LAPYPXcFHcnsgGmACbGIPeVDdYc
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------2F19EE9A8A1A6F779F5D14AF"
|
||||
Content-Language: en-US
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------2F19EE9A8A1A6F779F5D14AF
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
|
||||
|
||||
--------------2F19EE9A8A1A6F779F5D14AF
|
||||
Content-Type: application/pgp-keys;
|
||||
name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: attachment;
|
||||
filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxCZ=
|
||||
pDh
|
||||
I5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQOL9WYT=
|
||||
f4S
|
||||
PWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIYzsyM/P97x=
|
||||
Snd
|
||||
NvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyB=
|
||||
OfN
|
||||
H5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLe=
|
||||
XUt
|
||||
RWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwJQEEwEIAD4CGwMFCwkIBwIGFQoJCAsCB=
|
||||
BYC
|
||||
AwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCYC32ygUJB4sMzgAKCRAWHAh1giNZ9=
|
||||
/K8
|
||||
B/4qs84Ii/zKH+q+C8vwO4jUJkOM73qD0pgB7zBs651zWbpgopyol1YUKNpFaHlx/Qch7RDI7=
|
||||
Vcz
|
||||
1+60/KZJSJR19/N2EDVbCUdh8ueioUp9X/218YWV2TRJNxTnljd4FAn7smZnXuP1TsLjQ6sKO=
|
||||
V0U
|
||||
u6JoiG6LZFXqDgxYpA++58Rkl6xaY6R71VkmVQlbEKtubX9AjHydq97Y+Jvn11XzWZaKhv4L7=
|
||||
6Pa
|
||||
4tMKXvvrKh1oywMmh6mZJo+5ZA/ABTkr45cwlTPYqGTS9+uvOHt+PH/oYwwJB4ls2cIAUldSj=
|
||||
TVQ
|
||||
IsseYz3LlbcCfKJiiCFxeHOQXA5J6zNLKOT58TsczsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3=
|
||||
D07
|
||||
kCBNvu4HQfps6h1rgNxGhE32VmpESHebvIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF=
|
||||
88F
|
||||
yf2NBcejpIbcP7BS/g+C6KOowYj+Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCa=
|
||||
knm
|
||||
3c7dNuXmhflz/w3aBj32q9ZyGqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPk=
|
||||
utT
|
||||
ouEZQbMK+i+chOH3AsKCuNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr=
|
||||
8RB
|
||||
owVAdcvjZxeIDKALABEBAAHCwHwEGAEIACYCGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCY=
|
||||
C32
|
||||
lAUJB4sMmAAKCRAWHAh1giNZ9+Y2B/9rTKZaKviae+ummXNumXcrKvbkAAvfuLpKUn53FlQLm=
|
||||
L6H
|
||||
jB++lJnPWvVSzdZxdv8FiPP3d632XHKUrkQRQM/9byRDXDommi7Qttx7YCkhd4JLVYqJqpnAQ=
|
||||
xI5
|
||||
RMkXiZNWyr1lz8JOM1XvDk1M7sJwPMWews8VOIE03E1nt7AsQGnvHtadgEnQaufrYNX3hFA8S=
|
||||
osO
|
||||
HSnedcys6yrzCSIGCqCD9VHbnMtS4DOv0XJGh2hwc8omzH0KZA517dyKBorJRwadcVauGXDKx=
|
||||
Etv
|
||||
Im4rl94PR/3An1Mj6HeeVVpLqDQ5Jb9J90BahWeQ53FzRa4EQzYCw0nLnxcsT1ZEEP5u
|
||||
=3Dv/1p
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------2F19EE9A8A1A6F779F5D14AF
|
||||
Content-Type: message/rfc822;
|
||||
name="HTML with attachment external PGP.eml"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment;
|
||||
filename="HTML with attachment external PGP.eml"
|
||||
|
||||
Delivered-To: pm.bridge.qa@gmail.com
|
||||
Received: by 2002:a17:906:a051:0:0:0:0 with SMTP id bg17csp66709ejb;
|
||||
Wed, 24 Mar 2021 22:03:32 -0700 (PDT)
|
||||
X-Google-Smtp-Source: ABdhPJxllBuHnnJzKWy77R291tZbVFVk0iahkLm1TQsluEYTvyAXdOWB/zp1y10e60UlGGZYH3YF
|
||||
X-Received: by 2002:a05:6000:118c:: with SMTP id g12mr6758087wrx.353.1616648612550;
|
||||
Wed, 24 Mar 2021 22:03:32 -0700 (PDT)
|
||||
ARC-Seal: i=1; a=rsa-sha256; t=1616648612; cv=none;
|
||||
d=google.com; s=arc-20160816;
|
||||
b=Jf4vmKEoeJQ3rIDMbI2twiDkfn50ejNnqIbs2nkaFruITcw6XhvhbcfV9HLC80Yt8E
|
||||
tfN7TV9qoBneSWzfSJ+Sqw31hBKKtKpMhuqZT9GPzBN5gdMJKj5ISAQ8Lgm9zvR3Zbjn
|
||||
N0nOzCu/oT1amMMm+48hpKj8VL2tydjvNG+g/a5lk1Aw7JdqIKV6t1XhsyyYaa1O+NFC
|
||||
rQThdalcQj2NjoZWba1mjZSzI7B7hJdZg5d+jado2TPMQXe2kz2wGmr3+/JcKvPJjrSA
|
||||
S+jzhpjcd7ZnctkzTfpsdlBJAGKoDBnSvQc3eMJ/AgRHFc+5ks5nRDt/1DowSjQ7i7rp
|
||||
4a+g==
|
||||
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
|
||||
h=mime-version:message-id:subject:reply-to:from:to:dkim-signature
|
||||
:date;
|
||||
bh=vmJ0JT+IfeO4idMYP7zPvldBkdONjKTXWTp7ly/B9qk=;
|
||||
b=f8VY+ajsE/XNYrqD666FM0WCtNEQtUyU/Zh3pFCI9sFrMnAui4Qp9Gs1fe/8HLxt2v
|
||||
/C4l4eHELvPBv4vX0KtUvOlRZYPZbLZCNdtTcFtiuZEKUHWx370p7yyMWcmSMdlUbq4J
|
||||
NrKMPGfaYiZe5Rt3MyD5RKm4RJpqvep34VCHMYtoFQP/0Po4/1JMDw0Fy6SXUJ54rBRw
|
||||
bmzqNNBkonda3YghhK3WNrxTxzZ8I7KW9YdpENNS9ewJLeVtFQKdiLZwz5EpMZxOxG0I
|
||||
LW0jRtDlmZnqRe7bvTAo51IuLf9okHRI8PRiK0UHl+4Vr5Igq4mub7Ee8pC/Nz3Yj29G
|
||||
KODw==
|
||||
ARC-Authentication-Results: i=1; mx.google.com;
|
||||
dkim=pass header.i=@protonmail.com header.s=protonmail header.b=EX07e46H;
|
||||
spf=pass (google.com: domain of bridge-test-user@protonmail.com designates 185.70.40.22 as permitted sender) smtp.mailfrom=bridge-test-user@protonmail.com;
|
||||
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=protonmail.com
|
||||
Return-Path: <bridge-test-user@protonmail.com>
|
||||
Received: from mail2.protonmail.ch (mail2.protonmail.ch. [185.70.40.22])
|
||||
by mx.google.com with ESMTPS id g6si2999785wrr.110.2021.03.24.22.03.32
|
||||
for <pm.bridge.qa@gmail.com>
|
||||
(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
|
||||
Wed, 24 Mar 2021 22:03:32 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of bridge-test-user@protonmail.com designates 185.70.40.22 as permitted sender) client-ip=185.70.40.22;
|
||||
Authentication-Results: mx.google.com;
|
||||
dkim=pass header.i=@protonmail.com header.s=protonmail header.b=EX07e46H;
|
||||
spf=pass (google.com: domain of bridge-test-user@protonmail.com designates 185.70.40.22 as permitted sender) smtp.mailfrom=bridge-test-user@protonmail.com;
|
||||
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=protonmail.com
|
||||
Date: Thu, 25 Mar 2021 05:03:27 +0000
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com;
|
||||
s=protonmail; t=1616648611;
|
||||
bh=vmJ0JT+IfeO4idMYP7zPvldBkdONjKTXWTp7ly/B9qk=;
|
||||
h=Date:To:From:Reply-To:Subject:From;
|
||||
b=EX07e46H5/HmotAWZ69I4qa5jCVRao/p3KEM3eQn/AQ8s+cLMaR5b2ozdHrPCsTw5
|
||||
i5b1DLUHZHBf+6Ven47WJfKNwLUfkAGD2P0aI/dAk/h/h0Bg4Ni85pv+uPpRHLNQKv
|
||||
T3VnDP9MSwl6IUJu5zoM2EC70MLoiHS07lxhM2pw=
|
||||
To: External Bridge <pm.bridge.qa@gmail.com>
|
||||
From: Bridge Test <bridge-test-user@protonmail.com>
|
||||
Reply-To: Bridge Test <bridge-test-user@protonmail.com>
|
||||
Subject: HTML with attachment external PGP
|
||||
Message-ID: <LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA@cp7-web-042.plabs.ch>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="b1_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA"
|
||||
X-Spam-Status: No, score=-1.2 required=10.0 tests=ALL_TRUSTED,DKIM_SIGNED,
|
||||
DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE
|
||||
shortcircuit=no autolearn=disabled version=3.4.4
|
||||
X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on
|
||||
mailout.protonmail.ch
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
--b1_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="b2_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA"
|
||||
|
||||
--b2_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
VGhpcyBpcyBib2R5IG9mIEhUTUwgbWFpbCB3aXRoIGF0dGFjaG1lbnQ=
|
||||
|
||||
--b2_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
PGh0bWw+PGhlYWQ+PC9oZWFkPjxib2R5PlRoaXMgaXMgYm9keSBvZiA8Yj5IVE1MIG1haWw8L2I+
|
||||
IHdpdGggYXR0YWNobWVudA0KPC9ib2R5PjwvaHRtbD4=
|
||||
|
||||
|
||||
--b2_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA--
|
||||
|
||||
--b1_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA
|
||||
Content-Type: image/png; name=outline-light-instagram-48.png
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename=outline-light-instagram-48.png
|
||||
|
||||
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
|
||||
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
|
||||
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
|
||||
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
|
||||
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
|
||||
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
|
||||
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
|
||||
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
|
||||
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
|
||||
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
|
||||
|
||||
--b1_LCxIUvb0rqBufdwR4JNxMg4ZDMI8x7pRG0vEHGHYwA--
|
||||
|
||||
|
||||
--------------2F19EE9A8A1A6F779F5D14AF--
|
||||
|
||||
--avFkF0LAPYPXcFHcnsgGmACbGIPeVDdYc--
|
||||
|
||||
--Rrmlds5vN3IeeCVjbnepHmuVgyROSBjsS
|
||||
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmBciIAFAwAAAAAACgkQFhwIdYIjWfcN
|
||||
ZQf+NzAoEJRTSW5JFNgSGkwLsH89wAbw3wEt4PYuZaa+35xBuU8Sojm1oLOyuPkIasQf98Iu5P1o
|
||||
8cokViEa6wm+ZZpcFMi6T2/3+UNlSm81Epm7GrFyjAFTWrdTPLb4k4x47sz77RoTp/UEwm/7fVI5
|
||||
gMYhQyIYaocXHmDk61UshWE9q/Po6qjHBnnWS8YBnhUS9lK8uimpfRO9UQ9bIUjIYDGDPAtBoYnb
|
||||
X9V4SjBvbbdNrgoVaDxPw6HYCb3RhzRXunr5Icdnjfbc2H40/FayVi/p7GzFh+8zv/TzRxMkHo72
|
||||
DBsONaC7r8bxQ9BwJvpmWufqL7ZXHfVXQ6z+M43e1Q==
|
||||
=Stx+
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
--Rrmlds5vN3IeeCVjbnepHmuVgyROSBjsS--
|
||||
Content-Type: multipart/signed;
|
||||
boundary=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
|
||||
micalg=SHA-256; protocol="application/pgp-signature"
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Type: multipart/mixed; boundary="------------6sNRIgaKOJHTghuLxPUzlxn1";
|
||||
protected-headers="v1"
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
Message-ID: <3f60022f-1ff2-9792-926b-a556cc39195d@gmail.com>
|
||||
Subject: Fwd: simple html body
|
||||
References: <1655e652-1abc-575f-6e03-5bbf119afbfa@gmail.com>
|
||||
In-Reply-To: <1655e652-1abc-575f-6e03-5bbf119afbfa@gmail.com>
|
||||
|
||||
--------------6sNRIgaKOJHTghuLxPUzlxn1
|
||||
Content-Type: multipart/mixed; boundary="------------Z0a0IncIqoVlOIpOfcALEK5P"
|
||||
|
||||
--------------Z0a0IncIqoVlOIpOfcALEK5P
|
||||
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
DQo=
|
||||
--------------Z0a0IncIqoVlOIpOfcALEK5P
|
||||
Content-Type: message/rfc822; name="simple html body.eml"
|
||||
Content-Disposition: attachment; filename="simple html body.eml"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Message-ID: <1655e652-1abc-575f-6e03-5bbf119afbfa@gmail.com>
|
||||
Date: Tue, 25 Apr 2023 17:12:15 +0200
|
||||
MIME-Version: 1.0
|
||||
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
|
||||
Thunderbird/102.10.0
|
||||
Content-Language: en-US
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
Subject: simple html body
|
||||
Autocrypt: addr=pm.bridge.qa@gmail.com; keydata=
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxC
|
||||
ZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQO
|
||||
L9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIY
|
||||
zsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNM
|
||||
Zf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEB
|
||||
AAHNKEJyaWRnZSBLeXUtRWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgC
|
||||
GwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfU
|
||||
zQAKCRAWHAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn40UqX9gH
|
||||
bO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsilnh0RAmDV7fLoOfKX
|
||||
MLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vPegmxiLUwmsHIIDwTq6qZ0CVx
|
||||
bt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X00Ub326yay3BBUnsL4PJIGoly8hnLb5N
|
||||
3cyNzsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3D07kCBNvu4HQfps6h1rgNxGhE32VmpESHeb
|
||||
vIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF88Fyf2NBcejpIbcP7BS/g+C6KOowYj+
|
||||
Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCaknm3c7dNuXmhflz/w3aBj32q9Zy
|
||||
GqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPkutTouEZQbMK+i+chOH3AsKC
|
||||
uNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr8RBowVAdcvjZxeIDKAL
|
||||
ABEBAAHCwHYEGAEIACACGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUugAKCRAWHAh1
|
||||
giNZ9ycfB/97iOFFeYSFB5CqOxsyo9TjHve0BOzfGyLh632+sUQEw3qqwVreAqrjVa/wnLFW
|
||||
67Pbf4GBn6ZnaIMIq4rv6d4wnDZ4b18yQkITErWvU+3thpeZS/NUeWlLZlXFauDhCdPpAq5T
|
||||
nw7+5lq0pe2BEcNBF4IRoBRs7UQVhr/QpN+xawbnQUM1oIOMljTWuQZtPo75OsltQn59+OTn
|
||||
7ZYQD4Q3Sn0vPAzFbPBa4fostzbx8ktTGWfhBctQrpwS06VqoSowr8RFY9S+ssjCK+Hlq2t8
|
||||
ZC0CBL1aU1AAPqcAOfrw9rin0rN1dH07g1uDeZ9SnyLuee5QN7r3VY8b9tiOJV9m
|
||||
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||
protocol="application/pgp-signature";
|
||||
boundary="------------Kh8bXWpcGJrKDmQHlv9QWtgJ"
|
||||
|
||||
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||
--------------Kh8bXWpcGJrKDmQHlv9QWtgJ
|
||||
Content-Type: multipart/mixed; boundary="------------iFRY0nd000IWAcfqrg6ZdUuF";
|
||||
protected-headers="v1"
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
Message-ID: <1655e652-1abc-575f-6e03-5bbf119afbfa@gmail.com>
|
||||
Subject: simple html body
|
||||
|
||||
--------------iFRY0nd000IWAcfqrg6ZdUuF
|
||||
Content-Type: multipart/mixed; boundary="------------0tRoF00iqRv70TBczVNfagKk"
|
||||
|
||||
--------------0tRoF00iqRv70TBczVNfagKk
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||
-8">
|
||||
</head>
|
||||
<body>
|
||||
<p> </p>
|
||||
<p> And this is HTML<br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>Do I enjoy making courthouse puns?</b>
|
||||
Guilty.=3D3DE2=3D3D80=3D3D9=3D
|
||||
4 <i>@=3D3D baddadjokes</i></li>
|
||||
<li><b>Can February March?</b> No, but April May. =3D3DE2=3D3D80=3D=
|
||||
3D94<i=3D>@Bear=3D3D
|
||||
dedMOGuy</i=3D></li>
|
||||
</ul>
|
||||
<p> <br>
|
||||
</p>
|
||||
<p>
|
||||
<meta http-equiv=3D"3D"" content-type"=3D""
|
||||
content=3D"3D"text/html;" charset=3D"3DUTF=3D" -8"=3D"">
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
--------------0tRoF00iqRv70TBczVNfagKk
|
||||
Content-Type: application/pgp-keys; name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Disposition: attachment; filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Description: OpenPGP public key
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mst
|
||||
EhTfuxxCZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UEl
|
||||
MRQaQGzoCadQMaQOL9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6
|
||||
RrI6aZhjWG73xlqxS65dzTIYzsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYF
|
||||
Koc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HV
|
||||
akvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLeXUtRWh5
|
||||
aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgCGwMFCwkIBwIGFQoJ
|
||||
CAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUzQAKCRAW
|
||||
HAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn
|
||||
40UqX9gHbO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsil
|
||||
nh0RAmDV7fLoOfKXMLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vP
|
||||
egmxiLUwmsHIIDwTq6qZ0CVxbt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X
|
||||
00Ub326yay3BBUnsL4PJIGoly8hnLb5N3cyNwsCUBBMBCAA+FiEEXOYJeXAvKFvz
|
||||
KPmxFhwIdYIjWfcFAlxlUPwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgEC
|
||||
F4AACgkQFhwIdYIjWfeQzgf+NWseR+UvrJ7I1CFd6M2+RmyIC1QTQ+uyJIhIBQfC
|
||||
vBhueU9GUPWR6a6GWsb2DGCMkBM2aHoTCcf46fq87VspYb3+b0JsKfRFxBa2cuNH
|
||||
Ey6PK3xHBDnYFtSZaRB7QMQMfYVin5oyHq8mOd4mImOKfpGhuuhq1hrT8sOhVxRY
|
||||
Nl/2Hanya7tEJlVyAAEwtN4QVCqiRjjD7kBQ+mgxdDFo62X+sl4Zz2BFlZks+c1+
|
||||
LRWwaQZvGgf2tm2NqZhC04CKc2Gg5j7wNJBPVh/FVluxY27D2hV6v9/cTvXDGo8J
|
||||
pnZ28CnQqiiaKEU83BGwcUlcr4G5YApHyIPujaxffE2R887ATQRcZVD8AQgA4dh7
|
||||
F9t8lZybfX8Rc9w9O5AgTb7uB0H6bOoda4DcRoRN9lZqREh3m7yAecYy+sSmyKqk
|
||||
Wt8f+yg1QTSb3GtIFLKhLeZM2BfPBcn9jQXHo6SG3D+wUv4PguijqMGI/hLduk+h
|
||||
ncBVxMVHJfNCb6F/Mh0Dn7yaXYgUW48gfFKnwmpJ5t3O3Tbl5oX5c/8N2gY99qvW
|
||||
chqgNTaRwqS8lQJb0jUP6Wrf6xlAj2fXbsOCoVFKhJmrUD1RT5LrU6LhGUGzCvov
|
||||
nITh9wLCgrjQ37wg8Iq57EqiCZgIJdZQuHXvm7FmAjKjfXovfYTgkVMoWd4N7K/E
|
||||
QaMFQHXL42cXiAygCwARAQABwsB2BBgBCAAgAhsMFiEEXOYJeXAvKFvzKPmxFhwI
|
||||
dYIjWfcFAmRH1LoACgkQFhwIdYIjWfcnHwf/e4jhRXmEhQeQqjsbMqPU4x73tATs
|
||||
3xsi4et9vrFEBMN6qsFa3gKq41Wv8JyxVuuz23+BgZ+mZ2iDCKuK7+neMJw2eG9f
|
||||
MkJCExK1r1Pt7YaXmUvzVHlpS2ZVxWrg4QnT6QKuU58O/uZatKXtgRHDQReCEaAU
|
||||
bO1EFYa/0KTfsWsG50FDNaCDjJY01rkGbT6O+TrJbUJ+ffjk5+2WEA+EN0p9LzwM
|
||||
xWzwWuH6LLc28fJLUxln4QXLUK6cEtOlaqEqMK/ERWPUvrLIwivh5atrfGQtAgS9
|
||||
WlNQAD6nADn68Pa4p9KzdXR9O4Nbg3mfUp8i7nnuUDe691WPG/bYjiVfZsLAfAQY
|
||||
AQgAJhYhBFzmCXlwLyhb8yj5sRYcCHWCI1n3BQJcZVD8AhsMBQkDwmcAAAoJEBYc
|
||||
CHWCI1n3rhcH/iCB0ZV861H0RKJ2F7bXEyCLR2ncBFUCnFo3muSrN9NXTojz2vwv
|
||||
zexRBpZzaRJoksBkvH+ofuZ1iK7ycZO23dnukvPwGQsz3QiITjVeB6ZR0250MG1q
|
||||
A5yZRlZCsCbGJb4/2e8Ey8BbblHn49Zta4l2Ex5NpNNQ8FYoqXhXu5Bd2F/wX/Bp
|
||||
5gkZegfE3H9Dw4QjP82Mt0RZSBg9RMGCk6nNfEuze1Up+IOdtqzf3/Z8J5XxLzN2
|
||||
s8WPmDwJDwvxJRtto8U+ulv4ElcwlA+wYiKAq7cRCKGM/si5ClkUNgb319grUrBU
|
||||
6h8SuYtgnD84965xRiVAgtH4wCPN544N8CE=3D
|
||||
=3DNmqc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------0tRoF00iqRv70TBczVNfagKk--
|
||||
|
||||
--------------iFRY0nd000IWAcfqrg6ZdUuF--
|
||||
|
||||
--------------Kh8bXWpcGJrKDmQHlv9QWtgJ
|
||||
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmRH7c8FAwAAAAAACgkQFhwIdYIjWffG
|
||||
uQgAlK68JzT/9NLOg4SZPUbVaEdlbru6ebX1Lwaj93FZEKfmiEZ4heuYfq4yVw1JI/CGhHa1wxUJ
|
||||
M6+Tg+0lqIslG1rTposgIziugMBhEWXYWXBwDdvu+9T2eqo0se3h+oXueVlRihowfuPcP9jvt6p3
|
||||
DR+pu+hJ/D42DarJrn8v0CMm3PyLNHodyKLmEtZrzP4ok9Wal850xZ3Tmu5dL3v300wPGJaxrn8o
|
||||
4OeHRUodE43UijUrFKNoEfhqzY8HsMKIdF8Bi6+XiA0sHwcaUqJsaczBqgmbJbPqG8N4CokiUnUu
|
||||
4QDmClWa0rG4I5rrx4PrTbzAt7cGIb8N3ACNI7vI/Q==
|
||||
=Llod
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
--------------Kh8bXWpcGJrKDmQHlv9QWtgJ--
|
||||
--------------Z0a0IncIqoVlOIpOfcALEK5P
|
||||
Content-Type: application/pgp-keys; name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Disposition: attachment; filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Description: OpenPGP public key
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mst
|
||||
EhTfuxxCZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UEl
|
||||
MRQaQGzoCadQMaQOL9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6
|
||||
RrI6aZhjWG73xlqxS65dzTIYzsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYF
|
||||
Koc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HV
|
||||
akvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLeXUtRWh5
|
||||
aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgCGwMFCwkIBwIGFQoJ
|
||||
CAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUzQAKCRAW
|
||||
HAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn
|
||||
40UqX9gHbO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsil
|
||||
nh0RAmDV7fLoOfKXMLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vP
|
||||
egmxiLUwmsHIIDwTq6qZ0CVxbt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X
|
||||
00Ub326yay3BBUnsL4PJIGoly8hnLb5N3cyNwsCUBBMBCAA+FiEEXOYJeXAvKFvz
|
||||
KPmxFhwIdYIjWfcFAlxlUPwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgEC
|
||||
F4AACgkQFhwIdYIjWfeQzgf+NWseR+UvrJ7I1CFd6M2+RmyIC1QTQ+uyJIhIBQfC
|
||||
vBhueU9GUPWR6a6GWsb2DGCMkBM2aHoTCcf46fq87VspYb3+b0JsKfRFxBa2cuNH
|
||||
Ey6PK3xHBDnYFtSZaRB7QMQMfYVin5oyHq8mOd4mImOKfpGhuuhq1hrT8sOhVxRY
|
||||
Nl/2Hanya7tEJlVyAAEwtN4QVCqiRjjD7kBQ+mgxdDFo62X+sl4Zz2BFlZks+c1+
|
||||
LRWwaQZvGgf2tm2NqZhC04CKc2Gg5j7wNJBPVh/FVluxY27D2hV6v9/cTvXDGo8J
|
||||
pnZ28CnQqiiaKEU83BGwcUlcr4G5YApHyIPujaxffE2R887ATQRcZVD8AQgA4dh7
|
||||
F9t8lZybfX8Rc9w9O5AgTb7uB0H6bOoda4DcRoRN9lZqREh3m7yAecYy+sSmyKqk
|
||||
Wt8f+yg1QTSb3GtIFLKhLeZM2BfPBcn9jQXHo6SG3D+wUv4PguijqMGI/hLduk+h
|
||||
ncBVxMVHJfNCb6F/Mh0Dn7yaXYgUW48gfFKnwmpJ5t3O3Tbl5oX5c/8N2gY99qvW
|
||||
chqgNTaRwqS8lQJb0jUP6Wrf6xlAj2fXbsOCoVFKhJmrUD1RT5LrU6LhGUGzCvov
|
||||
nITh9wLCgrjQ37wg8Iq57EqiCZgIJdZQuHXvm7FmAjKjfXovfYTgkVMoWd4N7K/E
|
||||
QaMFQHXL42cXiAygCwARAQABwsB2BBgBCAAgAhsMFiEEXOYJeXAvKFvzKPmxFhwI
|
||||
dYIjWfcFAmRH1LoACgkQFhwIdYIjWfcnHwf/e4jhRXmEhQeQqjsbMqPU4x73tATs
|
||||
3xsi4et9vrFEBMN6qsFa3gKq41Wv8JyxVuuz23+BgZ+mZ2iDCKuK7+neMJw2eG9f
|
||||
MkJCExK1r1Pt7YaXmUvzVHlpS2ZVxWrg4QnT6QKuU58O/uZatKXtgRHDQReCEaAU
|
||||
bO1EFYa/0KTfsWsG50FDNaCDjJY01rkGbT6O+TrJbUJ+ffjk5+2WEA+EN0p9LzwM
|
||||
xWzwWuH6LLc28fJLUxln4QXLUK6cEtOlaqEqMK/ERWPUvrLIwivh5atrfGQtAgS9
|
||||
WlNQAD6nADn68Pa4p9KzdXR9O4Nbg3mfUp8i7nnuUDe691WPG/bYjiVfZsLAfAQY
|
||||
AQgAJhYhBFzmCXlwLyhb8yj5sRYcCHWCI1n3BQJcZVD8AhsMBQkDwmcAAAoJEBYc
|
||||
CHWCI1n3rhcH/iCB0ZV861H0RKJ2F7bXEyCLR2ncBFUCnFo3muSrN9NXTojz2vwv
|
||||
zexRBpZzaRJoksBkvH+ofuZ1iK7ycZO23dnukvPwGQsz3QiITjVeB6ZR0250MG1q
|
||||
A5yZRlZCsCbGJb4/2e8Ey8BbblHn49Zta4l2Ex5NpNNQ8FYoqXhXu5Bd2F/wX/Bp
|
||||
5gkZegfE3H9Dw4QjP82Mt0RZSBg9RMGCk6nNfEuze1Up+IOdtqzf3/Z8J5XxLzN2
|
||||
s8WPmDwJDwvxJRtto8U+ulv4ElcwlA+wYiKAq7cRCKGM/si5ClkUNgb319grUrBU
|
||||
6h8SuYtgnD84965xRiVAgtH4wCPN544N8CE=3D
|
||||
=3DNmqc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------Z0a0IncIqoVlOIpOfcALEK5P--
|
||||
|
||||
--------------6sNRIgaKOJHTghuLxPUzlxn1--
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename=OpenPGP_signature
|
||||
Content-Type: application/pgp-signature; name=OpenPGP_signature.asc
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GopenPGP 2.7.1
|
||||
Comment: https://gopenpgp.org
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmRIzW4FAwAAAAAACgkQ
|
||||
FhwIdYIjWfe84QgAj1mf/g/jvobkik9RRvyUCN3InyHBqcwMOgyVDmd4U5uh/DVZ
|
||||
sZ8kFU+/koEfr/8+Y7ataenEG5pY2f4krZNzc85VzjwniK1xcjxDW86UFa/ud/2Q
|
||||
OD7eqJUQO1UpuQ/xe4gbfy3BKvTp5u09UjcBDnZ/lHASSREpfGBhk37Y2lkb5iIP
|
||||
4KR7DOi1S1MFrdDPgKp59B9mk2ZMSC1Njzido9Zm/2tdYldAI66uMQiMgB2iv7yn
|
||||
FMGxKzewt2OxZWKVmNaAzzvrQw7AYfIr70NbwnK75Gmh1CkwLadNpzSRMCqW5BP6
|
||||
TsypxeGfXuQi0upWT2E3YpIvtkOrHRGXY9jXNw==
|
||||
=5Yce
|
||||
-----END PGP SIGNATURE-----
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--
|
||||
|
|
|
@ -1,116 +1,119 @@
|
|||
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||
protocol="application/pgp-signature";
|
||||
boundary="pavrbLYh8Q4RWBboYnVxY3mNBBzan1Zz4"
|
||||
|
||||
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||
--pavrbLYh8Q4RWBboYnVxY3mNBBzan1Zz4
|
||||
Content-Type: multipart/mixed; boundary="avFoFILZo8SdHM1Pc1OUviN4UKQh16HyR";
|
||||
protected-headers="v1"
|
||||
Subject: simple html body
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: schizofrenic@pm.me
|
||||
Message-ID: <d9c99685-4e1c-8f95-8b68-c6b0fcfd62ef@gmail.com>
|
||||
|
||||
--avFoFILZo8SdHM1Pc1OUviN4UKQh16HyR
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------9EAE2E1A715ACB9849E5C4E3"
|
||||
Content-Language: en-US
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------9EAE2E1A715ACB9849E5C4E3
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||
-8">
|
||||
</head>
|
||||
<body>
|
||||
And this is HTML<br>
|
||||
<ul>
|
||||
<li><b>Do I enjoy making courthouse puns?</b> Guilty.=E2=80=94 <i>@=
|
||||
baddadjokes</i></li>
|
||||
<li><b>Can February March?</b> No, but April May. =E2=80=94<i>@Bear=
|
||||
dedMOGuy</i></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--------------9EAE2E1A715ACB9849E5C4E3
|
||||
Content-Type: application/pgp-keys;
|
||||
name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: attachment;
|
||||
filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxCZ=
|
||||
pDh
|
||||
I5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQOL9WYT=
|
||||
f4S
|
||||
PWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIYzsyM/P97x=
|
||||
Snd
|
||||
NvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyB=
|
||||
OfN
|
||||
H5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLe=
|
||||
XUt
|
||||
RWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwJQEEwEIAD4CGwMFCwkIBwIGFQoJCAsCB=
|
||||
BYC
|
||||
AwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCYC32ygUJB4sMzgAKCRAWHAh1giNZ9=
|
||||
/K8
|
||||
B/4qs84Ii/zKH+q+C8vwO4jUJkOM73qD0pgB7zBs651zWbpgopyol1YUKNpFaHlx/Qch7RDI7=
|
||||
Vcz
|
||||
1+60/KZJSJR19/N2EDVbCUdh8ueioUp9X/218YWV2TRJNxTnljd4FAn7smZnXuP1TsLjQ6sKO=
|
||||
V0U
|
||||
u6JoiG6LZFXqDgxYpA++58Rkl6xaY6R71VkmVQlbEKtubX9AjHydq97Y+Jvn11XzWZaKhv4L7=
|
||||
6Pa
|
||||
4tMKXvvrKh1oywMmh6mZJo+5ZA/ABTkr45cwlTPYqGTS9+uvOHt+PH/oYwwJB4ls2cIAUldSj=
|
||||
TVQ
|
||||
IsseYz3LlbcCfKJiiCFxeHOQXA5J6zNLKOT58TsczsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3=
|
||||
D07
|
||||
kCBNvu4HQfps6h1rgNxGhE32VmpESHebvIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF=
|
||||
88F
|
||||
yf2NBcejpIbcP7BS/g+C6KOowYj+Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCa=
|
||||
knm
|
||||
3c7dNuXmhflz/w3aBj32q9ZyGqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPk=
|
||||
utT
|
||||
ouEZQbMK+i+chOH3AsKCuNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr=
|
||||
8RB
|
||||
owVAdcvjZxeIDKALABEBAAHCwHwEGAEIACYCGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCY=
|
||||
C32
|
||||
lAUJB4sMmAAKCRAWHAh1giNZ9+Y2B/9rTKZaKviae+ummXNumXcrKvbkAAvfuLpKUn53FlQLm=
|
||||
L6H
|
||||
jB++lJnPWvVSzdZxdv8FiPP3d632XHKUrkQRQM/9byRDXDommi7Qttx7YCkhd4JLVYqJqpnAQ=
|
||||
xI5
|
||||
RMkXiZNWyr1lz8JOM1XvDk1M7sJwPMWews8VOIE03E1nt7AsQGnvHtadgEnQaufrYNX3hFA8S=
|
||||
osO
|
||||
HSnedcys6yrzCSIGCqCD9VHbnMtS4DOv0XJGh2hwc8omzH0KZA517dyKBorJRwadcVauGXDKx=
|
||||
Etv
|
||||
Im4rl94PR/3An1Mj6HeeVVpLqDQ5Jb9J90BahWeQ53FzRa4EQzYCw0nLnxcsT1ZEEP5u
|
||||
=3Dv/1p
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------9EAE2E1A715ACB9849E5C4E3--
|
||||
|
||||
--avFoFILZo8SdHM1Pc1OUviN4UKQh16HyR--
|
||||
|
||||
--pavrbLYh8Q4RWBboYnVxY3mNBBzan1Zz4
|
||||
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmBa9hAFAwAAAAAACgkQFhwIdYIjWffL
|
||||
1AgApF18AVOPEm9y5R+d0NQmxqhSwAtvaqCwqQpG3mArIYK3Y0zrDkPQZZl/3emW8LWht7ZyYCAb
|
||||
NZo7HoYxjLy3yxAOPUl/Pc0nJpEqk/wAZT58yOnzv8DU5Q9o+444FfTMJpcrcH/M5cXYyqRtVhas
|
||||
k5wu5u2DEgSO3Kj/5l7lThb+CUgRC6wSiOuUkqGEWLiAguCdd88XDkLMbwrDnOu3PbhcA8o1msns
|
||||
PfkBdq3mFjp4M8M4ha+D2MxmV6tBv1E7snWf/spBVb9fHIa7zI4ZS6shpzGHCnJarO0Jco0Qh3IZ
|
||||
ZVfwhtJeFsmdqSm6DLvCmQWAYk2fDOZDMVKqe9IbUA==
|
||||
=pkS0
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
--pavrbLYh8Q4RWBboYnVxY3mNBBzan1Zz4--
|
||||
Content-Type: multipart/signed;
|
||||
boundary=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
|
||||
micalg=SHA-256; protocol="application/pgp-signature"
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Type: multipart/mixed; boundary="------------iFRY0nd000IWAcfqrg6ZdUuF";
|
||||
protected-headers="v1"
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
Message-ID: <1655e652-1abc-575f-6e03-5bbf119afbfa@gmail.com>
|
||||
Subject: simple html body
|
||||
|
||||
--------------iFRY0nd000IWAcfqrg6ZdUuF
|
||||
Content-Type: multipart/mixed; boundary="------------0tRoF00iqRv70TBczVNfagKk"
|
||||
|
||||
--------------0tRoF00iqRv70TBczVNfagKk
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||
-8">
|
||||
</head>
|
||||
<body>
|
||||
<p> </p>
|
||||
<p> And this is HTML<br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>Do I enjoy making courthouse puns?</b>
|
||||
Guilty.=3D3DE2=3D3D80=3D3D9=3D
|
||||
4 <i>@=3D3D baddadjokes</i></li>
|
||||
<li><b>Can February March?</b> No, but April May. =3D3DE2=3D3D80=3D=
|
||||
3D94<i=3D>@Bear=3D3D
|
||||
dedMOGuy</i=3D></li>
|
||||
</ul>
|
||||
<p> <br>
|
||||
</p>
|
||||
<p>
|
||||
<meta http-equiv=3D"3D"" content-type"=3D""
|
||||
content=3D"3D"text/html;" charset=3D"3DUTF=3D" -8"=3D"">
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
--------------0tRoF00iqRv70TBczVNfagKk
|
||||
Content-Type: application/pgp-keys; name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Disposition: attachment; filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Description: OpenPGP public key
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mst
|
||||
EhTfuxxCZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UEl
|
||||
MRQaQGzoCadQMaQOL9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6
|
||||
RrI6aZhjWG73xlqxS65dzTIYzsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYF
|
||||
Koc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HV
|
||||
akvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLeXUtRWh5
|
||||
aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgCGwMFCwkIBwIGFQoJ
|
||||
CAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUzQAKCRAW
|
||||
HAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn
|
||||
40UqX9gHbO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsil
|
||||
nh0RAmDV7fLoOfKXMLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vP
|
||||
egmxiLUwmsHIIDwTq6qZ0CVxbt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X
|
||||
00Ub326yay3BBUnsL4PJIGoly8hnLb5N3cyNwsCUBBMBCAA+FiEEXOYJeXAvKFvz
|
||||
KPmxFhwIdYIjWfcFAlxlUPwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgEC
|
||||
F4AACgkQFhwIdYIjWfeQzgf+NWseR+UvrJ7I1CFd6M2+RmyIC1QTQ+uyJIhIBQfC
|
||||
vBhueU9GUPWR6a6GWsb2DGCMkBM2aHoTCcf46fq87VspYb3+b0JsKfRFxBa2cuNH
|
||||
Ey6PK3xHBDnYFtSZaRB7QMQMfYVin5oyHq8mOd4mImOKfpGhuuhq1hrT8sOhVxRY
|
||||
Nl/2Hanya7tEJlVyAAEwtN4QVCqiRjjD7kBQ+mgxdDFo62X+sl4Zz2BFlZks+c1+
|
||||
LRWwaQZvGgf2tm2NqZhC04CKc2Gg5j7wNJBPVh/FVluxY27D2hV6v9/cTvXDGo8J
|
||||
pnZ28CnQqiiaKEU83BGwcUlcr4G5YApHyIPujaxffE2R887ATQRcZVD8AQgA4dh7
|
||||
F9t8lZybfX8Rc9w9O5AgTb7uB0H6bOoda4DcRoRN9lZqREh3m7yAecYy+sSmyKqk
|
||||
Wt8f+yg1QTSb3GtIFLKhLeZM2BfPBcn9jQXHo6SG3D+wUv4PguijqMGI/hLduk+h
|
||||
ncBVxMVHJfNCb6F/Mh0Dn7yaXYgUW48gfFKnwmpJ5t3O3Tbl5oX5c/8N2gY99qvW
|
||||
chqgNTaRwqS8lQJb0jUP6Wrf6xlAj2fXbsOCoVFKhJmrUD1RT5LrU6LhGUGzCvov
|
||||
nITh9wLCgrjQ37wg8Iq57EqiCZgIJdZQuHXvm7FmAjKjfXovfYTgkVMoWd4N7K/E
|
||||
QaMFQHXL42cXiAygCwARAQABwsB2BBgBCAAgAhsMFiEEXOYJeXAvKFvzKPmxFhwI
|
||||
dYIjWfcFAmRH1LoACgkQFhwIdYIjWfcnHwf/e4jhRXmEhQeQqjsbMqPU4x73tATs
|
||||
3xsi4et9vrFEBMN6qsFa3gKq41Wv8JyxVuuz23+BgZ+mZ2iDCKuK7+neMJw2eG9f
|
||||
MkJCExK1r1Pt7YaXmUvzVHlpS2ZVxWrg4QnT6QKuU58O/uZatKXtgRHDQReCEaAU
|
||||
bO1EFYa/0KTfsWsG50FDNaCDjJY01rkGbT6O+TrJbUJ+ffjk5+2WEA+EN0p9LzwM
|
||||
xWzwWuH6LLc28fJLUxln4QXLUK6cEtOlaqEqMK/ERWPUvrLIwivh5atrfGQtAgS9
|
||||
WlNQAD6nADn68Pa4p9KzdXR9O4Nbg3mfUp8i7nnuUDe691WPG/bYjiVfZsLAfAQY
|
||||
AQgAJhYhBFzmCXlwLyhb8yj5sRYcCHWCI1n3BQJcZVD8AhsMBQkDwmcAAAoJEBYc
|
||||
CHWCI1n3rhcH/iCB0ZV861H0RKJ2F7bXEyCLR2ncBFUCnFo3muSrN9NXTojz2vwv
|
||||
zexRBpZzaRJoksBkvH+ofuZ1iK7ycZO23dnukvPwGQsz3QiITjVeB6ZR0250MG1q
|
||||
A5yZRlZCsCbGJb4/2e8Ey8BbblHn49Zta4l2Ex5NpNNQ8FYoqXhXu5Bd2F/wX/Bp
|
||||
5gkZegfE3H9Dw4QjP82Mt0RZSBg9RMGCk6nNfEuze1Up+IOdtqzf3/Z8J5XxLzN2
|
||||
s8WPmDwJDwvxJRtto8U+ulv4ElcwlA+wYiKAq7cRCKGM/si5ClkUNgb319grUrBU
|
||||
6h8SuYtgnD84965xRiVAgtH4wCPN544N8CE=3D
|
||||
=3DNmqc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------0tRoF00iqRv70TBczVNfagKk--
|
||||
|
||||
--------------iFRY0nd000IWAcfqrg6ZdUuF--
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename=OpenPGP_signature
|
||||
Content-Type: application/pgp-signature; name=OpenPGP_signature.asc
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GopenPGP 2.7.1
|
||||
Comment: https://gopenpgp.org
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmRH7c8FAwAAAAAACgkQ
|
||||
FhwIdYIjWffGuQgAlK68JzT/9NLOg4SZPUbVaEdlbru6ebX1Lwaj93FZEKfmiEZ4
|
||||
heuYfq4yVw1JI/CGhHa1wxUJM6+Tg+0lqIslG1rTposgIziugMBhEWXYWXBwDdvu
|
||||
+9T2eqo0se3h+oXueVlRihowfuPcP9jvt6p3DR+pu+hJ/D42DarJrn8v0CMm3PyL
|
||||
NHodyKLmEtZrzP4ok9Wal850xZ3Tmu5dL3v300wPGJaxrn8o4OeHRUodE43UijUr
|
||||
FKNoEfhqzY8HsMKIdF8Bi6+XiA0sHwcaUqJsaczBqgmbJbPqG8N4CokiUnUu4QDm
|
||||
ClWa0rG4I5rrx4PrTbzAt7cGIb8N3ACNI7vI/Q==
|
||||
=Llod
|
||||
-----END PGP SIGNATURE-----
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--
|
||||
|
|
|
@ -1,161 +1,143 @@
|
|||
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||
protocol="application/pgp-signature";
|
||||
boundary="MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM"
|
||||
|
||||
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM
|
||||
Content-Type: multipart/mixed; boundary="FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378";
|
||||
protected-headers="v1"
|
||||
Subject: Alternative
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: schizofrenic@pm.me
|
||||
Message-ID: <753d0314-0286-2c88-2abb-f8080ac7a4cb@gmail.com>
|
||||
|
||||
--FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------F97C8ED4878E94675762AE43"
|
||||
Content-Language: en-US
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------F97C8ED4878E94675762AE43
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="------------041318B15DD3FA540FED32C6"
|
||||
|
||||
|
||||
--------------041318B15DD3FA540FED32C6
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
This Rich formated text
|
||||
|
||||
* /What kind of shoes do ninjas wear? /*Sneakers!*
|
||||
* /How does a penguin build its house?/**_/*Igloos it together.*/_
|
||||
|
||||
|
||||
|
||||
|
||||
--------------041318B15DD3FA540FED32C6
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||
-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>This <font color=3D"#ee24cc">Rich</font> formated text</p>
|
||||
<ul>
|
||||
<li><i>What kind of shoes do ninjas wear? </i><b>Sneakers!</b></li>=
|
||||
|
||||
<li><i>How does a penguin build its house?</i><b> </b><u><i><b>Iglo=
|
||||
os
|
||||
it together.</b></i></u></li>
|
||||
</ul>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--------------041318B15DD3FA540FED32C6--
|
||||
|
||||
--------------F97C8ED4878E94675762AE43
|
||||
Content-Type: application/pdf;
|
||||
name="minimal.pdf"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment;
|
||||
filename="minimal.pdf"
|
||||
|
||||
JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1Bh
|
||||
Z2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAg
|
||||
L0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0
|
||||
NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVu
|
||||
dCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8
|
||||
IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAv
|
||||
U3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21h
|
||||
bgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29u
|
||||
dGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0
|
||||
cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBU
|
||||
agogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAK
|
||||
MDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAw
|
||||
MDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAg
|
||||
ICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==
|
||||
--------------F97C8ED4878E94675762AE43
|
||||
Content-Type: application/pgp-keys;
|
||||
name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: attachment;
|
||||
filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxCZ=
|
||||
pDh
|
||||
I5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQOL9WYT=
|
||||
f4S
|
||||
PWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIYzsyM/P97x=
|
||||
Snd
|
||||
NvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyB=
|
||||
OfN
|
||||
H5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLe=
|
||||
XUt
|
||||
RWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwJQEEwEIAD4CGwMFCwkIBwIGFQoJCAsCB=
|
||||
BYC
|
||||
AwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCYC32ygUJB4sMzgAKCRAWHAh1giNZ9=
|
||||
/K8
|
||||
B/4qs84Ii/zKH+q+C8vwO4jUJkOM73qD0pgB7zBs651zWbpgopyol1YUKNpFaHlx/Qch7RDI7=
|
||||
Vcz
|
||||
1+60/KZJSJR19/N2EDVbCUdh8ueioUp9X/218YWV2TRJNxTnljd4FAn7smZnXuP1TsLjQ6sKO=
|
||||
V0U
|
||||
u6JoiG6LZFXqDgxYpA++58Rkl6xaY6R71VkmVQlbEKtubX9AjHydq97Y+Jvn11XzWZaKhv4L7=
|
||||
6Pa
|
||||
4tMKXvvrKh1oywMmh6mZJo+5ZA/ABTkr45cwlTPYqGTS9+uvOHt+PH/oYwwJB4ls2cIAUldSj=
|
||||
TVQ
|
||||
IsseYz3LlbcCfKJiiCFxeHOQXA5J6zNLKOT58TsczsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3=
|
||||
D07
|
||||
kCBNvu4HQfps6h1rgNxGhE32VmpESHebvIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF=
|
||||
88F
|
||||
yf2NBcejpIbcP7BS/g+C6KOowYj+Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCa=
|
||||
knm
|
||||
3c7dNuXmhflz/w3aBj32q9ZyGqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPk=
|
||||
utT
|
||||
ouEZQbMK+i+chOH3AsKCuNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr=
|
||||
8RB
|
||||
owVAdcvjZxeIDKALABEBAAHCwHwEGAEIACYCGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCY=
|
||||
C32
|
||||
lAUJB4sMmAAKCRAWHAh1giNZ9+Y2B/9rTKZaKviae+ummXNumXcrKvbkAAvfuLpKUn53FlQLm=
|
||||
L6H
|
||||
jB++lJnPWvVSzdZxdv8FiPP3d632XHKUrkQRQM/9byRDXDommi7Qttx7YCkhd4JLVYqJqpnAQ=
|
||||
xI5
|
||||
RMkXiZNWyr1lz8JOM1XvDk1M7sJwPMWews8VOIE03E1nt7AsQGnvHtadgEnQaufrYNX3hFA8S=
|
||||
osO
|
||||
HSnedcys6yrzCSIGCqCD9VHbnMtS4DOv0XJGh2hwc8omzH0KZA517dyKBorJRwadcVauGXDKx=
|
||||
Etv
|
||||
Im4rl94PR/3An1Mj6HeeVVpLqDQ5Jb9J90BahWeQ53FzRa4EQzYCw0nLnxcsT1ZEEP5u
|
||||
=3Dv/1p
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------F97C8ED4878E94675762AE43--
|
||||
|
||||
--FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378--
|
||||
|
||||
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM
|
||||
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmBciUoFAwAAAAAACgkQFhwIdYIjWfez
|
||||
rgf+NZCibnCUTovpWRVRiiPQtBPGeHUPEwz2xq2zz4AaqrHC2v4mYUIPe6am7INk8fkBLsa8Dj/A
|
||||
UN/28Qh7tNb7JsXtHDT4PIoXszukQ8VIRbe09mSkkP6jR4WzNR166d6n3rSxzHpviOyQldjjpOMr
|
||||
Zl7LxmgGr4ojsgCf6pvurWwCCOGJqbSusrD6JVv6DsmPmmQeBmnlTK/0oG9pnlNkugpNB1WS2K5d
|
||||
RY6+kWkSrxbq95HrgILpHip8Y/+ITWvQocm14PBIAAdW8Hr7iFQLETFJ/KDA+VP19Bt8n4Kitdi8
|
||||
DPqMsV0oOhATqBjnD63AePJ0VWg8R1z6GEK5A+WOpg==
|
||||
=Bc6p
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM--
|
||||
Content-Type: multipart/signed;
|
||||
boundary=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
|
||||
micalg=SHA-256; protocol="application/pgp-signature"
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Type: multipart/mixed; boundary="------------8730PPHC3FpcshavBTup7Fxz";
|
||||
protected-headers="v1"
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
Message-ID: <5dce90f2-f1e5-eecb-672e-f39def728f44@gmail.com>
|
||||
Subject: Alternative
|
||||
|
||||
--------------8730PPHC3FpcshavBTup7Fxz
|
||||
Content-Type: multipart/mixed; boundary="------------CCuN7f3eLVIM0a1fSlNjk0pq"
|
||||
|
||||
--------------CCuN7f3eLVIM0a1fSlNjk0pq
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="------------5kz0EAFr3j63ikmxfQPRt0bq"
|
||||
|
||||
--------------5kz0EAFr3j63ikmxfQPRt0bq
|
||||
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
VGhpcyBSaWNoIGZvcm1hdGVkIHRleHQNCg0KICAqIFdoYXQga2luZCBvZiBzaG9lcyBkbyBu
|
||||
aW5qYXMgd2Vhcj8gKlNuZWFrZXJzISoNCiAgKiBIb3cgZG9lcyBhIHBlbmd1aW4gYnVpbGQg
|
||||
aXRzIGhvdXNlPyAqSWdsb29zIGl0IHRvZ2V0aGVyKg0KDQpUaGlzIFJpY2ggZm9ybWF0ZWQg
|
||||
dGV4dA0KDQogICogL1doYXQga2luZCBvZiBzaG9lcyBkbyBuaW5qYXMgd2Vhcj8gLypTbmVh
|
||||
a2VycyEqDQogICogL0hvdyBkb2VzIGEgcGVuZ3VpbiBidWlsZCBpdHMgaG91c2U/LyoqXy8q
|
||||
SWdsb29zIGl0IHRvZ2V0aGVyLiovXw0KDQoNCg0K
|
||||
--------------5kz0EAFr3j63ikmxfQPRt0bq
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||
-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>This Rich formated text<br>
|
||||
</p>
|
||||
<ul>
|
||||
<li>What kind of shoes do ninjas wear? <b>Sneakers!</b></li>
|
||||
<li>How does a penguin build its house? <b>Igloos it together</b></=
|
||||
li>
|
||||
</ul>
|
||||
<p> </p>
|
||||
<p>This <font color=3D"3D"#ee24cc"">Rich</font> formated
|
||||
text</p>
|
||||
<ul>
|
||||
<li><i>What kind of shoes do ninjas wear? </i><b>Sneakers!</b></li>=
|
||||
|
||||
<li><i>How does a penguin build its house?</i><b> </b><u><i><b>Iglo=
|
||||
os
|
||||
it together.</b></i></u></li>
|
||||
</ul>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>
|
||||
<meta http-equiv=3D"3D"content-type""
|
||||
content=3D"3D"text/html;" charset=3D"3DUTF-8"">
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--------------5kz0EAFr3j63ikmxfQPRt0bq--
|
||||
--------------CCuN7f3eLVIM0a1fSlNjk0pq
|
||||
Content-Type: application/pgp-keys; name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Disposition: attachment; filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Description: OpenPGP public key
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mst
|
||||
EhTfuxxCZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UEl
|
||||
MRQaQGzoCadQMaQOL9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6
|
||||
RrI6aZhjWG73xlqxS65dzTIYzsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYF
|
||||
Koc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HV
|
||||
akvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLeXUtRWh5
|
||||
aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgCGwMFCwkIBwIGFQoJ
|
||||
CAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUzQAKCRAW
|
||||
HAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn
|
||||
40UqX9gHbO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsil
|
||||
nh0RAmDV7fLoOfKXMLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vP
|
||||
egmxiLUwmsHIIDwTq6qZ0CVxbt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X
|
||||
00Ub326yay3BBUnsL4PJIGoly8hnLb5N3cyNwsCUBBMBCAA+FiEEXOYJeXAvKFvz
|
||||
KPmxFhwIdYIjWfcFAlxlUPwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgEC
|
||||
F4AACgkQFhwIdYIjWfeQzgf+NWseR+UvrJ7I1CFd6M2+RmyIC1QTQ+uyJIhIBQfC
|
||||
vBhueU9GUPWR6a6GWsb2DGCMkBM2aHoTCcf46fq87VspYb3+b0JsKfRFxBa2cuNH
|
||||
Ey6PK3xHBDnYFtSZaRB7QMQMfYVin5oyHq8mOd4mImOKfpGhuuhq1hrT8sOhVxRY
|
||||
Nl/2Hanya7tEJlVyAAEwtN4QVCqiRjjD7kBQ+mgxdDFo62X+sl4Zz2BFlZks+c1+
|
||||
LRWwaQZvGgf2tm2NqZhC04CKc2Gg5j7wNJBPVh/FVluxY27D2hV6v9/cTvXDGo8J
|
||||
pnZ28CnQqiiaKEU83BGwcUlcr4G5YApHyIPujaxffE2R887ATQRcZVD8AQgA4dh7
|
||||
F9t8lZybfX8Rc9w9O5AgTb7uB0H6bOoda4DcRoRN9lZqREh3m7yAecYy+sSmyKqk
|
||||
Wt8f+yg1QTSb3GtIFLKhLeZM2BfPBcn9jQXHo6SG3D+wUv4PguijqMGI/hLduk+h
|
||||
ncBVxMVHJfNCb6F/Mh0Dn7yaXYgUW48gfFKnwmpJ5t3O3Tbl5oX5c/8N2gY99qvW
|
||||
chqgNTaRwqS8lQJb0jUP6Wrf6xlAj2fXbsOCoVFKhJmrUD1RT5LrU6LhGUGzCvov
|
||||
nITh9wLCgrjQ37wg8Iq57EqiCZgIJdZQuHXvm7FmAjKjfXovfYTgkVMoWd4N7K/E
|
||||
QaMFQHXL42cXiAygCwARAQABwsB2BBgBCAAgAhsMFiEEXOYJeXAvKFvzKPmxFhwI
|
||||
dYIjWfcFAmRH1LoACgkQFhwIdYIjWfcnHwf/e4jhRXmEhQeQqjsbMqPU4x73tATs
|
||||
3xsi4et9vrFEBMN6qsFa3gKq41Wv8JyxVuuz23+BgZ+mZ2iDCKuK7+neMJw2eG9f
|
||||
MkJCExK1r1Pt7YaXmUvzVHlpS2ZVxWrg4QnT6QKuU58O/uZatKXtgRHDQReCEaAU
|
||||
bO1EFYa/0KTfsWsG50FDNaCDjJY01rkGbT6O+TrJbUJ+ffjk5+2WEA+EN0p9LzwM
|
||||
xWzwWuH6LLc28fJLUxln4QXLUK6cEtOlaqEqMK/ERWPUvrLIwivh5atrfGQtAgS9
|
||||
WlNQAD6nADn68Pa4p9KzdXR9O4Nbg3mfUp8i7nnuUDe691WPG/bYjiVfZsLAfAQY
|
||||
AQgAJhYhBFzmCXlwLyhb8yj5sRYcCHWCI1n3BQJcZVD8AhsMBQkDwmcAAAoJEBYc
|
||||
CHWCI1n3rhcH/iCB0ZV861H0RKJ2F7bXEyCLR2ncBFUCnFo3muSrN9NXTojz2vwv
|
||||
zexRBpZzaRJoksBkvH+ofuZ1iK7ycZO23dnukvPwGQsz3QiITjVeB6ZR0250MG1q
|
||||
A5yZRlZCsCbGJb4/2e8Ey8BbblHn49Zta4l2Ex5NpNNQ8FYoqXhXu5Bd2F/wX/Bp
|
||||
5gkZegfE3H9Dw4QjP82Mt0RZSBg9RMGCk6nNfEuze1Up+IOdtqzf3/Z8J5XxLzN2
|
||||
s8WPmDwJDwvxJRtto8U+ulv4ElcwlA+wYiKAq7cRCKGM/si5ClkUNgb319grUrBU
|
||||
6h8SuYtgnD84965xRiVAgtH4wCPN544N8CE=3D
|
||||
=3DNmqc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------CCuN7f3eLVIM0a1fSlNjk0pq--
|
||||
|
||||
--------------8730PPHC3FpcshavBTup7Fxz--
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename=OpenPGP_signature
|
||||
Content-Type: application/pgp-signature; name=OpenPGP_signature.asc
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GopenPGP 2.7.1
|
||||
Comment: https://gopenpgp.org
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmRH94EFAwAAAAAACgkQ
|
||||
FhwIdYIjWfcFfgf/XHYGr9+DcntW0TLe6dO82xhLuO6HFmQnnChnfNHKA5U6pOeU
|
||||
5wJoEFL2O4WEU1hbuf5K0wpgPi8ZF+r966bCUTt+tYT/p7sKV0OXYJjtPYxiL+ju
|
||||
RaNZgK3rIDyi2DNjbRXSV1Y7H2S3pMLAwPio7ovNpe3OgfkAgFDdiG+NnXl8CzqN
|
||||
suhMwqJbLJRWlEaX7UdbcLimpNtPIU7wtr4YV0NIsqt3EV7AeHnVI9cHJMLvF5tB
|
||||
VrnkxXbdO3xEylUB+MNmX5NDIrCg5Iwwq4NMk6qaMK80J9XSxERln56SJVN/+tIu
|
||||
TZFgwBWM/n48prGXrEo8TPk5UkrVYhLXl/ndTg==
|
||||
=FIoh
|
||||
-----END PGP SIGNATURE-----
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--
|
||||
|
|
|
@ -1,103 +1,95 @@
|
|||
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||
protocol="application/pgp-signature";
|
||||
boundary="x4FrOFG2PnNJvlbzxe80NPwxzh2yUHABp"
|
||||
|
||||
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||
--x4FrOFG2PnNJvlbzxe80NPwxzh2yUHABp
|
||||
Content-Type: multipart/mixed; boundary="bBln6dwDJTLkin5LPHkHBQudqRLwIzTUH";
|
||||
protected-headers="v1"
|
||||
Subject: simple plaintext body
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: schizofrenic@pm.me
|
||||
Message-ID: <adb5ac5d-b8f6-c9a3-5cc0-0fb2e9677512@gmail.com>
|
||||
|
||||
--bBln6dwDJTLkin5LPHkHBQudqRLwIzTUH
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------1B34C666A4C2FB03E0324F1A"
|
||||
Content-Language: en-US
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------1B34C666A4C2FB03E0324F1A
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Why don't crabs give to charity? Because they're shellfish.
|
||||
|
||||
|
||||
|
||||
--------------1B34C666A4C2FB03E0324F1A
|
||||
Content-Type: application/pgp-keys;
|
||||
name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: attachment;
|
||||
filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxCZ=
|
||||
pDh
|
||||
I5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQOL9WYT=
|
||||
f4S
|
||||
PWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIYzsyM/P97x=
|
||||
Snd
|
||||
NvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyB=
|
||||
OfN
|
||||
H5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLe=
|
||||
XUt
|
||||
RWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwJQEEwEIAD4CGwMFCwkIBwIGFQoJCAsCB=
|
||||
BYC
|
||||
AwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCYC32ygUJB4sMzgAKCRAWHAh1giNZ9=
|
||||
/K8
|
||||
B/4qs84Ii/zKH+q+C8vwO4jUJkOM73qD0pgB7zBs651zWbpgopyol1YUKNpFaHlx/Qch7RDI7=
|
||||
Vcz
|
||||
1+60/KZJSJR19/N2EDVbCUdh8ueioUp9X/218YWV2TRJNxTnljd4FAn7smZnXuP1TsLjQ6sKO=
|
||||
V0U
|
||||
u6JoiG6LZFXqDgxYpA++58Rkl6xaY6R71VkmVQlbEKtubX9AjHydq97Y+Jvn11XzWZaKhv4L7=
|
||||
6Pa
|
||||
4tMKXvvrKh1oywMmh6mZJo+5ZA/ABTkr45cwlTPYqGTS9+uvOHt+PH/oYwwJB4ls2cIAUldSj=
|
||||
TVQ
|
||||
IsseYz3LlbcCfKJiiCFxeHOQXA5J6zNLKOT58TsczsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3=
|
||||
D07
|
||||
kCBNvu4HQfps6h1rgNxGhE32VmpESHebvIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF=
|
||||
88F
|
||||
yf2NBcejpIbcP7BS/g+C6KOowYj+Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCa=
|
||||
knm
|
||||
3c7dNuXmhflz/w3aBj32q9ZyGqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPk=
|
||||
utT
|
||||
ouEZQbMK+i+chOH3AsKCuNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr=
|
||||
8RB
|
||||
owVAdcvjZxeIDKALABEBAAHCwHwEGAEIACYCGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCY=
|
||||
C32
|
||||
lAUJB4sMmAAKCRAWHAh1giNZ9+Y2B/9rTKZaKviae+ummXNumXcrKvbkAAvfuLpKUn53FlQLm=
|
||||
L6H
|
||||
jB++lJnPWvVSzdZxdv8FiPP3d632XHKUrkQRQM/9byRDXDommi7Qttx7YCkhd4JLVYqJqpnAQ=
|
||||
xI5
|
||||
RMkXiZNWyr1lz8JOM1XvDk1M7sJwPMWews8VOIE03E1nt7AsQGnvHtadgEnQaufrYNX3hFA8S=
|
||||
osO
|
||||
HSnedcys6yrzCSIGCqCD9VHbnMtS4DOv0XJGh2hwc8omzH0KZA517dyKBorJRwadcVauGXDKx=
|
||||
Etv
|
||||
Im4rl94PR/3An1Mj6HeeVVpLqDQ5Jb9J90BahWeQ53FzRa4EQzYCw0nLnxcsT1ZEEP5u
|
||||
=3Dv/1p
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------1B34C666A4C2FB03E0324F1A--
|
||||
|
||||
--bBln6dwDJTLkin5LPHkHBQudqRLwIzTUH--
|
||||
|
||||
--x4FrOFG2PnNJvlbzxe80NPwxzh2yUHABp
|
||||
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmBa9YIFAwAAAAAACgkQFhwIdYIjWfem
|
||||
vQgAjUMAaxL7D6fRtFBqLjdQGr7PkDBigeQD9ax17CJFld7Zfo2dAYUzYJRi0HP0Kn1YCSBppF0w
|
||||
5/P8458H2sqfPC32ptbDCZ/seL0Rpt/gRx6yufbz7wQC0iUZxqxBq2Ox9PGZYSCrTO837lAVYxUo
|
||||
aMnDL/K9ohAGIyTZVv31z+r3LLWQsFpfpB5hJFqsjQXA9IGKSQIkWbaeE+0wveJSwqxdTwYvsHs2
|
||||
xjBw+s8tRHO/whP4pvzL185fGsHAb8x9a9oyoDVcszhw5xBpiWW37mI58qkQ6g+4wTarreuXGTp3
|
||||
RKgPupoYOMJja90yh3TWovcmuZz6QOgne5Rbn3s+Vg==
|
||||
=hUb8
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
--x4FrOFG2PnNJvlbzxe80NPwxzh2yUHABp--
|
||||
Content-Type: multipart/signed;
|
||||
boundary=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;
|
||||
micalg=SHA-256; protocol="application/pgp-signature"
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Type: multipart/mixed; boundary="------------bhHlRtbq4tEzp2NgVmAJ4AbV";
|
||||
protected-headers="v1"
|
||||
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||
To: "InfernalBridgeTester@proton.me" <InfernalbridgeTester@proton.me>
|
||||
Message-ID: <9bb09a2a-1442-9439-f53f-490df7e46331@gmail.com>
|
||||
Subject: simple plaintext body
|
||||
|
||||
--------------bhHlRtbq4tEzp2NgVmAJ4AbV
|
||||
Content-Type: multipart/mixed; boundary="------------FHzf8jBNv06d9SowGc7KOjXr"
|
||||
|
||||
--------------FHzf8jBNv06d9SowGc7KOjXr
|
||||
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
V2h5IGRvbid0IGNyYWJzIGdpdmUgdG8gY2hhcml0eT8gQmVjYXVzZSB0aGV5J3JlIHNoZWxs
|
||||
ZmlzaC4NCg0K
|
||||
--------------FHzf8jBNv06d9SowGc7KOjXr
|
||||
Content-Type: application/pgp-keys; name="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Disposition: attachment; filename="OpenPGP_0x161C0875822359F7.asc"
|
||||
Content-Description: OpenPGP public key
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mst
|
||||
EhTfuxxCZpDhI5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UEl
|
||||
MRQaQGzoCadQMaQOL9WYTf4SPWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6
|
||||
RrI6aZhjWG73xlqxS65dzTIYzsyM/P97xSndNvlvWtGvLlpFkzxfAEGpVzfOYVYF
|
||||
Koc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyBOfNH5fpU8r7A5Q7l+HV
|
||||
akvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLeXUtRWh5
|
||||
aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwI4EEwEIADgCGwMFCwkIBwIGFQoJ
|
||||
CAsCBBYCAwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCZEfUzQAKCRAW
|
||||
HAh1giNZ9872B/4mM6cXErSSYK6/apGUVebg9QiP1RhFlLE/kg7BW3FaSP/yTUZN
|
||||
ZdX7WPnEVyaa5Dk4xRZiB47Yc5myspwc+JEJ3YDAHq+4n/D74TF1kUCzP+QVsWcn
|
||||
40UqX9gHbO01O/DYtoxMOljEgkfQjEZcRoHuUhCUzldFf8aV+uZKiOXhrPYCwsil
|
||||
nh0RAmDV7fLoOfKXMLiKXE8wM/5Bax+dk2AmEM4bOTIo58GGDDqseIg03ocrW7vP
|
||||
egmxiLUwmsHIIDwTq6qZ0CVxbt2uv4cCyNz/0pmRzG7p8192Evdu8JOuLSj3pI1X
|
||||
00Ub326yay3BBUnsL4PJIGoly8hnLb5N3cyNwsCUBBMBCAA+FiEEXOYJeXAvKFvz
|
||||
KPmxFhwIdYIjWfcFAlxlUPwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgEC
|
||||
F4AACgkQFhwIdYIjWfeQzgf+NWseR+UvrJ7I1CFd6M2+RmyIC1QTQ+uyJIhIBQfC
|
||||
vBhueU9GUPWR6a6GWsb2DGCMkBM2aHoTCcf46fq87VspYb3+b0JsKfRFxBa2cuNH
|
||||
Ey6PK3xHBDnYFtSZaRB7QMQMfYVin5oyHq8mOd4mImOKfpGhuuhq1hrT8sOhVxRY
|
||||
Nl/2Hanya7tEJlVyAAEwtN4QVCqiRjjD7kBQ+mgxdDFo62X+sl4Zz2BFlZks+c1+
|
||||
LRWwaQZvGgf2tm2NqZhC04CKc2Gg5j7wNJBPVh/FVluxY27D2hV6v9/cTvXDGo8J
|
||||
pnZ28CnQqiiaKEU83BGwcUlcr4G5YApHyIPujaxffE2R887ATQRcZVD8AQgA4dh7
|
||||
F9t8lZybfX8Rc9w9O5AgTb7uB0H6bOoda4DcRoRN9lZqREh3m7yAecYy+sSmyKqk
|
||||
Wt8f+yg1QTSb3GtIFLKhLeZM2BfPBcn9jQXHo6SG3D+wUv4PguijqMGI/hLduk+h
|
||||
ncBVxMVHJfNCb6F/Mh0Dn7yaXYgUW48gfFKnwmpJ5t3O3Tbl5oX5c/8N2gY99qvW
|
||||
chqgNTaRwqS8lQJb0jUP6Wrf6xlAj2fXbsOCoVFKhJmrUD1RT5LrU6LhGUGzCvov
|
||||
nITh9wLCgrjQ37wg8Iq57EqiCZgIJdZQuHXvm7FmAjKjfXovfYTgkVMoWd4N7K/E
|
||||
QaMFQHXL42cXiAygCwARAQABwsB2BBgBCAAgAhsMFiEEXOYJeXAvKFvzKPmxFhwI
|
||||
dYIjWfcFAmRH1LoACgkQFhwIdYIjWfcnHwf/e4jhRXmEhQeQqjsbMqPU4x73tATs
|
||||
3xsi4et9vrFEBMN6qsFa3gKq41Wv8JyxVuuz23+BgZ+mZ2iDCKuK7+neMJw2eG9f
|
||||
MkJCExK1r1Pt7YaXmUvzVHlpS2ZVxWrg4QnT6QKuU58O/uZatKXtgRHDQReCEaAU
|
||||
bO1EFYa/0KTfsWsG50FDNaCDjJY01rkGbT6O+TrJbUJ+ffjk5+2WEA+EN0p9LzwM
|
||||
xWzwWuH6LLc28fJLUxln4QXLUK6cEtOlaqEqMK/ERWPUvrLIwivh5atrfGQtAgS9
|
||||
WlNQAD6nADn68Pa4p9KzdXR9O4Nbg3mfUp8i7nnuUDe691WPG/bYjiVfZsLAfAQY
|
||||
AQgAJhYhBFzmCXlwLyhb8yj5sRYcCHWCI1n3BQJcZVD8AhsMBQkDwmcAAAoJEBYc
|
||||
CHWCI1n3rhcH/iCB0ZV861H0RKJ2F7bXEyCLR2ncBFUCnFo3muSrN9NXTojz2vwv
|
||||
zexRBpZzaRJoksBkvH+ofuZ1iK7ycZO23dnukvPwGQsz3QiITjVeB6ZR0250MG1q
|
||||
A5yZRlZCsCbGJb4/2e8Ey8BbblHn49Zta4l2Ex5NpNNQ8FYoqXhXu5Bd2F/wX/Bp
|
||||
5gkZegfE3H9Dw4QjP82Mt0RZSBg9RMGCk6nNfEuze1Up+IOdtqzf3/Z8J5XxLzN2
|
||||
s8WPmDwJDwvxJRtto8U+ulv4ElcwlA+wYiKAq7cRCKGM/si5ClkUNgb319grUrBU
|
||||
6h8SuYtgnD84965xRiVAgtH4wCPN544N8CE=3D
|
||||
=3DNmqc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
--------------FHzf8jBNv06d9SowGc7KOjXr--
|
||||
|
||||
--------------bhHlRtbq4tEzp2NgVmAJ4AbV--
|
||||
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
Content-Description: OpenPGP digital signature
|
||||
Content-Disposition: attachment; filename=OpenPGP_signature
|
||||
Content-Type: application/pgp-signature; name=OpenPGP_signature.asc
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Comment: https://gopenpgp.org
|
||||
Version: GopenPGP 2.7.1
|
||||
|
||||
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmRH6MwFAwAAAAAACgkQ
|
||||
FhwIdYIjWfdX5Qf/W2hFY5PiCrRTvcXGASc2RBLXmCh/mn0tTNUsGtyq/MhcNKfR
|
||||
9bSFCb3xz9q26MAJDHtO/Vm0lUjre42rLMkEIDIdJT960HIClELzmgglwFbVgdqy
|
||||
T0Psma8ySQpZ2LxZ1oleCXeXaxm4DOwQP+COfb5+FmLTA2z1djLA3HjFPNKglcUr
|
||||
atzCTvlt2yqwrx6aeqTxcFezPkl1o+kdqjMCMP0LFFuImuor0vaCFUz76hgNC3kk
|
||||
CflcYGPJIVH7D06UXkLKC5vnJZ+Pidn4K5sMkF3nXBlourmSU2cZFTNUUdHNAxi8
|
||||
s9XTavfQxm4fyLPyGcgNpdM6PhZW9r+lZkr66w==
|
||||
=se49
|
||||
-----END PGP SIGNATURE-----
|
||||
--e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"net"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -43,19 +44,19 @@ func IsPortFree(port int) bool {
|
|||
|
||||
func isOccupied(port string) bool {
|
||||
// Try to create server at port.
|
||||
dummyserver, err := net.Listen("tcp", port)
|
||||
dummyServer, err := net.Listen("tcp", port)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
_ = dummyserver.Close()
|
||||
_ = dummyServer.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
// FindFreePortFrom finds first empty port, starting with `startPort`.
|
||||
func FindFreePortFrom(startPort int) int {
|
||||
// FindFreePortFrom finds first empty port, starting with `startPort`, and excluding ports listed in exclude.
|
||||
func FindFreePortFrom(startPort int, exclude ...int) int {
|
||||
loopedOnce := false
|
||||
freePort := startPort
|
||||
for !IsPortFree(freePort) {
|
||||
for slices.Contains(exclude, freePort) || !IsPortFree(freePort) {
|
||||
freePort++
|
||||
if freePort >= maxPortNumber {
|
||||
freePort = 1
|
||||
|
|
|
@ -32,12 +32,12 @@ func TestFreePort(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOccupiedPort(t *testing.T) {
|
||||
dummyserver, err := net.Listen("tcp", ":"+strconv.Itoa(testPort))
|
||||
dummyServer, err := net.Listen("tcp", ":"+strconv.Itoa(testPort))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, !IsPortFree(testPort), "port should be occupied")
|
||||
|
||||
_ = dummyserver.Close()
|
||||
_ = dummyServer.Close()
|
||||
}
|
||||
|
||||
func TestFindFreePortFromDirectly(t *testing.T) {
|
||||
|
@ -46,11 +46,21 @@ func TestFindFreePortFromDirectly(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFindFreePortFromNextOne(t *testing.T) {
|
||||
dummyserver, err := net.Listen("tcp", ":"+strconv.Itoa(testPort))
|
||||
dummyServer, err := net.Listen("tcp", ":"+strconv.Itoa(testPort))
|
||||
require.NoError(t, err)
|
||||
|
||||
foundPort := FindFreePortFrom(testPort)
|
||||
require.Equal(t, testPort+1, foundPort)
|
||||
|
||||
_ = dummyserver.Close()
|
||||
_ = dummyServer.Close()
|
||||
}
|
||||
|
||||
func TestFindFreePortExcluding(t *testing.T) {
|
||||
dummyServer, err := net.Listen("tcp", ":"+strconv.Itoa(testPort))
|
||||
require.NoError(t, err)
|
||||
|
||||
foundPort := FindFreePortFrom(testPort, testPort+1, testPort+2)
|
||||
require.Equal(t, testPort+3, foundPort)
|
||||
|
||||
_ = dummyServer.Close()
|
||||
}
|
||||
|
|
|
@ -107,7 +107,10 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^the header in the "([^"]*)" request to "([^"]*)" has "([^"]*)" set to "([^"]*)"$`, s.theHeaderInTheRequestToHasSetTo)
|
||||
ctx.Step(`^the body in the "([^"]*)" request to "([^"]*)" is:$`, s.theBodyInTheRequestToIs)
|
||||
ctx.Step(`^the API requires bridge version at least "([^"]*)"$`, s.theAPIRequiresBridgeVersion)
|
||||
|
||||
ctx.Step(`^the network port (\d+) is busy$`, s.networkPortIsBusy)
|
||||
ctx.Step(`^the network port range (\d+)-(\d+) is busy$`, s.networkPortRangeIsBusy)
|
||||
ctx.Step(`^bridge IMAP port is (\d+)`, s.bridgeIMAPPortIs)
|
||||
ctx.Step(`^bridge SMTP port is (\d+)`, s.bridgeSMTPPortIs)
|
||||
// ==== SETUP ====
|
||||
ctx.Step(`^there exists an account with username "([^"]*)" and password "([^"]*)"$`, s.thereExistsAnAccountWithUsernameAndPassword)
|
||||
ctx.Step(`^there exists a disabled account with username "([^"]*)" and password "([^"]*)"$`, s.thereExistsAnAccountWithUsernameAndPasswordWithDisablePrimary)
|
||||
|
@ -121,7 +124,7 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAddressOfAccountHasTheFollowingMessagesInMailbox)
|
||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAddressOfAccountHasMessagesInMailbox)
|
||||
ctx.Step(`^the following fields were changed in draft (\d+) for address "([^"]*)" of account "([^"]*)":$`, s.theFollowingFieldsWereChangedInDraftForAddressOfAccount)
|
||||
ctx.Step(`^draft (\d+) for address "([^"]*)" of account "([^"]*) was moved to trash$`, s.drafAtIndexWasMovedToTrashForAddressOfAccount)
|
||||
ctx.Step(`^draft (\d+) for address "([^"]*)" of account "([^"]*)" was moved to trash$`, s.drafAtIndexWasMovedToTrashForAddressOfAccount)
|
||||
|
||||
// === REPORTER ===
|
||||
ctx.Step(`^test skips reporter checks$`, s.skipReporterChecks)
|
||||
|
@ -132,15 +135,22 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^bridge stops$`, s.bridgeStops)
|
||||
ctx.Step(`^bridge is version "([^"]*)" and the latest available version is "([^"]*)" reachable from "([^"]*)"$`, s.bridgeVersionIsAndTheLatestAvailableVersionIsReachableFrom)
|
||||
ctx.Step(`^the user has disabled automatic updates$`, s.theUserHasDisabledAutomaticUpdates)
|
||||
ctx.Step(`^the user has disabled automatic start`, s.theUserHasDisabledAutomaticStart)
|
||||
ctx.Step(`^the user has enabled alternative routing`, s.theUserHasEnabledAlternativeRouting)
|
||||
ctx.Step(`^the user set IMAP mode to SSL`, s.theUserSetIMAPModeToSSL)
|
||||
ctx.Step(`^the user set SMTP mode to SSL`, s.theUserSetSMTPModeToSSL)
|
||||
ctx.Step(`^the user changes the IMAP port to (\d+)$`, s.theUserChangesTheIMAPPortTo)
|
||||
ctx.Step(`^the user changes the SMTP port to (\d+)$`, s.theUserChangesTheSMTPPortTo)
|
||||
ctx.Step(`^the user sets the address mode of user "([^"]*)" to "([^"]*)"$`, s.theUserSetsTheAddressModeOfUserTo)
|
||||
ctx.Step(`^the user changes the default keychain application`, s.theUserChangesTheDefaultKeychainApplication)
|
||||
ctx.Step(`^the user changes the gluon path$`, s.theUserChangesTheGluonPath)
|
||||
ctx.Step(`^the user deletes the gluon files$`, s.theUserDeletesTheGluonFiles)
|
||||
ctx.Step(`^the user deletes the gluon cache$`, s.theUserDeletesTheGluonCache)
|
||||
ctx.Step(`^the user reports a bug$`, s.theUserReportsABug)
|
||||
ctx.Step(`^the user hides All Mail$`, s.theUserHidesAllMail)
|
||||
ctx.Step(`^the user shows All Mail$`, s.theUserShowsAllMail)
|
||||
ctx.Step(`^the user disables telemetry in bridge settings$`, s.theUserDisablesTelemetryInBridgeSettings)
|
||||
ctx.Step(`^the user enables telemetry in bridge settings$`, s.theUserEnablesTelemetryInBridgeSettings)
|
||||
ctx.Step(`^bridge sends a connection up event$`, s.bridgeSendsAConnectionUpEvent)
|
||||
ctx.Step(`^bridge sends a connection down event$`, s.bridgeSendsAConnectionDownEvent)
|
||||
ctx.Step(`^bridge sends a deauth event for user "([^"]*)"$`, s.bridgeSendsADeauthEventForUser)
|
||||
|
@ -153,6 +163,8 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^bridge sends an update not available event$`, s.bridgeSendsAnUpdateNotAvailableEvent)
|
||||
ctx.Step(`^bridge sends a forced update event$`, s.bridgeSendsAForcedUpdateEvent)
|
||||
ctx.Step(`^bridge reports a message with "([^"]*)"$`, s.bridgeReportsMessage)
|
||||
ctx.Step(`^bridge telemetry feature is enabled$`, s.bridgeTelemetryFeatureEnabled)
|
||||
ctx.Step(`^bridge telemetry feature is disabled$`, s.bridgeTelemetryFeatureDisabled)
|
||||
|
||||
// ==== FRONTEND ====
|
||||
ctx.Step(`^frontend sees that bridge is version "([^"]*)"$`, s.frontendSeesThatBridgeIsVersion)
|
||||
|
@ -166,6 +178,7 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^user "([^"]*)" is listed but not connected$`, s.userIsListedButNotConnected)
|
||||
ctx.Step(`^user "([^"]*)" is not listed$`, s.userIsNotListed)
|
||||
ctx.Step(`^user "([^"]*)" finishes syncing$`, s.userFinishesSyncing)
|
||||
ctx.Step(`^user "([^"]*)" has telemetry set to (\d+)$`, s.userHasTelemetrySetTo)
|
||||
|
||||
// ==== IMAP ====
|
||||
ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)"$`, s.userConnectsIMAPClient)
|
||||
|
@ -206,6 +219,7 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^IMAP client "([^"]*)" appends "([^"]*)" to "([^"]*)"$`, s.imapClientAppendsToMailbox)
|
||||
ctx.Step(`^IMAP clients "([^"]*)" and "([^"]*)" move message with subject "([^"]*)" of "([^"]*)" to "([^"]*)" by ([^"]*) ([^"]*) ([^"]*)`, s.imapClientsMoveMessageWithSubjectUserFromToByOrderedOperations)
|
||||
ctx.Step(`^IMAP client "([^"]*)" sees header "([^"]*)" in message with subject "([^"]*)" in "([^"]*)"$`, s.imapClientSeesHeaderInMessageWithSubject)
|
||||
ctx.Step(`^IMAP client "([^"]*)" does not see header "([^"]*)" in message with subject "([^"]*)" in "([^"]*)"$`, s.imapClientDoesNotSeeHeaderInMessageWithSubject)
|
||||
|
||||
// ==== SMTP ====
|
||||
ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)"$`, s.userConnectsSMTPClient)
|
||||
|
@ -222,6 +236,12 @@ func TestFeatures(testingT *testing.T) {
|
|||
ctx.Step(`^SMTP client "([^"]*)" sends RSET$`, s.smtpClientSendsReset)
|
||||
ctx.Step(`^SMTP client "([^"]*)" sends the following message from "([^"]*)" to "([^"]*)":$`, s.smtpClientSendsTheFollowingMessageFromTo)
|
||||
ctx.Step(`^SMTP client "([^"]*)" logs out$`, s.smtpClientLogsOut)
|
||||
|
||||
// ==== TELEMETRY ====
|
||||
ctx.Step(`^bridge eventually sends the following heartbeat:$`, s.bridgeEventuallySendsTheFollowingHeartbeat)
|
||||
ctx.Step(`^bridge needs to send heartbeat`, s.bridgeNeedsToSendHeartbeat)
|
||||
ctx.Step(`^bridge do not need to send heartbeat`, s.bridgeDoNotNeedToSendHeartbeat)
|
||||
ctx.Step(`^heartbeat is not whitelisted`, s.heartbeatIsNotwhitelisted)
|
||||
},
|
||||
Options: &godog.Options{
|
||||
Format: "pretty",
|
||||
|
|
|
@ -21,7 +21,9 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
|
@ -78,6 +80,10 @@ func (s *scenario) theUserSetsTheAddressModeOfUserTo(user, mode string) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *scenario) theUserChangesTheDefaultKeychainApplication() error {
|
||||
return s.t.bridge.SetKeychainApp("CustomKeychainApp")
|
||||
}
|
||||
|
||||
func (s *scenario) theUserChangesTheGluonPath() error {
|
||||
gluonDir, err := os.MkdirTemp(s.t.dir, "gluon")
|
||||
if err != nil {
|
||||
|
@ -116,7 +122,6 @@ func (s *scenario) theUserHasDisabledAutomaticUpdates() error {
|
|||
|
||||
started = true
|
||||
}
|
||||
|
||||
if err := s.t.bridge.SetAutoUpdate(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -126,10 +131,26 @@ func (s *scenario) theUserHasDisabledAutomaticUpdates() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) theUserHasDisabledAutomaticStart() error {
|
||||
return s.t.bridge.SetAutostart(false)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserHasEnabledAlternativeRouting() error {
|
||||
s.t.expectProxyCtlAllowProxy()
|
||||
return s.t.bridge.SetProxyAllowed(true)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserSetIMAPModeToSSL() error {
|
||||
return s.t.bridge.SetIMAPSSL(true)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserSetSMTPModeToSSL() error {
|
||||
return s.t.bridge.SetSMTPSSL(true)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserReportsABug() error {
|
||||
return s.t.bridge.ReportBug(context.Background(), "osType", "osVersion", "description", "username", "email", "client", false)
|
||||
}
|
||||
|
@ -284,6 +305,22 @@ func (s *scenario) bridgeReportsMessage(message string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) bridgeTelemetryFeatureEnabled() error {
|
||||
return s.checkTelemetry(true)
|
||||
}
|
||||
|
||||
func (s *scenario) bridgeTelemetryFeatureDisabled() error {
|
||||
return s.checkTelemetry(false)
|
||||
}
|
||||
|
||||
func (s *scenario) checkTelemetry(expect bool) error {
|
||||
res := s.t.bridge.IsTelemetryAvailable()
|
||||
if res != expect {
|
||||
return fmt.Errorf("expected telemetry feature %v but got %v ", expect, res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) theUserHidesAllMail() error {
|
||||
return s.t.bridge.SetShowAllMail(false)
|
||||
}
|
||||
|
@ -291,3 +328,45 @@ func (s *scenario) theUserHidesAllMail() error {
|
|||
func (s *scenario) theUserShowsAllMail() error {
|
||||
return s.t.bridge.SetShowAllMail(true)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserDisablesTelemetryInBridgeSettings() error {
|
||||
return s.t.bridge.SetTelemetryDisabled(true)
|
||||
}
|
||||
|
||||
func (s *scenario) theUserEnablesTelemetryInBridgeSettings() error {
|
||||
return s.t.bridge.SetTelemetryDisabled(false)
|
||||
}
|
||||
|
||||
func (s *scenario) networkPortIsBusy(port int) {
|
||||
if listener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port)); err == nil { // we ignore errors. Most likely port is already busy.
|
||||
s.t.dummyListeners = append(s.t.dummyListeners, listener)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scenario) networkPortRangeIsBusy(startPort, endPort int) {
|
||||
if startPort > endPort {
|
||||
startPort, endPort = endPort, startPort
|
||||
}
|
||||
|
||||
for port := startPort; port <= endPort; port++ {
|
||||
s.networkPortIsBusy(port)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scenario) bridgeIMAPPortIs(expectedPort int) error {
|
||||
actualPort := s.t.bridge.GetIMAPPort()
|
||||
if actualPort != expectedPort {
|
||||
return fmt.Errorf("expected IMAP port to be %v but got %v", expectedPort, actualPort)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) bridgeSMTPPortIs(expectedPort int) error {
|
||||
actualPort := s.t.bridge.GetSMTPPort()
|
||||
if actualPort != expectedPort {
|
||||
return fmt.Errorf("expected SMTP port to be %v but got %v", expectedPort, actualPort)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -174,6 +174,9 @@ func (t *testCtx) initBridge() (<-chan events.Event, error) {
|
|||
}
|
||||
|
||||
t.bridge = bridge
|
||||
t.heartbeat.setBridge(bridge)
|
||||
|
||||
bridge.StartHeartbeat(t.heartbeat)
|
||||
|
||||
return t.events.collectFrom(eventCh), nil
|
||||
}
|
||||
|
@ -342,6 +345,9 @@ func (t *testCtx) closeFrontendClient() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
func (t *testCtx) expectProxyCtlAllowProxy() {
|
||||
t.mocks.ProxyCtl.EXPECT().AllowProxy()
|
||||
}
|
||||
|
||||
type mockRestarter struct{}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue