chore: merge "release/trift" to devel

This commit is contained in:
Jakub 2023-07-13 08:21:49 +02:00
commit c0fc23d7cd
37 changed files with 693 additions and 260 deletions

View File

@ -158,7 +158,6 @@ test-windows:
extends:
- .env-windows
- .script-test
- .rules-branch-and-MR-manual
test-darwin:
extends:
@ -174,7 +173,7 @@ test-coverage:
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
needs:
- test-linux
#- test-windows
- test-windows
- test-darwin
- test-integration
tags:

View File

@ -155,7 +155,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-2437: Silence harmless report to sentry.
* GODT-2649: Clean up cache files after failed connector create (Gluon).
* GODT-2638: Validate messages before import.
* GODT-2646: Bump GPA and Gluon dependecy after CIRCL upgrade.
* GODT-2646: Bump GPA and Gluon dependency after CIRCL upgrade.
* GODT-2454: Only Send status update if transaction succeeded.
* Test: fix flaky tests.
* GODT-2628: Attempt to fix closed channel panic on logout.
@ -215,7 +215,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-2574: Fix label/unlabel of large amounts of messages.
* GODT-2573: Handle invalid header fields in message.
* GODT-2573: Crash on null update.
* GODT-2407: Replace invalid email addresses with emtpy for new Drafts.
* GODT-2407: Replace invalid email addresses with empty for new Drafts.
## [Bridge 3.1.3] Quebec
@ -356,7 +356,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-2429: Do not report context cancel to sentry.
### Fixed
* GODT-2467: elide long email adresses in 'bad event' QML notification dialog.
* GODT-2467: elide long email addresses in 'bad event' QML notification dialog.
* GODT-2449: fix bug in Bridge-GUI's Exception::what().
* GODT-2427: Parsing header issues.
* GODT-2426: Fix crash on user delete.
@ -373,7 +373,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-2404: Handle unexpected EOF.
* GODT-2400: Allow state updates to be applied if command fails.
* GODT-2399: Fix immediate message deletion during updates.
* GODT-2390: Missing changes from pervious commit.
* GODT-2390: Missing changes from previous commit.
* GODT-2390: Add reports for uncaught json and net.opErr.
* GODT-2414: Multiple deletion bug in WriteControlledStore.
@ -438,7 +438,7 @@ GODT-1804: Preserve MIME parameters when uploading attachments.
* GODT-2223: Improve event handling.
* GODT-2305: Detect missing gluon DB.
* GODT-2291: Change gluon store default location from Cache to Data.
* Other: Disable dialer test until badssl cert is bumbed.
* Other: Disable dialer test until badssl cert is bumped.
* GODT-2292: Updated BUILDS.md doc.
* GODT-2258: suggest email as login when signing in via status window.
* Other: Report corrupt and/or insecure vaults to sentry.
@ -718,7 +718,7 @@ GODT-1804: Preserve MIME parameters when uploading attachments.
## [Bridge 2.4.6] Osney
### Changed
* GODT-2019: When signing out and a single user is connecte* we do not go back to the welcome screen.
* GODT-2019: When signing out and a single user is connected we do not go back to the welcome screen.
* GODT-2071: Bridge-gui report error if an orphan bridge is detected.
* GODT-2046: Bridge-gui log is included in optional archive sent with bug reports.
* GODT-2039: Bridge monitors bridge-gui via its PID.
@ -872,7 +872,7 @@ GODT-1804: Preserve MIME parameters when uploading attachments.
* GODT-1260: Renaming.
* GODT-1502: Rebranding: color and radius.
* GODT-1549: Add notification when address list changes.
* GODT-1560: Dependecy licenses update and link.
* GODT-1560: Dependency licenses update and link.
### Changed
* GODT-1543: Using one buffered event for off and on connection.
@ -969,7 +969,7 @@ GODT-1537: Manual in-app update mechanism.
* GODT-1338: GODT-1343 Help view buttons.
* GODT-1340: Not crashing, user list updating in main thread.
* GODT-1345: Adding panic handlers.
* GODT-1271: Fix Status margings.
* GODT-1271: Fix Status margins.
* GODT-1320: Add loading property to each action within a notification.
* GODT-1210: Add "free user" banner.
* GODT-1314: Limit description field length within 150/800 bounds.
@ -1011,7 +1011,7 @@ GODT-1537: Manual in-app update mechanism.
* GODT-1381 Treat readonly folder as failure for cache on disk.
* GODT-1431 Prevent watcher when not using disk on cache.
* GODT-1381: Use in-memory cache in case local cache is unavailable.
* GODT-1356 GODT-1302: Cache on disk concurency and API retries.
* GODT-1356 GODT-1302: Cache on disk concurrency and API retries.
* GODT-1332 Added tests for cache move functions.
* GODT-1332: moved cache related functions to separate file.
* GODT-1332 moving cache does not work on Windows.
@ -1262,7 +1262,7 @@ GODT-1537: Manual in-app update mechanism.
### Fixed
* GODT-1029 Fix tray icon not updating under certain conditions.
* GODT-1062 Fix lost notification bar when window is closed.
* GODT-1058 Install version after chaning channel right away only in case of downgrade.
* GODT-1058 Install version after changing channel right away only in case of downgrade.
* GODT-1073 Re-write autostart link on every start if turned on in preferences.
* GODT-1055 Fix flaky empty trash test.
@ -1352,7 +1352,7 @@ GODT-1537: Manual in-app update mechanism.
* GODT-820 Added GUI notification on impossibility of update installation (both silent and manual).
* GODT-870 Added GUI notification on error during silent update.
* GODT-805 Added GUI notification on update available.
* GODT-804 Added GUI notification on silent update installed (promt to restart).
* GODT-804 Added GUI notification on silent update installed (prompt to restart).
* GODT-275 Added option to disable autoupdates in settings (default autoupdate is enabled).
* GODT-874 Added manual triggers to Updater module.
* GODT-851 Added support of UID EXPUNGE.
@ -1676,7 +1676,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
### Changed
* GODT-360 Detect charset embedded in html/xml.
* GODT-354 Do not label/unlabel messsages from `All Mail` folder.
* GODT-354 Do not label/unlabel messages from `All Mail` folder.
* GODT-388 Support for both bridge and import/export credentials by package users.
* GODT-387 Store factory to make store optional.
* GODT-386 Renamed bridge to general users and keep bridge only for bridge stuff.
@ -1841,13 +1841,13 @@ CSB-331 Fix sending error due to mixed case in sender address.
* GODT-88 Run mbox sync in parallel when switch password mode (re-init not user).
* GODT-95 Do not throw error when trying to create new mailbox in IMAP root.
* GODT-75 Do not fail on unlabel inside delete.
* #1095 always delete IMAP USER including wrong pasword.
* #1095 always delete IMAP USER including wrong password.
* Unique pmapi client userID (including #1098).
* Using go.enmime@v0.6.1 snapshot.
* Better detection of non-auth-error.
* Reset `hasAuthChannel` during logout for proper login functionality (set up auth channel and unlock keys).
* Allow `APPEND` messages without parsable email address in sender field.
* #1060 avoid `Append` after internal message ID was found and message was copyed to mailbox using `MessageLabel`.
* #1060 avoid `Append` after internal message ID was found and message was copied to mailbox using `MessageLabel`.
* #1049 Basic usage of store in SMTP package to poll event loop during sending message.
* #1050 pollNow waits for events to be processed.
* #1047 Fix fetch of empty mailbox.
@ -1973,7 +1973,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* #903 added http.Client timeout to not hang out forever.
* Closing body after checking internet connection.
* Pedantic lint for bridgeUtils.
* Selected events are buffered and emited again when frontend loop is ready.
* Selected events are buffered and emitted again when frontend loop is ready.
* #890 implemented 2FA endpoint (auth split).
* #888 TLS Cert.
* Error bar and modal with explanation in GUI.
@ -1981,7 +1981,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Add pinning to bridge (only for live API builds).
* #887 #883:
* Wait before clearing data.
* Configer which provides pmapi.ClientConfig and app directories.
* Configure which provides pmapi.ClientConfig and app directories.
* #861 restart after clear data.
* Panic handler for all goroutines.
* CD for linux.
@ -2029,7 +2029,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* #882 unassign PMAPI client after logout and force to run garbage collector.
* #880, #884, #885, #886 fix of informing user about outgoing non-encrypted e-mail.
* #838 `Sirupsen` -> `sirupsen`.
* #893 save panic report file everytime.
* #893 save panic report file every time.
* #880 fix of informing user about outgoing non-encrypted e-mail.
* Fix aliases in split mode.
* Fix decrypted data in log notification.
@ -2103,7 +2103,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
### Changed
* Fix custom message format.
* #802 acumulated long lines while parsing body structure.
* #802 accumulated long lines while parsing body structure.
* Process `AddressEvent` before `MessageEvent`.
* #791 updated crypto: fix wrong signature format.
* #793 fix returning size.
@ -2125,7 +2125,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
### Changed
* #748 when charset missing assume utf8 and check the validity.
* #750 before sync check that events are uptodate, if not poll events instead of sync.
* #750 before sync check that events are up-to-date, if not poll events instead of sync.
* Use pmapi with support of decrypted access token.
* #750 Status is using DB status instead of API.
* Format panic error as string instead of struct dump.
@ -2142,7 +2142,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Full version of program visible on release notes.
### Changed
* #720 only one concurent DB sync.
* #720 only one concurrent DB sync.
* #720 sync every 3 pages.
* #512 extending list of charsets go-pm-mime!4.
@ -2166,7 +2166,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Fix srp modulus issue with new `ProtonMail/crypto`.
* Generate version files from main file.
* Be able to set update set on build.
* #597 check on start that certificat will be still valid after one month and generate new cert if not.
* #597 check on start that certificate will be still valid after one month and generate new cert if not.
* #597 extended certificate validity to 2 years.
* Copyright 2019.
* Exclude `protontech` repos from credits.
@ -2185,7 +2185,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* #592 internal references are added only when not present already.
* #592 field `Date` changed to m.Time only when wrong format or missing `Date`.
* #645 pmapi#26 `Message.Flags` instead of `IsEncrypted`, `Type`, `IsReplied`, `IsRepliedAll`, `IsForwarded`.
* DB: do not allow to put Body or Attachements to db.
* DB: do not allow to put Body or Attachments to db.
* #574 SMTP: can now send more than one email.
* #671 Verbosity levels: `debug` (only bridge), `debug-client` (bridge and client communication), `debug-server` (bridge, whole SMTP/IMAP communication).
* #644 Return rfc.size 0 or correct size of fetched body (stored in DB).
@ -2257,7 +2257,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Start with new versioning.
1.1.0
| | `--- bug fix number (internal, irregular, beta relases)
| | `--- bug fix number (internal, irregular, beta releases)
| `----- minor version (features, release once per month, live release, milestones)
`------- major version (big changes, once per year, breaking changes, api force upgrade)
@ -2323,7 +2323,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* All `client.Do` errors are interpreted as connection issue.
* Moved to internal gitlab.
* Typo `frontend-qml`.
* Better message for case when server is not reacheable.
* Better message for case when server is not reachable.
* Setting 1min timeout to IMAP connection.
### Changed
@ -2355,12 +2355,12 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Keychain format and function refactor.
* Create crash file on panic with full trace.
* Clear old data only in main process (no double keychain typing).
* Create label udpate API route.
* Create label update API route.
* Selectable text in release notes.
### Added
* Support sending to external PGP recipients.
* Return error codes: `0: Ok`, `2: Frontend crashed`, `3: Bridge already running`, `4: Uknown argument`, `42: Restart application`.
* Return error codes: `0: Ok`, `2: Frontend crashed`, `3: Bridge already running`, `4: Unknown argument`, `42: Restart application`.
### Release notes
* Support of encryption to external PGP recipients using contacts created on beta.protonmail.com (see https://protonmail.com/blog/pgp-vulnerability-efail/ to understand the vulnerabilities that may be associated with sending to other PGP clients).
@ -2385,7 +2385,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
* Bug report window.
* Checkbox and with label (only I/E).
* Error dialog and Info tooltip (only I/E).
* Add user modal formating (colors, text).
* Add user modal formatting (colors, text).
* Account view style.
* Input box style (used in bug report).
* Input field style (used in add account and change port).

View File

@ -2,13 +2,13 @@
## First login and sync
When user logs in to the bridge for the first time, immediatelly starts the first sync.
When user logs in to the bridge for the first time, immediately starts the first sync.
First sync downloads all headers of all e-mails and creates database to have proper UIDs
and indexes for IMAP. See [database](database.md) for more information.
By default, whenever it's possible, sync downloads only all e-mails maiblox which already
have list of labels so we can construct all mailboxes (inbox, sent, trash, custom folders
and lables) without need to download each e-mail headers many times.
and labels) without need to download each e-mail headers many times.
Note that we need to download also bodies to calculate size of the e-mail and set proper
content type (clients uses content type for guess if e-mail contains attachment)--but only
@ -22,7 +22,7 @@ client right after adding account.
When account is added to client, client start the sync. This sync will ask Bridge app
for all headers (done quickly) and then starts to download all bodies and attachment.
Unfortunatelly for some e-mail more than once if the same e-mail is in more mailboxes
Unfortunately for some e-mail more than once if the same e-mail is in more mailboxes
(e.g. inbox and all mail)--there is no way to tell over IMAP it's the same message.
After successful login of client to IMAP, Bridge starts event loop. That periodicly ask
@ -37,7 +37,7 @@ sequenceDiagram
Note right of B: Set up PM account<br/>by user
loop First sync
B ->> S: Fetch body and attachements
B ->> S: Fetch body and attachments
Note right of B: Build local database<br/>(e-mail UIDs)
end
@ -58,8 +58,8 @@ sequenceDiagram
C ->> B: IMAP SELECT directory
C ->> B: IMAP SEARCH e-mails UIDs
C ->> B: IMAP FETCH of e-mail UID
B ->> S: Fetch body and attachements
Note right of B: Decrypt message<br/>and attachement
B ->> S: Fetch body and attachments
Note right of B: Decrypt message<br/>and attachment
B ->> C: IMAP response
end
```

View File

@ -1,12 +1,12 @@
# Update mechanism of Bridge
There are mulitple options how to change version of application:
There are multiple options how to change version of application:
* Automatic in-app update
* Manual in-app update
* Manual install
In-app update ends with restarting bridge into new version. Automatic in-app
update is downloading, verifying and installing the new version immediatelly
update is downloading, verifying and installing the new version immediately
without user confirmation. For manual in-app update user needs to confirm first.
Update is done from special update file published on website.
@ -25,7 +25,7 @@ The bridge is installed and executed differently for given OS:
* macOS app does not use launcher
* No launcher, only one executable
* In-App udpate replaces the bridge files in installation path directly
* In-App update replaces the bridge files in installation path directly
```mermaid

View File

@ -31,7 +31,7 @@ import (
"github.com/stretchr/testify/require"
)
// Disabled due to flakyness.
// Disabled due to flakiness.
func _TestBridge_SyncExistsWithErrorWhenTooManyFilesAreOpen(t *testing.T) { //nolint:unused
var rlimitCurrent syscall.Rlimit

View File

@ -385,6 +385,14 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *)
app().log().debug(__FUNCTION__);
UsersTab &usersTab = app().mainWindow().usersTab();
loginUsername_ = QString::fromStdString(request->username());
SPUser const& user = usersTab.userTable().userWithUsernameOrEmail(QString::fromStdString(request->username()));
if (user) {
qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(user->id()));
return Status::OK;
}
if (usersTab.nextUserUsernamePasswordError()) {
qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::USERNAME_PASSWORD_ERROR, usersTab.usernamePasswordErrorMessage()));
return Status::OK;
@ -826,7 +834,7 @@ bool GRPCService::sendEvent(SPStreamEvent const &event) {
//****************************************************************************************************************************************************
void GRPCService::finishLogin() {
UsersTab &usersTab = app().mainWindow().usersTab();
SPUser user = usersTab.userWithUsername(loginUsername_);
SPUser user = usersTab.userWithUsernameOrEmail(loginUsername_);
bool const alreadyExist = user.get();
if (!user) {
user = randomUser();

View File

@ -272,8 +272,8 @@ bridgepp::SPUser UsersTab::userWithID(QString const &userID) {
/// \return The user with the given username.
/// \return A null pointer if the user is not in the list.
//****************************************************************************************************************************************************
bridgepp::SPUser UsersTab::userWithUsername(QString const &username) {
return users_.userWithUsername(username);
bridgepp::SPUser UsersTab::userWithUsernameOrEmail(QString const &username) {
return users_.userWithUsernameOrEmail(username);
}

View File

@ -38,7 +38,7 @@ public: // member functions.
UsersTab &operator=(UsersTab &&) = delete; ///< Disabled move assignment operator.
UserTable &userTable(); ///< Returns a reference to the user table.
bridgepp::SPUser userWithID(QString const &userID); ///< Get the user with the given ID.
bridgepp::SPUser userWithUsername(QString const &username); ///< Get the user with the given username.
bridgepp::SPUser userWithUsernameOrEmail(QString const &username); ///< Get the user with the given username.
bool nextUserUsernamePasswordError() const; ///< Check if next user login should trigger a username/password error.
bool nextUserFreeUserError() const; ///< Check if next user login should trigger a Free user error.
bool nextUserTFARequired() const; ///< Check if next user login should requires 2FA.

View File

@ -150,13 +150,16 @@ bridgepp::SPUser UserTable::userWithID(QString const &userID) {
//****************************************************************************************************************************************************
/// \param[in] username The username.
/// \param[in] username The username, or any email address attached to the account.
/// \return The user with the given username.
/// \return A null pointer if the user is not in the list.
//****************************************************************************************************************************************************
bridgepp::SPUser UserTable::userWithUsername(QString const &username) {
bridgepp::SPUser UserTable::userWithUsernameOrEmail(QString const &username) {
QList<SPUser>::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&username](SPUser const &user) -> bool {
return user->username() == username;
if (user->username().compare(username, Qt::CaseInsensitive) == 0) {
return true;
}
return user->addresses().contains(username, Qt::CaseInsensitive);
});
return it == users_.end() ? nullptr : *it;

View File

@ -40,7 +40,7 @@ public: // member functions.
void append(bridgepp::SPUser const &user); ///< Append a user.
bridgepp::SPUser userAtIndex(qint32 index); ///< Return the user at the given index.
bridgepp::SPUser userWithID(QString const &userID); ///< Return the user with a given id.
bridgepp::SPUser userWithUsername(QString const &username); ///< Return the user with a given username.
bridgepp::SPUser userWithUsernameOrEmail(QString const &username); ///< Return the user with a given username.
qint32 indexOfUser(QString const &userID); ///< Return the index of a given User.
void touch(qint32 index); ///< touch the user at a given index (indicates it has been modified).
void touch(QString const& userID); ///< touch the user with the given userID (indicates it has been modified).

View File

@ -51,7 +51,7 @@ public: // member functions.
void showSettings(QString const &reason); ///< Show the settings page.
void selectUser(QString const &userID, bool forceShowWindow, QString const &reason); ///< Select the user and display its account details (or login screen).
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
// invocable methods can be called from QML. They generally return a value, which slots cannot do.
Q_INVOKABLE static QString buildYear(); ///< Return the application build year.
Q_INVOKABLE QPoint getCursorPos() const; ///< Retrieve the cursor position.
Q_INVOKABLE bool isPortFree(int port) const; ///< Check if a given network port is available.

View File

@ -192,6 +192,8 @@ TrayIcon::TrayIcon()
if (!onLinux()) { // we disable this on linux because of a Qt bug that causes the signal to be emitted for other apps (GODT-2750)
connect(this, &TrayIcon::messageClicked, []() { app().backend().showMainWindow("tray icon popup notification clicked"); });
}
this->setIcon();
this->show();
// TrayIcon does not expose its screen, so we connect relevant screen events to our DPI change handler.

View File

@ -63,7 +63,7 @@ BRIDGE_BUILD_ENV= ${BRIDGE_BUILD_ENV:-"dev"}
git submodule update --init --recursive ${VCPKG_ROOT}
check_exit "Failed to initialize vcpkg as a submodule."
echo submodule udpated
echo submodule updated
VCPKG_EXE="${VCPKG_ROOT}/vcpkg"
VCPKG_BOOTSTRAP="${VCPKG_ROOT}/bootstrap-vcpkg.sh"

View File

@ -137,12 +137,21 @@ bool checkSingleInstance(QLockFile &lock) {
if (lock.getLockInfo(&pid, &hostname, &appName)) {
details = QString("(PID : %1 - Host : %2 - App : %3)").arg(pid).arg(hostname, appName);
}
if (lock.error() == QLockFile::LockFailedError) {
// This happens if a stale lock file exists and another process uses that PID.
// Try removing the stale file, which will fail if a real process is holding a
// file-level lock. A false error is more problematic than not locking properly
// on corner-case systems.
if (lock.removeStaleLockFile() && lock.tryLock()) {
app().log().info("Removed stale lock file");
app().log().info(QString("lock file created %1").arg(lock.fileName()));
return true;
}
}
app().log().error(QString("Instance already exists %1 %2").arg(lock.fileName(), details));
return false;
} else {
app().log().info(QString("lock file created %1").arg(lock.fileName()));
}
app().log().info(QString("lock file created %1").arg(lock.fileName()));
return true;
}

View File

@ -134,6 +134,11 @@ FocusScope {
stackLayout.currentIndex = 0
root.reset()
}
function onLoginAlreadyLoggedIn(index) {
stackLayout.currentIndex = 0
root.reset()
}
}
ColumnLayout {

View File

@ -101,7 +101,7 @@ CREATE_SUBDIRS = YES
# level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed
# numer of 16 directories.
# number of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.

View File

@ -78,10 +78,10 @@ void FocusGRPCClient::removeServiceConfigFile(QString const &configDir) {
//****************************************************************************************************************************************************
/// \param[in] timeoutMs The timeout for the connexion.
/// \param[in] timeoutMs The timeout for the connection.
/// \param[in] port The gRPC server port.
/// \param[out] outError if not null and the function returns false.
/// \return true iff the connexion was successfully established.
/// \return true iff the connection was successfully established.
//****************************************************************************************************************************************************
bool FocusGRPCClient::connectToServer(qint64 timeoutMs, quint16 port, QString *outError) {
try {
@ -97,7 +97,7 @@ bool FocusGRPCClient::connectToServer(qint64 timeoutMs, quint16 port, QString *o
}
if (channel_->GetState(true) != GRPC_CHANNEL_READY) {
throw Exception("Connexion check with focus service failed.");
throw Exception("Connection check with focus service failed.");
}
log_.debug(QString("Successfully connected to focus gRPC service."));

View File

@ -59,7 +59,7 @@ void GRPCClient::removeServiceConfigFile(QString const &configDir) {
//****************************************************************************************************************************************************
/// \param[in] sessionID The sessionID.
/// \param[in] timeoutMs The timeout in milliseconds
/// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connexion cannot be established. Ignored if null.
/// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connection cannot be established. Ignored if null.
/// \return The service config.
//****************************************************************************************************************************************************
GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const & sessionID, QString const &configDir, qint64 timeoutMs,
@ -118,7 +118,7 @@ void GRPCClient::setLog(Log *log) {
//****************************************************************************************************************************************************
/// \param[in] sessionID The sessionID.
/// \param[in] configDir The configuration directory
/// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connexion cannot be established. Ignored if null.
/// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connection cannot be established. Ignored if null.
/// \return true iff the connection was successful.
//****************************************************************************************************************************************************
void GRPCClient::connectToServer(QString const &sessionID, QString const &configDir, GRPCConfig const &config, ProcessMonitor *serverProcess) {
@ -150,7 +150,7 @@ void GRPCClient::connectToServer(QString const &sessionID, QString const &config
int i = 0;
while (true) {
if (serverProcess && serverProcess->getStatus().ended) {
throw Exception("Bridge application ended before gRPC connexion could be established.", QString(), __FUNCTION__,
throw Exception("Bridge application ended before gRPC connection could be established.", QString(), __FUNCTION__,
tailOfLatestBridgeLog(sessionID));
}

View File

@ -461,7 +461,7 @@ func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Emp
defer async.HandlePanic(s.panicHandler)
if s.auth.UID == "" || s.authClient == nil {
s.log.Errorf("Login 2FA: authethication incomplete %s %p", s.auth.UID, s.authClient)
s.log.Errorf("Login 2FA: authentication incomplete %s %p", s.auth.UID, s.authClient)
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, "Missing authentication, try again."))
s.loginClean()
return

View File

@ -28,6 +28,7 @@ import (
"github.com/ProtonMail/gluon/connector"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/rfc5322"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/gopenpgp/v2/crypto"
@ -281,6 +282,11 @@ func (conn *imapConnector) CreateMessage(
return imap.Message{}, nil, connector.ErrOperationNotAllowed
}
toList, err := getLiteralToList(literal)
if err != nil {
return imap.Message{}, nil, fmt.Errorf("failed to retrieve addresses from literal:%w", err)
}
// Compute the hash of the message (to match it against SMTP messages).
hash, err := getMessageHash(literal)
if err != nil {
@ -288,7 +294,7 @@ func (conn *imapConnector) CreateMessage(
}
// Check if we already tried to send this message recently.
if messageID, ok, err := conn.sendHash.hasEntryWait(ctx, hash, time.Now().Add(90*time.Second)); err != nil {
if messageID, ok, err := conn.sendHash.hasEntryWait(ctx, hash, time.Now().Add(90*time.Second), toList); err != nil {
return imap.Message{}, nil, fmt.Errorf("failed to check send hash: %w", err)
} else if ok {
conn.log.WithField("messageID", messageID).Warn("Message already sent")
@ -413,72 +419,13 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
return connector.ErrOperationNotAllowed
}
if err := conn.client.UnlabelMessages(ctx, mapTo[imap.MessageID, string](messageIDs), string(mailboxID)); err != nil {
msgIDs := mapTo[imap.MessageID, string](messageIDs)
if err := conn.client.UnlabelMessages(ctx, msgIDs, string(mailboxID)); err != nil {
return err
}
if mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
var msgToPermaDelete []string
// There's currently no limit on how many IDs we can filter on,
// but to be nice to API, let's chunk it by 150.
for _, messageIDs := range xslices.Chunk(messageIDs, 150) {
metadata, err := conn.client.GetMessageMetadata(ctx, proton.MessageFilter{
ID: mapTo[imap.MessageID, string](messageIDs),
})
if err != nil {
return err
}
msgIds, err := safe.LockRetErr(func() ([]string, error) {
var msgIds []string
// If a message is not preset in any other label other than AllMail, AllDrafts and AllSent, it can be
// permanently deleted.
for _, m := range metadata {
var remainingLabels []string
for _, id := range m.LabelIDs {
label, ok := conn.apiLabels[id]
if !ok {
// Handle case where this label was newly introduced and we do not yet know about it.
logrus.WithField("labelID", id).Warnf("Unknown label found during expung from Trash, attempting to locate it")
label, err = conn.client.GetLabel(ctx, id, proton.LabelTypeFolder, proton.LabelTypeSystem, proton.LabelTypeSystem)
if err != nil {
if errors.Is(err, proton.ErrNoSuchLabel) {
logrus.WithField("labelID", id).Warn("Label does not exist, ignoring")
continue
}
logrus.WithField("labelID", id).Errorf("Failed to resolve label: %v", err)
return nil, fmt.Errorf("failed to resolve label: %w", err)
}
}
if !wantLabel(label) {
continue
}
if id != proton.AllDraftsLabel && id != proton.AllMailLabel && id != proton.AllSentLabel {
remainingLabels = append(remainingLabels, m.ID)
}
}
if len(remainingLabels) == 0 {
msgIds = append(msgIds, m.ID)
}
}
return msgIds, nil
}, conn.User.apiLabelsLock)
if err != nil {
return err
}
msgToPermaDelete = append(msgToPermaDelete, msgIds...)
}
logrus.Debugf("Following message(s) will be perma-deleted: %v", msgToPermaDelete)
if err := conn.client.DeleteMessage(ctx, msgToPermaDelete...); err != nil {
if err := conn.client.DeleteMessage(ctx, msgIDs...); err != nil {
return err
}
}
@ -757,3 +704,45 @@ func buildFlagSetFromMessageMetadata(message proton.MessageMetadata) imap.FlagSe
return flags
}
func getLiteralToList(literal []byte) ([]string, error) {
headerLiteral, _ := rfc822.Split(literal)
header, err := rfc822.NewHeader(headerLiteral)
if err != nil {
return nil, err
}
var result []string
parseAddress := func(field string) error {
if fieldAddr, ok := header.GetChecked(field); ok {
addr, err := rfc5322.ParseAddressList(fieldAddr)
if err != nil {
return fmt.Errorf("failed to parse addresses for '%v': %w", field, err)
}
result = append(result, xslices.Map(addr, func(addr *mail.Address) string {
return addr.Address
})...)
return nil
}
return nil
}
if err := parseAddress("To"); err != nil {
return nil, err
}
if err := parseAddress("Cc"); err != nil {
return nil, err
}
if err := parseAddress("Bcc"); err != nil {
return nil, err
}
return result, nil
}

View File

@ -32,11 +32,14 @@ import (
const sendEntryExpiry = 30 * time.Minute
type SendRecorderID uint64
type sendRecorder struct {
expiry time.Duration
entries map[string][]*sendEntry
entriesLock sync.Mutex
entries map[string][]*sendEntry
entriesLock sync.Mutex
cancelIDCounter uint64
}
func newSendRecorder(expiry time.Duration) *sendRecorder {
@ -47,6 +50,7 @@ func newSendRecorder(expiry time.Duration) *sendRecorder {
}
type sendEntry struct {
srID SendRecorderID
msgID string
toList []string
exp time.Time
@ -69,16 +73,17 @@ func (h *sendRecorder) tryInsertWait(
hash string,
toList []string,
deadline time.Time,
) (bool, error) {
) (SendRecorderID, bool, error) {
// If we successfully inserted the hash, we can return true.
if h.tryInsert(hash, toList) {
return true, nil
srID, waitCh, ok := h.tryInsert(hash, toList)
if ok {
return srID, true, nil
}
// A message with this hash is already being sent; wait for it.
_, wasSent, err := h.wait(ctx, hash, deadline)
_, wasSent, err := h.wait(ctx, hash, waitCh, srID, deadline)
if err != nil {
return false, fmt.Errorf("failed to wait for message to be sent: %w", err)
return 0, false, fmt.Errorf("failed to wait for message to be sent: %w", err)
}
// If the message failed to send, try to insert it again.
@ -86,18 +91,23 @@ func (h *sendRecorder) tryInsertWait(
return h.tryInsertWait(ctx, hash, toList, deadline)
}
return false, nil
return srID, false, nil
}
// hasEntryWait returns whether the given message already exists in the send recorder.
// If it does, it waits for its ID to be known, then returns it and true.
// If no entry exists, or it times out while waiting for its ID to be known, it returns false.
func (h *sendRecorder) hasEntryWait(ctx context.Context, hash string, deadline time.Time) (string, bool, error) {
if !h.hasEntry(hash) {
func (h *sendRecorder) hasEntryWait(ctx context.Context,
hash string,
deadline time.Time,
toList []string,
) (string, bool, error) {
srID, waitCh, found := h.getEntryWaitInfo(hash, toList)
if !found {
return "", false, nil
}
messageID, wasSent, err := h.wait(ctx, hash, deadline)
messageID, wasSent, err := h.wait(ctx, hash, waitCh, srID, deadline)
if errors.Is(err, context.DeadlineExceeded) {
return "", false, nil
} else if err != nil {
@ -108,7 +118,7 @@ func (h *sendRecorder) hasEntryWait(ctx context.Context, hash string, deadline t
return messageID, true, nil
}
return h.hasEntryWait(ctx, hash, deadline)
return h.hasEntryWait(ctx, hash, deadline, toList)
}
func (h *sendRecorder) removeExpiredUnsafe() {
@ -125,7 +135,7 @@ func (h *sendRecorder) removeExpiredUnsafe() {
}
}
func (h *sendRecorder) tryInsert(hash string, toList []string) bool {
func (h *sendRecorder) tryInsert(hash string, toList []string) (SendRecorderID, <-chan struct{}, bool) {
h.entriesLock.Lock()
defer h.entriesLock.Unlock()
@ -135,42 +145,50 @@ func (h *sendRecorder) tryInsert(hash string, toList []string) bool {
if ok {
for _, entry := range entries {
if matchToList(entry.toList, toList) {
return false
return entry.srID, entry.waitCh, false
}
}
}
cancelID := h.newSendRecorderID()
waitCh := make(chan struct{})
h.entries[hash] = append(entries, &sendEntry{
srID: cancelID,
exp: time.Now().Add(h.expiry),
toList: toList,
waitCh: make(chan struct{}),
waitCh: waitCh,
})
return true
return cancelID, waitCh, true
}
func (h *sendRecorder) hasEntry(hash string) bool {
func (h *sendRecorder) getEntryWaitInfo(hash string, toList []string) (SendRecorderID, <-chan struct{}, bool) {
h.entriesLock.Lock()
defer h.entriesLock.Unlock()
h.removeExpiredUnsafe()
if _, ok := h.entries[hash]; ok {
return true
if entries, ok := h.entries[hash]; ok {
for _, e := range entries {
if matchToList(e.toList, toList) {
return e.srID, e.waitCh, true
}
}
}
return false
return 0, nil, false
}
// signalMessageSent should be called after a message has been successfully sent.
func (h *sendRecorder) signalMessageSent(hash, msgID string, toList []string) {
func (h *sendRecorder) signalMessageSent(hash string, srID SendRecorderID, msgID string) {
h.entriesLock.Lock()
defer h.entriesLock.Unlock()
entries, ok := h.entries[hash]
if ok {
for _, entry := range entries {
if matchToList(entry.toList, toList) {
if entry.srID == srID {
entry.msgID = msgID
entry.closeWaitChannel()
return
@ -181,7 +199,7 @@ func (h *sendRecorder) signalMessageSent(hash, msgID string, toList []string) {
logrus.Warn("Cannot add message ID to send hash entry, it may have expired")
}
func (h *sendRecorder) removeOnFail(hash string, toList []string) {
func (h *sendRecorder) removeOnFail(hash string, id SendRecorderID) {
h.entriesLock.Lock()
defer h.entriesLock.Unlock()
@ -191,7 +209,7 @@ func (h *sendRecorder) removeOnFail(hash string, toList []string) {
}
for idx, entry := range entries {
if entry.msgID == "" && matchToList(entry.toList, toList) {
if entry.srID == id && entry.msgID == "" {
entry.closeWaitChannel()
remaining := xslices.Remove(entries, idx, 1)
@ -204,15 +222,16 @@ func (h *sendRecorder) removeOnFail(hash string, toList []string) {
}
}
func (h *sendRecorder) wait(ctx context.Context, hash string, deadline time.Time) (string, bool, error) {
func (h *sendRecorder) wait(
ctx context.Context,
hash string,
waitCh <-chan struct{},
srID SendRecorderID,
deadline time.Time,
) (string, bool, error) {
ctx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
waitCh, ok := h.getWaitCh(hash)
if !ok {
return "", false, nil
}
select {
case <-ctx.Done():
return "", false, ctx.Err()
@ -225,21 +244,19 @@ func (h *sendRecorder) wait(ctx context.Context, hash string, deadline time.Time
defer h.entriesLock.Unlock()
if entry, ok := h.entries[hash]; ok {
return entry[0].msgID, true, nil
for _, e := range entry {
if e.srID == srID {
return e.msgID, true, nil
}
}
}
return "", false, nil
}
func (h *sendRecorder) getWaitCh(hash string) (<-chan struct{}, bool) {
h.entriesLock.Lock()
defer h.entriesLock.Unlock()
if entry, ok := h.entries[hash]; ok {
return entry[0].waitCh, true
}
return nil, false
func (h *sendRecorder) newSendRecorderID() SendRecorderID {
h.cancelIDCounter++
return SendRecorderID(h.cancelIDCounter)
}
// getMessageHash returns the hash of the given message.

View File

@ -29,67 +29,73 @@ func TestSendHasher_Insert(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srdID1, hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash1)
// Simulate successfully sending the message.
h.signalMessageSent(hash1, "abc", nil)
h.signalMessageSent(hash1, srdID1, "abc")
// Inserting a message with the same hash should return false.
_, ok, err = testTryInsert(h, literal1, time.Now().Add(time.Second))
srdID2, _, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.False(t, ok)
require.Equal(t, srdID1, srdID2)
// Inserting a message with a different hash should return true.
hash2, ok, err := testTryInsert(h, literal2, time.Now().Add(time.Second))
srdID3, hash2, ok, err := testTryInsert(h, literal2, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash2)
require.NotEqual(t, srdID3, srdID1)
}
func TestSendHasher_Insert_Expired(t *testing.T) {
h := newSendRecorder(time.Second)
// Insert a message into the hasher.
hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash1)
// Simulate successfully sending the message.
h.signalMessageSent(hash1, "abc", nil)
h.signalMessageSent(hash1, srID1, "abc")
// Wait for the entry to expire.
time.Sleep(time.Second)
// Inserting a message with the same hash should return true because the previous entry has since expired.
hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID2, hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
// The hashes should be the same.
require.Equal(t, hash1, hash2)
// Send IDs should differ
require.NotEqual(t, srID2, srID1)
}
func TestSendHasher_Insert_DifferentToList(t *testing.T) {
h := newSendRecorder(time.Second)
// Insert a message into the hasher.
hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def"}...)
srID1, hash1, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def"}...)
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash1)
// Insert the same message into the hasher but with a different to list.
hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def", "ghi"}...)
srID2, hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def", "ghi"}...)
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash2)
require.NotEqual(t, srID1, srID2)
// Insert the same message into the hasher but with the same to list.
_, ok, err = testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def", "ghi"}...)
_, _, ok, err = testTryInsert(h, literal1, time.Now().Add(time.Second), []string{"abc", "def", "ghi"}...)
require.Error(t, err)
require.False(t, ok)
}
@ -98,7 +104,7 @@ func TestSendHasher_Wait_SendSuccess(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
@ -106,20 +112,21 @@ func TestSendHasher_Wait_SendSuccess(t *testing.T) {
// Simulate successfully sending the message after half a second.
go func() {
time.Sleep(time.Millisecond * 500)
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, srID1, "abc")
}()
// Inserting a message with the same hash should fail.
_, ok, err = testTryInsert(h, literal1, time.Now().Add(time.Second))
srID2, _, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.False(t, ok)
require.Equal(t, srID1, srID2)
}
func TestSendHasher_Wait_SendFail(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
@ -127,13 +134,14 @@ func TestSendHasher_Wait_SendFail(t *testing.T) {
// Simulate failing to send the message after half a second.
go func() {
time.Sleep(time.Millisecond * 500)
h.removeOnFail(hash, nil)
h.removeOnFail(hash, srID1)
}()
// Inserting a message with the same hash should succeed because the first message failed to send.
hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID2, hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEqual(t, srID2, srID1)
// The hashes should be the same.
require.Equal(t, hash, hash2)
@ -143,13 +151,13 @@ func TestSendHasher_Wait_Timeout(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
_, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
// We should fail to insert because the message is not sent within the timeout period.
_, _, err = testTryInsert(h, literal1, time.Now().Add(time.Second))
_, _, _, err = testTryInsert(h, literal1, time.Now().Add(time.Second))
require.Error(t, err)
}
@ -157,13 +165,13 @@ func TestSendHasher_HasEntry(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
// Simulate successfully sending the message.
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, srID1, "abc")
// The message was already sent; we should find it in the hasher.
messageID, ok, err := testHasEntry(h, literal1, time.Now().Add(time.Second))
@ -176,7 +184,7 @@ func TestSendHasher_HasEntry_SendSuccess(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
@ -184,7 +192,7 @@ func TestSendHasher_HasEntry_SendSuccess(t *testing.T) {
// Simulate successfully sending the message after half a second.
go func() {
time.Sleep(time.Millisecond * 500)
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, srID1, "abc")
}()
// The message was already sent; we should find it in the hasher.
@ -202,15 +210,15 @@ func TestSendHasher_DualAddDoesNotCauseCrash(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
// Simulate successfully sending the message. We call this method twice as it possible for multiple SMTP connections
// to attempt to send the same message.
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, srID1, "abc")
h.signalMessageSent(hash, srID1, "abc")
// The message was already sent; we should find it in the hasher.
messageID, ok, err := testHasEntry(h, literal1, time.Now().Add(time.Second))
@ -223,23 +231,52 @@ func TestSendHashed_MessageWithSameHasButDifferentRecipientsIsInserted(t *testin
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), "Receiver <receiver@pm.me>")
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), "Receiver <receiver@pm.me>")
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), "Receiver <receiver@pm.me>", "Receiver2 <receiver2@pm.me>")
srID2, hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second), "Receiver <receiver@pm.me>", "Receiver2 <receiver2@pm.me>")
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash2)
require.Equal(t, hash, hash2)
// Should map to different requests
require.NotEqual(t, srID2, srID1)
}
func TestSendHashed_SameMessageWIthDifferentToListShouldWaitSuccessfullyAfterSend(t *testing.T) {
// Check that if we send the same message twice with different recipients and the second message is somehow
// sent before the first, ensure that we check if the message was sent we wait on the correct object.
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
_, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Minute), "Receiver <receiver@pm.me>")
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
srID2, hash2, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Microsecond), "Receiver <receiver@pm.me>", "Receiver2 <receiver2@pm.me>")
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash2)
require.Equal(t, hash, hash2)
// simulate message sent
h.signalMessageSent(hash2, srID2, "newID")
// Simulate Wait on message 2
_, ok, err = h.hasEntryWait(context.Background(), hash2, time.Now().Add(time.Second), []string{"Receiver <receiver@pm.me>", "Receiver2 <receiver2@pm.me>"})
require.NoError(t, err)
require.True(t, ok)
}
func TestSendHasher_HasEntry_SendFail(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
@ -247,7 +284,7 @@ func TestSendHasher_HasEntry_SendFail(t *testing.T) {
// Simulate failing to send the message after half a second.
go func() {
time.Sleep(time.Millisecond * 500)
h.removeOnFail(hash, nil)
h.removeOnFail(hash, srID1)
}()
// The message failed to send; we should not find it in the hasher.
@ -260,7 +297,7 @@ func TestSendHasher_HasEntry_Timeout(t *testing.T) {
h := newSendRecorder(sendEntryExpiry)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
_, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
@ -275,13 +312,13 @@ func TestSendHasher_HasEntry_Expired(t *testing.T) {
h := newSendRecorder(time.Second)
// Insert a message into the hasher.
hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
srID1, hash, ok, err := testTryInsert(h, literal1, time.Now().Add(time.Second))
require.NoError(t, err)
require.True(t, ok)
require.NotEmpty(t, hash)
// Simulate successfully sending the message.
h.signalMessageSent(hash, "abc", nil)
h.signalMessageSent(hash, srID1, "abc")
// Wait for the entry to expire.
time.Sleep(time.Second)
@ -410,25 +447,25 @@ func TestGetMessageHash(t *testing.T) {
}
}
func testTryInsert(h *sendRecorder, literal string, deadline time.Time, toList ...string) (string, bool, error) { //nolint:unparam
func testTryInsert(h *sendRecorder, literal string, deadline time.Time, toList ...string) (SendRecorderID, string, bool, error) { //nolint:unparam
hash, err := getMessageHash([]byte(literal))
if err != nil {
return 0, "", false, err
}
srID, ok, err := h.tryInsertWait(context.Background(), hash, toList, deadline)
if err != nil {
return 0, "", false, err
}
return srID, hash, ok, nil
}
func testHasEntry(h *sendRecorder, literal string, deadline time.Time, toList ...string) (string, bool, error) { //nolint:unparam
hash, err := getMessageHash([]byte(literal))
if err != nil {
return "", false, err
}
ok, err := h.tryInsertWait(context.Background(), hash, toList, deadline)
if err != nil {
return "", false, err
}
return hash, ok, nil
}
func testHasEntry(h *sendRecorder, literal string, deadline time.Time) (string, bool, error) { //nolint:unparam
hash, err := getMessageHash([]byte(literal))
if err != nil {
return "", false, err
}
return h.hasEntryWait(context.Background(), hash, deadline)
return h.hasEntryWait(context.Background(), hash, deadline, toList)
}

View File

@ -81,7 +81,8 @@ func (user *User) sendMail(authID string, from string, to []string, r io.Reader)
}
// Check if we already tried to send this message recently.
if ok, err := user.sendHash.tryInsertWait(ctx, hash, to, time.Now().Add(90*time.Second)); err != nil {
srID, ok, err := user.sendHash.tryInsertWait(ctx, hash, to, time.Now().Add(90*time.Second))
if err != nil {
return fmt.Errorf("failed to check send hash: %w", err)
} else if !ok {
user.log.Warn("A duplicate message was already sent recently, skipping")
@ -89,7 +90,7 @@ func (user *User) sendMail(authID string, from string, to []string, r io.Reader)
}
// If we fail to send this message, we should remove the hash from the send recorder.
defer user.sendHash.removeOnFail(hash, to)
defer user.sendHash.removeOnFail(hash, srID)
// Create a new message parser from the reader.
parser, err := parser.New(bytes.NewReader(b))
@ -162,7 +163,7 @@ func (user *User) sendMail(authID string, from string, to []string, r io.Reader)
}
// If the message was successfully sent, we can update the message ID in the record.
user.sendHash.signalMessageSent(hash, sent.ID, to)
user.sendHash.signalMessageSent(hash, srID, sent.ID)
return nil
})

View File

@ -355,7 +355,7 @@ func (user *User) syncMessages(
case syncLimits.MaxSyncMemory == 2*Gigabyte:
// Increasing the max download capacity has very little effect on sync speed. We could increase the download
// memory but the user would see less sync notifications. A smaller value here leads to more frequent
// updates. Additionally, most of ot sync time is spent in the message building.
// updates. Additionally, most of sync time is spent in the message building.
syncMaxDownloadRequestMem = syncLimits.MaxDownloadRequestMem
// Currently limited so that if a user has multiple accounts active it also doesn't cause excessive memory usage.
syncMaxMessageBuildingMem = syncLimits.MaxMessageBuildingMem

View File

@ -24,7 +24,7 @@ import (
var ErrInvalidReplyType = errors.New("reply type does not match")
// Utilities to implement Chanel Procedure Calls. Similar in concept to RPC, but with between go-routines.
// Utilities to implement Channel Procedure Calls. Similar in concept to RPC, but with between go-routines.
// Request contains the data for a request as well as the means to reply to a request.
type Request struct {

View File

@ -115,7 +115,7 @@ func (h *macOSHelper) Get(secretURL string) (string, string, error) {
results, err := keychain.QueryItem(query)
if err != nil {
l.WithError(err).Error("Querry item failed")
l.WithError(err).Error("Query item failed")
return "", "", parseError(err)
}

View File

@ -205,7 +205,7 @@ func EncodeHeader(s string) string {
return mime.QEncoding.Encode("utf-8", s)
}
// DecodeCharset decodes the orginal using content type parameters.
// DecodeCharset decodes the original using content type parameters.
// If the charset parameter is missing it checks that the content is valid utf8.
// If it isn't, it checks if it's embedded in the html/xml.
// If it isn't, it falls back to windows-1252.
@ -240,7 +240,7 @@ func DecodeCharset(original []byte, contentType string) ([]byte, error) {
logrus.WithField("encoding", name).Warn("Determined encoding but was not certain")
}
// Reencode as UTF-8.
// Re-encode as UTF-8.
decoded, err := encoding.NewDecoder().Bytes(original)
if err != nil {
return original, errors.Wrap(err, "failed to decode as windows-1252")

View File

@ -446,7 +446,7 @@ func TestEncodeReader(t *testing.T) {
}
if bytes.Equal(decoded, expected) {
// fmt.Println("Succesfull decoding of ", val.params, ":", string(decoded))
// fmt.Println("Successful decoding of ", val.params, ":", string(decoded))
} else {
t.Error("Wrong encoding of ", val.charset, ".Expected\n", expected, "\nbut have\n", decoded)
}

View File

@ -28,7 +28,7 @@ import (
"github.com/sirupsen/logrus"
)
// maxFileSize limit tre single file size after decopression is not larger than 1GB.
// maxFileSize limit the single file size after decompression is not larger than 1GB.
const maxFileSize = int64(1 * 1024 * 1024 * 1024) // 1 GB
// ErrFileTooLarge returned when decompressed file is too large.

View File

@ -1,3 +1,238 @@
## v3.3.1
- 2023-07-07
### New
- Improved Bridge debugging capabilities by adding more information to the application logs
- Started measuring the Bridge setup experience
### Fixed
- Temporarily removed clickable notifications on Linux to not raise the Bridge window when receiving non-Bridge notifications
## v3.3.0
- 2023-06-08
### New
- Reduced the number of occasions when email clients ask for Bridge credentials
- Added new Bridge notifications to help users to configure and troubleshoot their email clients
- To avoid the need to reconfigure email clients, Bridge remembers the old account password when an account is re-added (removed and added again)
- Further improved logging to support troubleshooting
- 2 factor authentication (2FA) is submitted automatically after entering a code
- Removed the requirement of having an administrator account on macOS to install Bridge
### Fixed
- Fixed numerous crashes
- Fixed the case when an email could not be sent if a PDF was attached to the email
- Added varioius bugfixes and security improvemenets
- Reduced the Bridge cache size by cleaning up temporary emails that were saved during failed initial synchronizations
- Further reduced the chance of desyncronization between the email client and Bridge
## v3.2.0
- 2023-05-15
### New
- Enhanced Proton infrastructure protection
- Enhanced the integration with the operating system by replacing status windows with native tray icon context menu
- Switched to two columns layout on the account details page to make the informaion easier to access
- Improved logs to support troubleshooting
- Added optional usage sharing to support user experience improvements. Additional information about data sharing can be found on our [support page](https://proton.me/support/share-usage-statistics).
- Implemented smart picking of default IMAP and SMTP ports
- Added various security and performance improvements
### Fixed
- Replaced invalid email addresses with empty field for new drafts so it can be syncronized across Proton clients
- Improved crash handling
- Fixed label / unlabel performance when applied on large amount of emails
- Fixed "reply to" related issues
- Updated build instructions
- Announced IMAP ID capability to email clients
## v3.1.3
- 2023-05-10
### Fixed
- Added a missing error handler that can make the initial synchronization to stuck
## v3.1.2
- 2023-04-27
### New
- Optimized Recovered Messages folder size by not adding a message to the folder if that message has been added to it before (deduplication)
## v3.1.1
- 2023-04-11
### Fixed
- Improved exception / crash handling
## v3.1.0
- 2023-04-05
### New
- Significantly reduced memory consumption both during synchronization and communication with email clients
- Added synchronization indicator to the graphical user interface (GUI)
- Added "Close window" and "Quit Bridge" buttons to the main window
- Added command line switches to control GUI rendering
- Switched to software rendering on Windows to support old graphics cards
- Added support for Proton's Scheduled send feature
- Avoided making email clients to ask for Bridge credentials when they started faster than Bridge at startup
- Added a notification when a user is signed out from Bridge in the background
- Improved desynchronization avoidence by setting UIDValidity from the current time
- Started updating emails in the email clients frequently when Bridge is started after not being online for longer period of time
- Improved error detection and handling
### Fixed
- Fixed transparent window with old graphics cards or virtual machines on Windows
- Reduced notifications that does not require user actions
- Improved exception / crash handling
- Improved handling complex MIME types
- Reduced the source of errors that can lead to gRPC related error messages
- Fixed sub-folder rename issues
- Fixed various bugs related to secure vault handling, network communication errors, Proton server communication, operating system integration.
## v3.0.21
- 2023-03-23
### New
- Extended the migration from the previous major Bridge version with certificates
- Improved error detection
### Fixed
- Fixed the misplaced .desktop file on Linux
- Fixed DBUS secret service integration (e.g., KWallet, KeePass)
- Made Bridge more resilient against Proton server outages
## v3.0.20
- 2023-03-09
### New
- Added better explanation when an email cannot be sent because of non-existing email addresses
- Added a dialog to Bridge where users can repair the application when it encounters an internal error
- Improved error detection
### Fixed
- Reduced the cases when Bridge could not restart automatically
- Fixed the bug that could cause email states (e.g., read, unread, answered) to come out of sync with the web application. **NOTE: This fix is only applied to new emails. In order to fix older emails in Bridge, the account in Bridge needs to be removed and added back.**
- Fixed incorrect subject parsing caused by double quotes
## v3.0.19
- 2023-03-01
### New
- Improved inter-process communication error detection
- Improved exceptions related error detection
### Fixed
- Fixed numerous sources of errors leading to logout (internal errors)
- Fixed inter-process communication related startup issues (e.g., gRPC, service configuration file exchange)
## v3.0.18
- 2023-02-24
### New
- Improved event processing related error handling
### Fixed
- Fixed manual update errors on Windows by ensuring that all new files are deployed by the Bridge installer
## v3.0.16
- 2023-02-17
### Fixed
- Desynchronization while creating draft.
## v3.0.15
- 2023-02-14
### Fixed
- Better network error handling
## v3.0.14
- 2023-02-09
### New
- Improved error detection
### Fixed
- Fixed the sync issues that can happen when updating from an earlier v3 version
- Improved attachment handling by setting proper MIME parameters
- Improved update processing while Bridge is not active or performs a synchronization with Proton servers
## v3.0.12
- 2023-02-01
### New
- Changed the default location of the database and storage files. **NOTE: Please delete the old cache location if necessary.**
- Optimised cache, database and storage placement
- Improved email sending performance
- Improved unexpected event handling
### Fixed
- Outlook does not show sent messages as drafts
- Improved 'Reply to' behaviour
## v3.0.10
- 2023-01-17
### New
- Program argument to use software rendering.
- Improved exception handling in GUI.
### Fixed
- API event processing more robust.
- Improve the startup process.
- Fixed sub-folder creation bug.
## v3.0.9
- 2023-01-05
### New
- Added an option to the GUI to export TLS certificates
- Increased tolerance of invalid messages
### Fixed
- Autostart is set only when changed by the user
- Folders that are created during initial sync are synchronized correctly
- Improved settings migration from 2.x to 3.x
- Error reporting improvements on Intel Macs
- Show the setup guide after the first login
- User name and password validation messages are shown only when the Sign in button is pressed
- The Bridge main window is not shown on startup or after a crash
- Sign in button is not greyed out after the first login
## v3.0.8
- 2022-12-20
### New
- Improved error detection when Proton server updates cannot be processed
### Fixed
- Proton server update processing will not stop after a folder update failure
## v3.0.7
- 2022-12-19
### New
- Increase worker count (performance improvement)
### Fixed
- Bridge password migration from 2.x to 3.x
- Ensure proper handling of folders and labels with non-US ASCII chars
## v3.0.6
- 2022-12-12
@ -178,7 +413,7 @@
- Improved Sentry reporting
### Fixed
- Ensure messageID is properly removed from DB when it is no logner present on the API
- Ensure messageID is properly removed from DB when it is no longer present on the API
## v2.1.0
@ -372,7 +607,7 @@ New local cache
- Added IMAP requests to the logs for easier debugging
### Fixed
- NoGUI bulid
- NoGUI build
- Background of GUI welcome message
- Incorrect total mailbox size displayed in Apple Mail

View File

@ -1,31 +1,157 @@
## v2.4.8
- 2022-11-22
## v3.3.0
- 2023-06-20
### New
- Native Mac M1 release
- Upgrade to Qt 6:
- Change the app architecture
- Drop therecipe/qt dependency
- Update to go1.18
- Update to Qt 6.3.2
- Ensured the use of random port for gRPC
- Implemented token exchange for identity validation
- Ensured gRPC generates its own TLS certificate
- Increased bridge-gui timeout for gRPC server connection
- Added new warnings for 'TLS pinning' and 'no active key for recipient' errors
- GUI improvements
- More verbose logs for GUI-related issues
- New icon for .dmg installer
- Reduced the number of occasions when email clients ask for Bridge credentials
- Added new Bridge notifications to help users to configure and troubleshoot their email clients
- To avoid the need to reconfigure email clients, Bridge remembers the old account password when an account is re-added (removed and added again)
- Further improved logging to support troubleshooting
- 2 factor authentication (2FA) is submitted automatically after entering a code
- Removed the requirement of having an administrator account on macOS to install Bridge
### Fixed
- Fixed numerous crashes
- Fixed the case when an email could not be sent if a PDF was attached to the email
- Added varioius bugfixes and security improvemenets
- Reduced the Bridge cache size by cleaning up temporary emails that were saved during failed initial synchronizations
- Further reduced the chance of desyncronization between the email client and Bridge
## v3.2.0
- 2023-05-26
### New
- Enhanced Proton infrastructure protection
- Enhanced the integration with the operating system by replacing status windows with native tray icon context menu
- Switched to two columns layout on the account details page to make the informaion easier to access
- Improved logs to support troubleshooting
- Added optional usage sharing to support user experience improvements. Additional information about data sharing can be found on our [support page](https://proton.me/support/share-usage-statistics).
- Implemented smart picking of default IMAP and SMTP ports
- Added various security and performance improvements
### Fixed
- Replaced invalid email addresses with empty field for new drafts so it can be syncronized across Proton clients
- Improved crash handling
- Fixed label / unlabel performance when applied on large amount of emails
- Fixed "reply to" related issues
- Updated build instructions
- Announced IMAP ID capability to email clients
## v3.1.3
- 2023-05-10
### Fixed
- Added a missing error handler that can make the initial synchronization to stuck
## v3.1.2
- 2023-04-27
### New
- Significantly reduced memory consumption both during synchronization and communication with email clients
- Added synchronization indicator to the graphical user interface (GUI)
- Added "Close window" and "Quit Bridge" buttons to the main window
- Added command line switches to control GUI rendering
- Switched to software rendering on Windows to support old graphics cards
- Added support for Proton's Scheduled send feature
- Avoided making email clients to ask for Bridge credentials when they started faster than Bridge at startup
- Added a notification when a user is signed out from Bridge in the background
- Improved desynchronization avoidence by setting UIDValidity from the current time
- Started updating emails in the email clients frequently when Bridge is started after not being online for longer period of time
- Improved error detection and handling
- Optimized Recovered Messages folder size by not adding a message to the folder if that message has been added to it before (deduplication)
### Fixed
- Fixed transparent window with old graphics cards or virtual machines on Windows
- Reduced notifications that does not require user actions
- Improved exception / crash handling
- Improved handling complex MIME types
- Reduced the source of errors that can lead to gRPC related error messages
- Fixed sub-folder rename issues
- Fixed various bugs related to secure vault handling, network communication errors, Proton server communication, operating system integration.
## v3.0.21
- 2023-03-23
### New
- Extended the migration from the previous major Bridge version with certificates
- Improved error detection
### Fixed
- Fixed the misplaced .desktop file on Linux
- Fixed DBUS secret service integration (e.g., KWallet, KeePass)
- Made Bridge more resilient against Proton server outages
## v3.0.20
- 2023-03-09
### New
- Added better explanation when an email cannot be sent because of non-existing email addresses
- Added a dialog to Bridge where users can repair the application when it encounters an internal error
- Improved error detection
### Fixed
- Reduced the cases when Bridge could not restart automatically
- Fixed the bug that could cause email states (e.g., read, unread, answered) to come out of sync with the web application. **NOTE: This fix is only applied to new emails. In order to fix older emails in Bridge, the account in Bridge needs to be removed and added back.**
- Fixed incorrect subject parsing caused by double quotes
## v3.0.19
- 2023-03-01
### New
- Improved inter-process communication error detection
- Improved exceptions related error detection
### Fixed
- Fixed numerous sources of errors leading to logout (internal errors)
- Fixed inter-process communication related startup issues (e.g., gRPC, service configuration file exchange)
## v3.0.18
- 2023-02-24
### New
- Improved event processing related error handling
### Fixed
- Fixed manual update errors on Windows by ensuring that all new files are deployed by the Bridge installer
## v3.0.17
- 2023-02-22
### New
- Rewrote a significant part of Bridge to improve overall Bridge stability and performance
- Open sourced and integrated a new IMAP library, Gluon (https://github.com/ProtonMail/gluon)
- Open sourced and integrated a new Proton API library, Go Proton API (GPA) (https://github.com/ProtonMail/go-proton-api)
- Significantly improved error detection and unexpected error handling
- Improved email sending performance
- Improved synchronization performance
- Added new command line argument for software rendering
- Extended the coverage of the Bridge data that is encrypted on the users' computers
- Added an option to the graphical user interface to export TLS certificates
- Reimplemented the user interface (upgraded to the Qt 6 user interface library)
- Added native Apple Silicon macOS support
- Added an option to change IMAP connection mode
- Added subfolder support
- Added a new icon for the .dmg installer
- Increased automated test coverage
### Fixed
- Desynchronization while creating a draft email
- Fixed sub-folder creation issues
- User name and password validation messages are shown only when the Sign in button is pressed
- Improved handling SMTP send deduplication
- Improved robustness of Bridge restart
- The notification for when Bridge ports are occupied
- Fixed the user notification for occupied Bridge ports
- Fixed vulnerabilities of golang.org/x/crypto
- Missing Library on Fedora/Gnome upgrade form 2.3 to 2.4
- Added Digital-Signature for DLLs (Windows Security Alert to show Bridge as coming from a trusted publisher)
- Change download and version check urls to proton.me
- Fixed manual check for updates after switching the update channel
- Fixes to the update process on Linux and Windows (qt6 related)
- Added the missing Library on Fedora/Gnome for 2.3 to 2.4 update
- Added digital-signature for DLLs (to avoid the Windows Security alert, and to show if Bridge is coming from a trusted publisher)
- Fixed many Qt 6 related Linux and Windows update process issues
- Changed the default location of the database and storage files to avoid conflicts with cache cleaner applications
## v2.3.0
@ -89,7 +215,7 @@
- Improved Sentry reporting
### Fixed
- Ensure messageID is properly removed from DB when it is no logner present on the API
- Ensure messageID is properly removed from DB when it is no longer present on the API
## v2.1.0
@ -251,7 +377,7 @@ Other
- Fixed GUI freeze while switching to early update channel
- Fixed Bridge autostart
- Improved signing of update packages
- NoGUI bulid
- NoGUI build
- Background of GUI welcome message
- Incorrect total mailbox size displayed in Apple Mail

View File

@ -7,7 +7,7 @@ Feature: IMAP remove messages from Trash
| label | label |
Then it succeeds
Scenario Outline: Message in Trash and some other label is not permanently deleted
Scenario Outline: Message in Trash and some other label is permanently deleted
Given the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Trash":
| from | to | subject | body |
| john.doe@mail.com | [user:user]@[domain] | foo | hello |
@ -27,8 +27,8 @@ Feature: IMAP remove messages from Trash
When IMAP client "1" expunges
Then it succeeds
And IMAP client "1" eventually sees 1 messages in "Trash"
And IMAP client "1" eventually sees 2 messages in "All Mail"
And IMAP client "1" eventually sees 1 messages in "Labels/label"
And IMAP client "1" eventually sees 1 messages in "All Mail"
And IMAP client "1" eventually sees 0 messages in "Labels/label"
Scenario Outline: Message in Trash only is permanently deleted
Given the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Trash":

View File

@ -1,3 +1,3 @@
This is version of libqcocoa obtained from original Qt 5.13.0 source available at https://github.com/qt/qtbase with patch from cocoa.patch applyed.
This is version of libqcocoa obtained from original Qt 5.13.0 source available at https://github.com/qt/qtbase with patch from cocoa.patch applied.
Please refer to https://bugreports.qt.io/browse/QTBUG-88600 for patch origin and https://doc.qt.io/qt-5/macos-building.html for instructions how to build Qt from source.

View File

@ -31,7 +31,7 @@ echo "0">$ERROR_COUNT_FILE
# -- Helper functions -- #
##########################
# err print out a given error ($2) and line where it hapens ($1),
# err print out a given error ($2) and line where it happens ($1),
# also it increases count of errors.
err () {
echo "CHANGELOG-LINTER: $2 on the following line:"
@ -92,7 +92,7 @@ check_change_types () {
case "$1" in
"### Added"|"### Changed"|"### Deprecated"|"### Removed"|"### Fixed"|"### Security") ;; # Standard keepachangelog.com compliant types.
"### Release notes"|"### Fixed bugs") ;; # Bridge aditional in app release notes types.
"### Release notes"|"### Fixed bugs") ;; # Bridge additional in app release notes types.
"### Guiding Principles"|"### Types of changes") ;; # Ignoring guide at the end of the changelog.
*) err "$1" "Change type must be one of the Added, Changed, Deprecated, Removed, Fixed, Hoftix"
esac

View File

@ -45,7 +45,7 @@ generate_dep_licenses(){
grep -E $'^\t[^=>]*$' $src | sed -r 's/\t([^ ]*) v.*/\1/g' > "$tmpDepLicenses"
grep -E $'^\t.*=>.*v.*$' $src | sed -r 's/^.*=> ([^ ]*)( v.*)?/\1/g' >> "$tmpDepLicenses"
# Replace each line with formated link
# Replace each line with formatted link
sed -i -r '/^github.com\/therecipe\/qt\/internal\/binding\/files\/docs\//d;' "$tmpDepLicenses"
sed -i -r 's|^(.*)/([[:alnum:]-]+)/(v[[:digit:]]+)$|* [\2](https://\1/\2/\3)|g' "$tmpDepLicenses"
sed -i -r 's|^(.*)/([[:alnum:]-]+)$|* [\2](https://\1/\2)|g' "$tmpDepLicenses"

View File

@ -35,14 +35,16 @@ if ! which pandoc; then
fi
# Check Pandoc version
PANDOC_VERSION=`pandoc --version | grep --color=never -m 1 "pandoc" | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p'`
PANDOC_VERSION=$(pandoc --version | grep --color=never -m 1 "pandoc" | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')
printf "PANDOC FOUND ! version : %s\n", "$PANDOC_VERSION"
# self-contained is deprecated since 2.19 in profit of --embed-resource option
DEPRECATING_VERSION="2.19.0"
# Build release notes
if [ "$(printf '%s\n' "$requiredver" "$PANDOC_VERSION" | sort -V | head -n1)" = "$DEPRECATING_VERSION" ]; then
function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); }
if [ $(ver $PANDOC_VERSION) -lt $(ver $DEPRECATING_VERSION) ]; then
pandoc "$INFILE" -f markdown -t html -s -o "$OUTFILE" -c utils/release_notes.css --self-contained --section-divs --metadata title="Release notes - Proton Mail Bridge - $CHANNEL"
else
pandoc "$INFILE" -f markdown -t html -s -o "$OUTFILE" -c utils/release_notes.css --embed-resource --standalone --section-divs --metadata title="Release notes - Proton Mail Bridge - $CHANNEL"
fi

View File

@ -20,10 +20,10 @@
# The Qt libs are dynamically loaded with rules like: `@rpath/QtGui.framework/Versions/5/QtGui`
# @rpath instructs the dynamic linker to search a list of paths in order to locate the framework
# The rules can be listed using `otool -l "${path_to_binary}"`
# The building process of therecipe/qt or qmake leaves the rules with additinal unwanted paths
# The building process of therecipe/qt or qmake leaves the rules with additional unwanted paths
# + absolute path to build directory
# + dummy replacement `/break_the_rpath`
# We need to manually remove those and add the path relative to exectuable: `@executable_path/../Frameworks`
# We need to manually remove those and add the path relative to executable: `@executable_path/../Frameworks`
path_to_binary=$1