Compare commits

..

34 Commits

Author SHA1 Message Date
8e634995c5 chore: Grunwald Bridge 3.17.0 changelog. 2025-01-21 14:42:52 +01:00
a2c1da9748 chore(BRIDGE-73): bumping GPA; includes changes from BRIDGE-93 and BRIDGE-73 (GPA); 2025-01-21 13:08:29 +01:00
b8cc71fdd8 chore(BRIDGE-287): bump x/net x/crypto dependencies; remove ignore statements from govulncheck 2025-01-20 16:56:25 +00:00
1949e89053 chore(BRIDGE-303): update govulncheck to use latest release 2025-01-20 16:09:32 +01:00
ae5469fc81 chore: remove export loop ref and loop-scope assignments (changed with go 1.22) 2025-01-20 14:54:34 +01:00
105ea4de0d feat(BRIDGE-226): bump version Go 1.23.4 Qt 6.4.3. 2025-01-20 11:12:35 +00:00
b21d126ab0 feat(BRIDGE-288): extended sync message update handler; observability tweaks; gluon bump; 2025-01-16 15:08:52 +01:00
7bc7a5e7b3 chore: downgraded govulncheck package 2025-01-16 14:43:44 +01:00
edbc7d0e3d chore: Flavien Bridge 3.16.0 changelog. 2025-01-09 14:49:17 +01:00
b7b1043b88 chore: Erasmus Bridge 3.15.1 changelog. 2025-01-09 14:48:38 +01:00
0641c63377 fix(BRIDGE-291): use correct field for user plan type 2025-01-08 10:21:10 +00:00
74a990c69a feat(BRIDGE-271): report to sentry when version file check fails 2025-01-08 08:59:29 +00:00
e340e9f845 fix(BRIDGE-143): added missing qml component attribute; cut/paste disabled on read only text areas 2025-01-08 08:29:21 +00:00
082849dc6c chore: year bump 2025-01-02 14:03:49 +01:00
6878b3b5e0 test(BRIDGE-247): Automate Bridge 0% update rollout 2024-12-30 16:01:10 +00:00
16245a372e test(BRIDGE-248): Additional Bridge UI e2e automation tests 2024-12-30 14:17:10 +00:00
28b0dbd051 chore: suppress vulnerability warnings in x/net package 2024-12-23 13:39:44 +01:00
0e6df4ce73 chore: Prepare for issue tracker removal 2024-12-16 10:47:24 +00:00
a4772ee4e0 chore: added CODEOWNER group 2024-12-12 10:46:17 +01:00
ef779a23c1 chore: fix linter issues. 2024-12-05 15:06:20 +01:00
dd2448f35a fix(BRIDGE-266): changed heartbeat measurement group 2024-12-04 08:51:17 +01:00
3f78f4d672 feat(BRIDGE-281): disable keychain test on macOS. 2024-11-29 09:14:29 +01:00
5fbe94c559 chore: Erasmus Bridge 3.15.0 changelog. 2024-11-27 14:59:13 +01:00
80d556343e fix(BRIDGE-256): fix reversed order of headers with multiple values. 2024-11-26 17:08:43 +00:00
612d1054db test(BRIDGE-246): Add Settings Menu Bridge UI e2e automation tests 2024-11-25 13:25:20 +00:00
acf2fc32c4 fix(BRIDGE-264): ignore apple notes as User-Agentt 2024-11-25 08:55:27 +00:00
af01c63298 fix(BRIDGE-261): delete gluon data during user deletion; integration tests; FF kill switch; Sentry report if error; 2024-11-22 14:32:28 +00:00
2e98d64f94 feat(BRIDGE-266): heartbeat telemetry update; extra integration tests; 2024-11-22 14:09:48 +00:00
cdcdd45bcf feat(BRIDGE-268): add kill switch feature flag for the IMAP AUTHENTICATE command. 2024-11-22 12:32:33 +01:00
b3e2a91f56 feat(BRIDGE-205): add support for the IMAP AUTHENTICATE command. 2024-11-12 15:28:22 +01:00
7d9753e2da fix(BRIDGE-107): improved human verification UX 2024-11-11 09:03:02 +00:00
f1aef383b7 fix(BRIDGE-258): fixed issue with draft updates and sending during synchronization 2024-11-07 17:47:36 +01:00
6647231278 chore: (BRIDGE-253) removing unused telemetry (activation and troubleshooting) 2024-10-30 15:13:41 +00:00
531368da86 feat(BRIDGE-252): restored the -h shortcut shortcut for the CLI --help switch. 2024-10-30 12:36:21 +01:00
660 changed files with 3252 additions and 3432 deletions

View File

@ -1,41 +0,0 @@
---
name: General issue template
about: Template for detailed report of issues
title: ''
labels: ''
assignees: ''
---
Issue tracker is ONLY used for reporting bugs with technical details. "It doesn't work" or new features should be discussed with our customer support. Please use bug report function in Bridge or contact bridge@protonmail.ch.
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behavior
<!--- Tell us what should happen -->
## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
## Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Version Information
<!--- Which version of the app(s) were you using when you experienced this issue? -->
## Context (Environment)
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Detailed Description
<!--- Provide a detailed description of the change or addition you are proposing -->
## Possible Implementation
<!--- Not obligatory, but suggest an idea for implementing addition or change -->

1
.gitlab/CODEOWNERS Normal file
View File

@ -0,0 +1 @@
* @go/bridge-ppl/devs

View File

@ -87,7 +87,6 @@ linters:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
- copyloopvar # detects places where loop variables are copied.
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
- godot # Check if comments end in a period [fast: true, auto-fix: true]

View File

@ -3,7 +3,7 @@
## Prerequisites
* 64-bit OS:
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
* Go 1.21.9
* Go 1.23.4
* 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)

View File

@ -1,10 +1,78 @@
# Contribution Policy
# Contributing guidelines
The following document describes how to contribute to the project. In this context, contribution does not only mean code contribution but also reporting issues, requesting new features, or just asking for help.
## Reporting issues
In case you experience issues while using the application, our request is to contact Proton customer support directly.
The benefits of using Proton customer support are
- Available 24/7/365.
- Provides priority support based on subscription type.
- Will escalate the issue to the developers every time it becomes too technical or they do not know the answer to a question.
- Easier to detect systematic issues by connecting similar reports.
- Possible to quickly derive frequency of an issue.
- Can assist you to transfer sensitive information safely to us.
To speed up the communication with customer support, consider the following:
- Whenever is possible, use the in-app bug report feature. It provides an application specific guide compared to using the generic report form on web.
- Whenever is possible, proactively attach logs to your report. Reporting an issue from the application can help you in that.
- Check whether your system is officially supported by Proton, including the source of the installer. We cannot provide help when the application is packaged by a third party or when the application is used on systems that we do not prepare to support.
- If your report is a feature request, see the Feature request section. In case it is an issue related to application security, see the Security vulnerabilities section.
In the past, we used GitHub issue tracker for more technical issues in parallel to Proton customer support, but we run into limitations with this approach:
- Monitoring GitHub issue tracker took development time as it was managed by the development team.
- It made issue frequency tracking challenging because we did not have a single point of entry for issues.
- Users were confused what technical issue means, and used the GitHub issue tracker for feature requests, or non-technical discussions.
- Users sometimes shared sensitive data through the GitHub issue tracker.
For the above reasons, we do not use GitHub issue tracker anymore but ask you to contact our customer support in case you run into a problem.
### Security vulnerabilities
Proton runs a bug bounty program for security vulnerabilities. They differ from normal bug reports in the following ways:
- These reports go directly to our security team.
- They expect deeper explanation of the issue.
- Depending on the finding, they may be financially rewarded.
More information about the program can be found [here](https://proton.me/security/bug-bounty).
## Feature requests
What someone considers as a bug is sometimes a feature, and sometimes, a missing feature is considered as a bug. Instead of reporting feature requests as bugs, we setup a UserVoice page to allow our users to share their preferences. UserVoice also makes it possible to vote on other feature requests, making the community preference public.
Our product team frequently monitors UserVoice, and the features listed there are taken into account in our planning.
Examples for UserVoice requests:
- Extending the officially supported environments (e.g., operating systems, clients, or computer architectures).
- Requesting new features.
- Integration with non-Proton services.
UserVoice is available [here](https://protonmail.uservoice.com/).
## Asking for help
The best ways to get answer for generic questions or to get help with setting up the system is to interact with our active community on [Reddit](https://reddit.com/r/ProtonMail/) or to contact customer support.
## Code contribution
We are grateful if you can contribute directly with code. In that case there is nothing else to do than to open a pull request.
The following is worthwhile noting
- The project is primarily developed on an internal repository, and the one on GitHub is only a mirror of it. For that reason, the merge request will not be merged on GitHub but added to the project internally. We are keeping the original author in the change set to respect the contribution.
- The application is used on numerous platforms and by many third party clients. To have higher chance your change to be accepted, consider all supported dependencies.
- Give detailed description of the issue, preferably with test steps to reproduce the original issue, and to verify the fix. It is even better if you also extend the automated tests.
### Contribution policy
By making a contribution to this project:
1. I assign any and all copyright related to the contribution to Proton AG;
2. I certify that the contribution was created in whole by me;
3. I understand and agree that this project and the contribution are public
and that a record of the contribution (including all personal information I
submit with it) is maintained indefinitely and may be redistributed with
this project or the open source license(s) involved.
1. You assign any and all copyright related to the contribution to Proton AG;
2. You certify that the contribution was created in whole by you;
3. You understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information you submit with it) is maintained indefinitely and may be redistributed with this project or the open source license(s) involved.

View File

@ -3,6 +3,52 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Grunwald Bridge 3.17.0
### Added
* BRIDGE-271: Report version file check failure to Sentry.
* BRIDGE-247: Test: Automate Bridge 0% update rollout.
* BRIDGE-248: Test: Additional Bridge UI e2e automation tests.
### Changed
* BRIDGE-73: Update goopenpgp.
* BRIDGE-287: Update x/net and x/crypto dependencies.
* BRIDGE-303: Update govulncheck to latest release.
* BRIDGE-226: Bump Go version to 1.23.4.
* BRIDGE-288: Extension to synchronization update handler, observability tweaks and gluon update.
### Fixed
* BRIDGE-291: Use correct field for user plan type.
* BRIDGE-143: Add missing QML component attribute, cut/paste disabled on read-only text areas.
## Flavien Bridge 3.16.0
### Added
* BRIDGE-205: Add support for the IMAP AUTHENTICATE command.
* BRIDGE-268: Add kill switch feature flag for the IMAP AUTHENTICATE command.
* BRIDGE-261: Delete gluon data during user deletion.
* BRIDGE-246: Test: Add Settings Menu Bridge UI e2e automation tests.
### Changed
* BRIDGE-107: Improved human verification UX.
* BRIDGE-281: Disable keychain test on macOS.
* BRIDGE-266: Heartbeat telemetry update.
* BRIDGE-253: Removed unused telemetry (activation and troubleshooting).
* BRIDGE-252: Restored the -h shortcut for the CLI --help switch.
* BRIDGE-264: Ignore apple notes as UserAgent.
### Fixed
* BRIDGE-256: Fix reversed order of headers with multiple values.
* BRIDGE-258: Fixed issue with draft updates and sending during synchronization.
## Erasmus Bridge 3.15.1
### Changed
* BRIDGE-281: Disable keychain test on macOS.
## Erasmus Bridge 3.15.0
### Added

View File

@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
.PHONY: build build-gui build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.15.0+git
BRIDGE_APP_VERSION?=3.17.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG

View File

@ -1,5 +1,5 @@
# Proton Mail Bridge
Copyright (c) 2024 Proton AG
Copyright (c) 2025 Proton AG
This repository holds the Proton Mail Bridge application.
For a detailed build information see [BUILDS](./BUILDS.md).

View File

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

View File

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

View File

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

View File

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

30
go.mod
View File

@ -1,16 +1,16 @@
module github.com/ProtonMail/proton-bridge/v3
go 1.21
go 1.23
toolchain go1.21.9
toolchain go1.23.4
require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
github.com/ProtonMail/go-proton-api v0.4.1-0.20250121114701-67bd01ad0bc3
github.com/ProtonMail/gopenpgp/v2 v2.8.2-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
@ -30,7 +30,7 @@ require (
github.com/go-resty/resty/v2 v2.7.0
github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
@ -46,10 +46,10 @@ require (
github.com/vmihailenco/msgpack/v5 v5.3.5
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.24.0
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sys v0.19.0
golang.org/x/text v0.14.0
golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0
google.golang.org/api v0.114.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.33.0
@ -60,7 +60,7 @@ require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
github.com/ProtonMail/go-crypto v1.1.4-proton // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/go-srp v0.0.7 // indirect
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
@ -68,7 +68,7 @@ require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/chzyer/test v1.0.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cronokirby/saferith v0.33.0 // indirect
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
@ -121,10 +121,10 @@ require (
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

70
go.sum
View File

@ -34,37 +34,25 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602 h1:EoMjWlC32tg46L/07hWoiZfLkqJyxVMcsq4Cyn+Ofqc=
github.com/ProtonMail/gluon v0.17.1-0.20240923151549-d23b4bec3602/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241002092751-3bbeea9053af h1:iMxTQUg2cB47cXqpMev3cZmQoGBOef3cSUjBbdEl33M=
github.com/ProtonMail/gluon v0.17.1-0.20241002092751-3bbeea9053af/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241002111651-173859b80060 h1:dcu3tT84GjoXb++n7crv8UJeG8eRwogjTYdkoJ+MjQI=
github.com/ProtonMail/gluon v0.17.1-0.20241002111651-173859b80060/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241002142736-ef4153d156d8 h1:YxPHSJUA87i1hc6s1YrW89++V7HpcR7LSFQ6XM0TsAE=
github.com/ProtonMail/gluon v0.17.1-0.20241002142736-ef4153d156d8/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241008123701-ddf4a459d0b4 h1:xE+V17O9HIttMpVymNCORQILk9OKpSekrrPbX7YGnF8=
github.com/ProtonMail/gluon v0.17.1-0.20241008123701-ddf4a459d0b4/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241014082854-9d93627be032 h1:5bwI+mwF26c460xlq2Dw3/cVF1cU4Xo4kTKX1/pBXko=
github.com/ProtonMail/gluon v0.17.1-0.20241014082854-9d93627be032/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e h1:+UfdKOkF9JEiH9VXWBo+/nlXNVSJcxtuf4+SJTrk9fw=
github.com/ProtonMail/gluon v0.17.1-0.20241018144126-31e040c2417e/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2 h1:lDgMidI/9j2eedavcy7YICv8+F73ooVTUoUGBE4dO0s=
github.com/ProtonMail/gluon v0.17.1-0.20250116113909-2ebd96ec0bc2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
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-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.1.4-proton h1:KIo9uNlk3vzlwI7o5VjhiEjI4Ld1TDixOMnoNZyfpFE=
github.com/ProtonMail/go-crypto v1.1.4-proton/go.mod h1:zNoyBJW3p/yVWiHNZgfTF9VsjwqYof5YY0M9kt2QaX0=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
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.20240918100656-b4860af56d47 h1:a+3dOyIxJEslN5HxyICM8flY9lnCyJupXNcv6fUaivA=
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250121114701-67bd01ad0bc3 h1:YYnLBVcg7WrEbYVmF1PBr4AEQlob9rCphsMHAmF4CAo=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250121114701-67bd01ad0bc3/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton h1:8tqHYM6IGsdEc6Vxf1TWiwpHNj8yIEQNACPhxsDagrk=
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton/go.mod h1:omVkSsfPAhmptzPF/piMXb16wKIWUvVhZbVW7sJKh0A=
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton h1:MMVgE6nk5Ulh9Ud5L1Xc5iaPKE85FbfKQV17ZMucrR0=
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton/go.mod h1:+PjybET6fgcLzldFy1hpy7s8VibZ0T1hLFbxnnMk0lo=
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=
@ -89,7 +77,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM=
github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@ -107,9 +94,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -247,8 +233,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -510,11 +496,10 @@ 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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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=
@ -539,8 +524,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -563,15 +549,14 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
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=
@ -586,8 +571,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -621,18 +606,15 @@ 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
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/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
@ -646,13 +628,12 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@ -681,8 +662,9 @@ golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWc
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.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -97,18 +97,21 @@ const (
appShortName = "bridge"
)
// the two flags below have been deprecated by BRIDGE-281. We however keep them so that bridge does not error if they are passed on startup.
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
Name: flagEnableKeychainTest,
Usage: "Enable the keychain test for the current and future executions of the application",
Usage: "This flag is deprecated and does nothing",
Value: false,
DisableDefaultText: true,
} //nolint:gochecknoglobals
Hidden: true,
}
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
Name: flagDisableKeychainTest,
Usage: "Disable the keychain test for the current and future executions of the application",
Usage: "This flag is deprecated and does nothing",
Value: false,
DisableDefaultText: true,
Hidden: true,
}
func New() *cli.App {
@ -205,12 +208,14 @@ func New() *cli.App {
// We override the default help value because we want "Show" to be capitalized
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h"},
Usage: "Show help",
DisableDefaultText: true,
}
if onMacOS() {
// The two flags below were introduced for BRIDGE-116, and are available only on macOS.
// They have been later removed fro BRIDGE-281.
app.Flags = append(app.Flags, cliFlagEnableKeychainTest, cliFlagDisableKeychainTest)
}
@ -282,8 +287,7 @@ func run(c *cli.Context) error {
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
// Look for available keychains
skipKeychainTest := checkSkipKeychainTest(c, settings)
return WithKeychainList(crashHandler, skipKeychainTest, func(keychains *keychain.List) error {
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
// Unlock the encrypted vault.
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
if !v.Migrated() {
@ -547,11 +551,11 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
}
// WithKeychainList init the list of usable keychains.
func WithKeychainList(panicHandler async.PanicHandler, skipKeychainTest bool, fn func(*keychain.List) error) error {
func WithKeychainList(panicHandler async.PanicHandler, fn func(*keychain.List) error) error {
logrus.Debug("Creating keychain list")
defer logrus.Debug("Keychain list stop")
defer async.HandlePanic(panicHandler)
return fn(keychain.NewList(skipKeychainTest))
return fn(keychain.NewList())
}
func setDeviceCookies(jar *cookies.Jar) error {
@ -572,38 +576,6 @@ func setDeviceCookies(jar *cookies.Jar) error {
return nil
}
func checkSkipKeychainTest(c *cli.Context, settingsDir string) bool {
if !onMacOS() {
return false
}
enable := c.Bool(flagEnableKeychainTest)
disable := c.Bool(flagDisableKeychainTest)
skip, err := vault.GetShouldSkipKeychainTest(settingsDir)
if err != nil {
logrus.WithError(err).Error("Could not load keychain settings.")
}
if (!enable) && (!disable) {
return skip
}
// if both switches are passed, 'enable' has priority
if disable {
skip = true
}
if enable {
skip = false
}
if err := vault.SetShouldSkipKeychainTest(settingsDir, skip); err != nil {
logrus.WithError(err).Error("Could not save keychain settings.")
}
return skip
}
func onMacOS() bool {
return runtime.GOOS == "darwin"
}

View File

@ -1,64 +0,0 @@
// Copyright (c) 2024 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 app
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
func TestCheckSkipKeychainTest(t *testing.T) {
var expectedResult bool
dir := t.TempDir()
app := cli.App{
Flags: []cli.Flag{
cliFlagEnableKeychainTest,
cliFlagDisableKeychainTest,
},
Action: func(c *cli.Context) error {
require.Equal(t, expectedResult, checkSkipKeychainTest(c, dir))
return nil
},
}
noArgs := []string{"appName"}
enableArgs := []string{"appName", "-" + flagEnableKeychainTest}
disableArgs := []string{"appName", "-" + flagDisableKeychainTest}
bothArgs := []string{"appName", "-" + flagDisableKeychainTest, "-" + flagEnableKeychainTest}
onMac := onMacOS()
expectedResult = false
require.NoError(t, app.Run(noArgs))
expectedResult = onMac
require.NoError(t, app.Run(disableArgs))
require.NoError(t, app.Run(noArgs))
expectedResult = false
require.NoError(t, app.Run(enableArgs))
require.NoError(t, app.Run(noArgs))
expectedResult = onMac
require.NoError(t, app.Run(disableArgs))
expectedResult = false
require.NoError(t, app.Run(bothArgs))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -50,6 +50,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
@ -439,6 +440,15 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
if err != nil {
bridge.publish(events.UpdateCheckFailed{Error: err})
if errors.Is(err, updater.ErrVersionFileDownloadOrVerify) {
logPkg.WithError(err).Error("Cannot download or verify the version file")
if reporterErr := bridge.reporter.ReportMessageWithContext(
"Cannot download or verify the version file",
reporter.Context{"error": err},
); reporterErr != nil {
logPkg.WithError(reporterErr).Error("Failed to report version file check error")
}
}
} else {
bridge.handleUpdate(version)
}
@ -636,14 +646,6 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
}, nil
}
func min(a, b time.Duration) time.Duration { //nolint:predeclared
if a < b {
return a
}
return b
}
func (bridge *Bridge) HasAPIConnection() bool {
return bridge.api.GetStatus() == proton.StatusUp
}
@ -723,3 +725,18 @@ func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.Dis
func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) {
bridge.observabilityService.ModifyHeartbeatInterval(duration)
}
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
logPkg.WithFields(logrus.Fields{
"err": err,
"sentryMessage": message,
"messageCtx": messageCtx,
}).Info("Error occurred when sending Report to Sentry")
}
}
// GetUsers is only used for testing purposes.
func (bridge *Bridge) GetUsers() map[string]*user.User {
return bridge.users
}

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -25,7 +25,6 @@ import (
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
)
@ -80,12 +79,6 @@ func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error
return err
}
safe.RLock(func() {
for _, user := range bridge.users {
user.ReportBugSent()
}
}, bridge.usersLock)
// if we have a token we can append more attachment to the bugReport
for i, att := range attachments {
if i == 0 && report.IncludeLogs {

View File

@ -1,46 +0,0 @@
// Copyright (c) 2024 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 (
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
)
func (bridge *Bridge) ReportBugClicked() {
safe.RLock(func() {
for _, user := range bridge.users {
user.ReportBugClicked()
}
}, bridge.usersLock)
}
func (bridge *Bridge) AutoconfigUsed(client string) {
safe.RLock(func() {
for _, user := range bridge.users {
user.AutoconfigUsed(client)
}
}, bridge.usersLock)
}
func (bridge *Bridge) ExternalLinkClicked(article string) {
safe.RLock(func() {
for _, user := range bridge.users {
user.ExternalLinkClicked(article)
}
}, bridge.usersLock)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -73,15 +73,15 @@ func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager
for _, user := range bridge.users {
if user.GetAddressMode() == vault.SplitMode {
splitMode = true
break
}
h.SetUserPlan(user.GetUserPlanName())
}
var nbAccount = len(bridge.users)
h.SetNbAccount(nbAccount)
var numberConnectedAccounts = len(bridge.users)
h.SetNumberConnectedAccounts(numberConnectedAccounts)
h.SetSplitMode(splitMode)
// Do not try to send if there is no user yet.
if nbAccount > 0 {
if numberConnectedAccounts > 0 {
defer h.start()
}
}, bridge.usersLock)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -17,7 +17,9 @@
package bridge
import "github.com/sirupsen/logrus"
import (
"github.com/sirupsen/logrus"
)
func (bridge *Bridge) GetCurrentUserAgent() string {
return bridge.identifier.GetUserAgent()
@ -30,6 +32,8 @@ func (bridge *Bridge) SetCurrentPlatform(platform string) {
func (bridge *Bridge) setUserAgent(name, version string) {
currentUserAgent := bridge.identifier.GetClientString()
bridge.heartbeat.SetContactedByAppleNotes(name)
bridge.identifier.SetClient(name, version)
newUserAgent := bridge.identifier.GetClientString()
@ -54,6 +58,7 @@ func (b *bridgeUserAgentUpdater) HasClient() bool {
}
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
b.heartbeat.SetContactedByAppleNotes(name)
b.identifier.SetClient(name, version)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -26,6 +26,7 @@ import (
imapEvents "github.com/ProtonMail/gluon/events"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
"github.com/sirupsen/logrus"
)
@ -93,6 +94,10 @@ func (b *bridgeIMAPSettings) LogServer() bool {
return b.b.logIMAPServer
}
func (b *bridgeIMAPSettings) DisableIMAPAuthenticate() bool {
return b.b.unleashService.GetFlagValue(unleash.IMAPAuthenticateCommandDisabled)
}
func (b *bridgeIMAPSettings) Port() int {
return b.b.vault.GetIMAPPort()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -318,11 +318,10 @@ func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleL
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
// which we need at next startup to decrypt the vault.
func (bridge *Bridge) FactoryReset(ctx context.Context) {
useTelemetry := !bridge.GetTelemetryDisabled()
// Delete all the users.
safe.Lock(func() {
for _, user := range bridge.users {
bridge.logoutUser(ctx, user, true, true, useTelemetry)
bridge.logoutUser(ctx, user, true, true)
}
}, bridge.usersLock)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -28,7 +28,6 @@ type Locator interface {
ProvideLogsPath() (string, error)
ProvideGluonCachePath() (string, error)
ProvideGluonDataPath() (string, error)
ProvideStatsPath() (string, error)
GetLicenseFilePath() string
GetDependencyLicensesLink() string
Clear(...string) error

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -33,6 +33,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
"github.com/ProtonMail/proton-bridge/v3/internal/try"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/go-resty/resty/v2"
@ -255,7 +256,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
return ErrNoSuchUser
}
bridge.logoutUser(ctx, user, true, false, false)
bridge.logoutUser(ctx, user, true, false)
bridge.publish(events.UserLoggedOut{
UserID: userID,
@ -280,7 +281,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
}
if user, ok := bridge.users[userID]; ok {
bridge.logoutUser(ctx, user, true, true, !bridge.GetTelemetryDisabled())
bridge.logoutUser(ctx, user, true, true)
}
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
@ -358,7 +359,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
return user.BadEventFeedbackResync(ctx)
}
bridge.logoutUser(ctx, user, true, false, false)
bridge.logoutUser(ctx, user, true, false)
bridge.publish(events.UserLoggedOut{
UserID: userID,
@ -527,11 +528,6 @@ func (bridge *Bridge) addUserWithVault(
vault *vault.User,
isNew bool,
) error {
statsPath, err := bridge.locator.ProvideStatsPath()
if err != nil {
return fmt.Errorf("failed to get Statistics directory: %w", err)
}
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
if err != nil {
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
@ -546,7 +542,6 @@ func (bridge *Bridge) addUserWithVault(
bridge.panicHandler,
bridge.vault.GetShowAllMail(),
bridge.vault.GetMaxSyncMemory(),
statsPath,
bridge,
bridge.serverManager,
bridge.serverManager,
@ -589,9 +584,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.heartbeat.SetNumberConnectedAccounts(len(bridge.users))
}, bridge.usersLock)
// Set user plan if its of a higher rank.
bridge.heartbeat.SetUserPlan(user.GetUserPlanName())
// As we need at least one user to send heartbeat, try to send it.
bridge.heartbeat.start()
@ -610,26 +608,21 @@ func (bridge *Bridge) newVaultUser(
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
}
// logout logs out the given user, optionally logging them out from the API too.
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData, withTelemetry bool) {
// logoutUser logs out the given user, optionally logging them out from the API and deleting user related gluon data.
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData bool) {
defer delete(bridge.users, user.ID())
// if this is actually a remove account
if withData && withAPI {
user.SendConfigStatusAbort(ctx, withTelemetry)
}
logUser.WithFields(logrus.Fields{
"userID": user.ID(),
"withAPI": withAPI,
"withData": withData,
}).Debug("Logging out user")
if err := user.Logout(ctx, withAPI); err != nil {
if err := user.Logout(ctx, withAPI, withData, bridge.unleashService.GetFlagValue(unleash.UserRemovalGluonDataCleanupDisabled)); err != nil {
logUser.WithError(err).Error("Failed to logout user")
}
bridge.heartbeat.SetNbAccount(len(bridge.users))
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users) - 1)
user.Close()
}

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) 2024 Proton AG
// Copyright (c) 2025 Proton AG
//
// This file is part of Proton Mail Bridge.
//
@ -43,8 +43,7 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
safe.Lock(func() {
bridge.logoutUser(ctx, user, false, false, false)
user.ReportConfigStatusFailure("User deauth.")
bridge.logoutUser(ctx, user, false, false)
}, bridge.usersLock)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,228 +0,0 @@
// Copyright (c) 2024 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 configstatus
import (
"encoding/json"
"fmt"
"os"
"strconv"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/sirupsen/logrus"
)
const version = "1.0.0"
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
status := ConfigurationStatus{
FilePath: filepath,
DataLock: safe.NewRWMutex(),
Data: &ConfigurationStatusData{},
}
if _, err := os.Stat(filepath); err == nil {
if err := status.Load(); err == nil {
return &status, nil
}
logrus.WithError(err).Warn("Cannot load configuration status file. Reset it.")
}
status.Data.init()
if err := status.Save(); err != nil {
return &status, err
}
return &status, nil
}
func (status *ConfigurationStatus) Load() error {
bytes, err := os.ReadFile(status.FilePath)
if err != nil {
return err
}
var metadata MetadataOnly
if err := json.Unmarshal(bytes, &metadata); err != nil {
return err
}
if metadata.Metadata.Version != version {
return fmt.Errorf("unsupported configstatus file version %s", metadata.Metadata.Version)
}
return json.Unmarshal(bytes, status.Data)
}
func (status *ConfigurationStatus) Save() error {
temp := status.FilePath + "_temp"
f, err := os.Create(temp) //nolint:gosec
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
err = enc.Encode(status.Data)
if err := f.Close(); err != nil {
logrus.WithError(err).Error("Error while closing configstatus file.")
}
if err != nil {
return err
}
return os.Rename(temp, status.FilePath)
}
func (status *ConfigurationStatus) IsPending() bool {
status.DataLock.RLock()
defer status.DataLock.RUnlock()
return !status.Data.DataV1.PendingSince.IsZero()
}
func (status *ConfigurationStatus) isPendingSinceMin() int {
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 { //nolint:predeclared
return min
}
return 0
}
func (status *ConfigurationStatus) IsFromFailure() bool {
status.DataLock.RLock()
defer status.DataLock.RUnlock()
return status.Data.DataV1.FailureDetails != ""
}
func (status *ConfigurationStatus) ApplySuccess() error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
status.Data.init()
status.Data.DataV1.PendingSince = time.Time{}
return status.Save()
}
func (status *ConfigurationStatus) ApplyFailure(err string) error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
status.Data.init()
status.Data.DataV1.FailureDetails = err
return status.Save()
}
func (status *ConfigurationStatus) ApplyProgress() error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
status.Data.DataV1.LastProgress = time.Now()
return status.Save()
}
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
if !status.Data.hasLinkClicked(link) {
status.Data.setClickedLink(link)
return status.Save()
}
return nil
}
func (status *ConfigurationStatus) ReportClicked() error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
if !status.Data.DataV1.ReportClick {
status.Data.DataV1.ReportClick = true
return status.Save()
}
return nil
}
func (status *ConfigurationStatus) ReportSent() error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
if !status.Data.DataV1.ReportSent {
status.Data.DataV1.ReportSent = true
return status.Save()
}
return nil
}
func (status *ConfigurationStatus) AutoconfigUsed(client string) error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
if client != status.Data.DataV1.Autoconf {
status.Data.DataV1.Autoconf = client
return status.Save()
}
return nil
}
func (status *ConfigurationStatus) Remove() error {
status.DataLock.Lock()
defer status.DataLock.Unlock()
return os.Remove(status.FilePath)
}
func (data *ConfigurationStatusData) init() {
data.Metadata = Metadata{
Version: version,
}
data.DataV1.PendingSince = time.Now()
data.DataV1.LastProgress = time.Time{}
data.DataV1.Autoconf = ""
data.DataV1.ClickedLink = 0
data.DataV1.ReportSent = false
data.DataV1.ReportClick = false
data.DataV1.FailureDetails = ""
}
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
data.DataV1.ClickedLink |= 1 << pos
}
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
val := data.DataV1.ClickedLink & (1 << pos)
return val > 0
}
func (data *ConfigurationStatusData) clickedLinkToString() string {
var str = ""
var first = true
for i := 0; i < 64; i++ {
if data.hasLinkClicked(uint64(i)) { //nolint:gosec // disable G115
if !first {
str += ","
} else {
first = false
str += "["
}
str += strconv.Itoa(i)
}
}
if str != "" {
str += "]"
}
return str
}

View File

@ -1,252 +0,0 @@
// Copyright (c) 2024 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 configstatus_test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/stretchr/testify/require"
)
func TestConfigStatus_init_virgin(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
require.Equal(t, false, config.Data.DataV1.ReportSent)
require.Equal(t, false, config.Data.DataV1.ReportClick)
require.Equal(t, "", config.Data.DataV1.FailureDetails)
}
func TestConfigStatus_init_existing(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
}
func TestConfigStatus_init_bad_version(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "2.0.0"},
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
require.Equal(t, "", config.Data.DataV1.Autoconf)
}
func TestConfigStatus_IsPending(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, true, config.IsPending())
config.Data.DataV1.PendingSince = time.Time{}
require.Equal(t, false, config.IsPending())
}
func TestConfigStatus_IsFromFailure(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, false, config.IsFromFailure())
config.Data.DataV1.FailureDetails = "test"
require.Equal(t, true, config.IsFromFailure())
}
func TestConfigStatus_ApplySuccess(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, true, config.IsPending())
require.NoError(t, config.ApplySuccess())
require.Equal(t, false, config.IsPending())
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
require.Equal(t, false, config2.Data.DataV1.ReportSent)
require.Equal(t, false, config2.Data.DataV1.ReportClick)
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
}
func TestConfigStatus_ApplyFailure(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.NoError(t, config.ApplySuccess())
require.NoError(t, config.ApplyFailure("Big Failure"))
require.Equal(t, true, config.IsFromFailure())
require.Equal(t, true, config.IsPending())
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
require.Equal(t, false, config2.Data.DataV1.ReportSent)
require.Equal(t, false, config2.Data.DataV1.ReportClick)
require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
}
func TestConfigStatus_ApplyProgress(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, true, config.IsPending())
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
require.NoError(t, config.ApplyProgress())
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
require.Equal(t, false, config2.Data.DataV1.ReportSent)
require.Equal(t, false, config2.Data.DataV1.ReportClick)
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
}
func TestConfigStatus_RecordLinkClicked(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
require.NoError(t, config.RecordLinkClicked(0))
require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
require.NoError(t, config.RecordLinkClicked(1))
require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
require.Equal(t, false, config2.Data.DataV1.ReportSent)
require.Equal(t, false, config2.Data.DataV1.ReportClick)
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
}
func TestConfigStatus_ReportClicked(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, false, config.Data.DataV1.ReportClick)
require.NoError(t, config.ReportClicked())
require.Equal(t, true, config.Data.DataV1.ReportClick)
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
require.Equal(t, false, config2.Data.DataV1.ReportSent)
require.Equal(t, true, config2.Data.DataV1.ReportClick)
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
}
func TestConfigStatus_ReportSent(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, false, config.Data.DataV1.ReportSent)
require.NoError(t, config.ReportSent())
require.Equal(t, true, config.Data.DataV1.ReportSent)
config2, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
require.Equal(t, "", config2.Data.DataV1.Autoconf)
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
require.Equal(t, true, config2.Data.DataV1.ReportSent)
require.Equal(t, false, config2.Data.DataV1.ReportClick)
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
}
func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
f, err := os.Create(file)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
return json.NewEncoder(f).Encode(data)
}

View File

@ -1,59 +0,0 @@
// Copyright (c) 2024 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 configstatus
import (
"strconv"
)
type ConfigAbortValues struct {
Duration int `json:"duration"`
}
type ConfigAbortDimensions struct {
ReportClick string `json:"report_click"`
ReportSent string `json:"report_sent"`
ClickedLink string `json:"clicked_link"`
}
type ConfigAbortData struct {
MeasurementGroup string
Event string
Values ConfigSuccessValues
Dimensions ConfigSuccessDimensions
}
type ConfigAbortBuilder struct{}
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
config.DataLock.RLock()
defer config.DataLock.RUnlock()
return ConfigAbortData{
MeasurementGroup: "bridge.any.configuration",
Event: "bridge_config_abort",
Values: ConfigSuccessValues{
Duration: config.isPendingSinceMin(),
},
Dimensions: ConfigSuccessDimensions{
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
ClickedLink: config.Data.clickedLinkToString(),
},
}
}

View File

@ -1,75 +0,0 @@
// Copyright (c) 2024 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 configstatus_test
import (
"path/filepath"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/stretchr/testify/require"
)
func TestConfigurationAbort_default(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigAbortBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_abort", req.Event)
require.Equal(t, 0, req.Values.Duration)
require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "", req.Dimensions.ClickedLink)
}
func TestConfigurationAbort_fed(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{
PendingSince: time.Now().Add(-10 * time.Minute),
LastProgress: time.Time{},
Autoconf: "Mr TBird",
ClickedLink: 42,
ReportSent: false,
ReportClick: true,
FailureDetails: "Not an error",
},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigAbortBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_abort", req.Event)
require.Equal(t, 10, req.Values.Duration)
require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
}

View File

@ -1,60 +0,0 @@
// Copyright (c) 2024 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 configstatus
import "time"
type ConfigProgressValues struct {
NbDay int `json:"nb_day"`
NbDaySinceLast int `json:"nb_day_since_last"`
}
type ConfigProgressData struct {
MeasurementGroup string
Event string
Values ConfigProgressValues
Dimensions struct{}
}
type ConfigProgressBuilder struct{}
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
config.DataLock.RLock()
defer config.DataLock.RUnlock()
return ConfigProgressData{
MeasurementGroup: "bridge.any.configuration",
Event: "bridge_config_progress",
Values: ConfigProgressValues{
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
},
}
}
func numberOfDay(now, prev time.Time) int {
if now.IsZero() || prev.IsZero() {
return 1
}
if now.Year() > prev.Year() {
return (365 * (now.Year() - prev.Year())) + now.YearDay() - prev.YearDay()
} else if now.YearDay() > prev.YearDay() {
return now.YearDay() - prev.YearDay()
}
return 0
}

View File

@ -1,100 +0,0 @@
// Copyright (c) 2024 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 configstatus_test
import (
"path/filepath"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/stretchr/testify/require"
)
func TestConfigurationProgress_default(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigProgressBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_progress", req.Event)
require.Equal(t, 0, req.Values.NbDay)
require.Equal(t, 1, req.Values.NbDaySinceLast)
}
func TestConfigurationProgress_fed(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{
PendingSince: time.Now().AddDate(0, 0, -5),
LastProgress: time.Now().AddDate(0, 0, -2),
Autoconf: "Mr TBird",
ClickedLink: 42,
ReportSent: false,
ReportClick: true,
FailureDetails: "Not an error",
},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigProgressBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_progress", req.Event)
require.Equal(t, 5, req.Values.NbDay)
require.Equal(t, 2, req.Values.NbDaySinceLast)
}
func TestConfigurationProgress_fed_year_change(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{
PendingSince: time.Now().AddDate(-1, 0, -5),
LastProgress: time.Now().AddDate(0, 0, -2),
Autoconf: "Mr TBird",
ClickedLink: 42,
ReportSent: false,
ReportClick: true,
FailureDetails: "Not an error",
},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigProgressBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_progress", req.Event)
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
require.Equal(t, 2, req.Values.NbDaySinceLast)
}

View File

@ -1,63 +0,0 @@
// Copyright (c) 2024 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 configstatus
import (
"strconv"
)
type ConfigRecoveryValues struct {
Duration int `json:"duration"`
}
type ConfigRecoveryDimensions struct {
Autoconf string `json:"autoconf"`
ReportClick string `json:"report_click"`
ReportSent string `json:"report_sent"`
ClickedLink string `json:"clicked_link"`
FailureDetails string `json:"failure_details"`
}
type ConfigRecoveryData struct {
MeasurementGroup string
Event string
Values ConfigRecoveryValues
Dimensions ConfigRecoveryDimensions
}
type ConfigRecoveryBuilder struct{}
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
config.DataLock.RLock()
defer config.DataLock.RUnlock()
return ConfigRecoveryData{
MeasurementGroup: "bridge.any.configuration",
Event: "bridge_config_recovery",
Values: ConfigRecoveryValues{
Duration: config.isPendingSinceMin(),
},
Dimensions: ConfigRecoveryDimensions{
Autoconf: config.Data.DataV1.Autoconf,
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
ClickedLink: config.Data.clickedLinkToString(),
FailureDetails: config.Data.DataV1.FailureDetails,
},
}
}

View File

@ -1,79 +0,0 @@
// Copyright (c) 2024 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 configstatus_test
import (
"path/filepath"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/stretchr/testify/require"
)
func TestConfigurationRecovery_default(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigRecoveryBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_recovery", req.Event)
require.Equal(t, 0, req.Values.Duration)
require.Equal(t, "", req.Dimensions.Autoconf)
require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "", req.Dimensions.ClickedLink)
require.Equal(t, "", req.Dimensions.FailureDetails)
}
func TestConfigurationRecovery_fed(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{
PendingSince: time.Now().Add(-10 * time.Minute),
LastProgress: time.Time{},
Autoconf: "Mr TBird",
ClickedLink: 42,
ReportSent: false,
ReportClick: true,
FailureDetails: "Not an error",
},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigRecoveryBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_recovery", req.Event)
require.Equal(t, 10, req.Values.Duration)
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
}

View File

@ -1,61 +0,0 @@
// Copyright (c) 2024 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 configstatus
import (
"strconv"
)
type ConfigSuccessValues struct {
Duration int `json:"duration"`
}
type ConfigSuccessDimensions struct {
Autoconf string `json:"autoconf"`
ReportClick string `json:"report_click"`
ReportSent string `json:"report_sent"`
ClickedLink string `json:"clicked_link"`
}
type ConfigSuccessData struct {
MeasurementGroup string
Event string
Values ConfigSuccessValues
Dimensions ConfigSuccessDimensions
}
type ConfigSuccessBuilder struct{}
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
config.DataLock.RLock()
defer config.DataLock.RUnlock()
return ConfigSuccessData{
MeasurementGroup: "bridge.any.configuration",
Event: "bridge_config_success",
Values: ConfigSuccessValues{
Duration: config.isPendingSinceMin(),
},
Dimensions: ConfigSuccessDimensions{
Autoconf: config.Data.DataV1.Autoconf,
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
ClickedLink: config.Data.clickedLinkToString(),
},
}
}

View File

@ -1,77 +0,0 @@
// Copyright (c) 2024 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 configstatus_test
import (
"path/filepath"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/stretchr/testify/require"
)
func TestConfigurationSuccess_default(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigSuccessBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_success", req.Event)
require.Equal(t, 0, req.Values.Duration)
require.Equal(t, "", req.Dimensions.Autoconf)
require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "", req.Dimensions.ClickedLink)
}
func TestConfigurationSuccess_fed(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "dummy.json")
var data = configstatus.ConfigurationStatusData{
Metadata: configstatus.Metadata{Version: "1.0.0"},
DataV1: configstatus.DataV1{
PendingSince: time.Now().Add(-10 * time.Minute),
LastProgress: time.Time{},
Autoconf: "Mr TBird",
ClickedLink: 42,
ReportSent: false,
ReportClick: true,
FailureDetails: "Not an error",
},
}
require.NoError(t, dumpConfigStatusInFile(&data, file))
config, err := configstatus.LoadConfigurationStatus(file)
require.NoError(t, err)
var builder = configstatus.ConfigSuccessBuilder{}
req := builder.New(config)
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_success", req.Event)
require.Equal(t, 10, req.Values.Duration)
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
}

View File

@ -1,56 +0,0 @@
// Copyright (c) 2024 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 configstatus
import (
"time"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
)
const ProgressCheckInterval = time.Hour
type Metadata struct {
Version string `json:"version"`
}
type MetadataOnly struct {
Metadata Metadata `json:"metadata"`
}
type DataV1 struct {
PendingSince time.Time `json:"pending_since"`
LastProgress time.Time `json:"last_progress"`
Autoconf string `json:"auto_conf"`
ClickedLink uint64 `json:"clicked_link"`
ReportSent bool `json:"report_sent"`
ReportClick bool `json:"report_click"`
FailureDetails string `json:"failure_details"`
}
type ConfigurationStatusData struct {
Metadata Metadata `json:"metadata"`
DataV1 DataV1 `json:"dataV1"`
}
type ConfigurationStatus struct {
FilePath string
DataLock safe.RWMutex
Data *ConfigurationStatusData
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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