Page MenuHome GnuPG

No OneTemporary

diff --git a/client/reencrypt/reencryptjob.cpp b/client/reencrypt/reencryptjob.cpp
index fa7a8af..875498b 100644
--- a/client/reencrypt/reencryptjob.cpp
+++ b/client/reencrypt/reencryptjob.cpp
@@ -1,419 +1,430 @@
// SPDX-FileCopyrightText: 2025 g10 code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "reencryptjob.h"
#include "choosekeydialog.h"
#include "ews/ewscopyitemrequest.h"
#include "ews/ewscreatefolderrequest.h"
#include "ews/ewscreateitemrequest.h"
#include "ews/ewsfinditemrequest.h"
#include "ews/ewsgetfoldercontentrequest.h"
#include "ews/ewsgetfolderrequest.h"
#include "ews/ewsgetitemrequest.h"
#include "ews/ewsitem.h"
#include "ews/ewsupdateitemrequest.h"
#include <QGpgME/DecryptJob>
#include <QGpgME/EncryptJob>
#include <QGpgME/Protocol>
#include <gpgme++/context.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/encryptionresult.h>
#include <QFile>
#include <QLabel>
#include <QListView>
#include <QLocale>
#include <QPlainTextEdit>
#include <QSaveFile>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTimer>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMime/Message>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
ReencryptJob::ReencryptJob(WebsocketClient *websocketClient, const EwsId &folderId, const EwsClient &client)
: KJob()
, m_itemModel(new QStandardItemModel(this))
, m_websocketClient(websocketClient)
, m_folderId(folderId)
, m_ewsClient(client)
{
}
void ReencryptJob::tryRaiseDialog()
{
- if (m_dialog) {
- m_dialog->show();
- m_dialog->activateWindow();
- m_dialog->raise();
+ if (m_progress_dialog) {
+ m_progress_dialog->show();
+ m_progress_dialog->activateWindow();
+ m_progress_dialog->raise();
}
}
+bool ReencryptJob::hasStarted() const
+{
+ return m_progress_dialog;
+}
+
void ReencryptJob::start()
{
auto request = new EwsGetFolderRequest(m_ewsClient, this);
request->setFolderIds({m_folderId});
connect(request, &EwsGetFolderRequest::finished, this, [this, request](KJob *) {
if (request->error() != KJob::NoError) {
setError(request->error());
setErrorText(request->errorText());
emitResult();
return;
}
const auto &responses = request->responses();
if (responses.isEmpty()) {
setError(KJob::UserDefinedError);
setErrorText(u"Empty response from EwsGetFolderRequest"_s);
emitResult();
return;
}
m_folder = responses.at(0).folder();
- m_dialog = new ChooseKeyDialog(m_folder);
- m_dialog->setAttribute(Qt::WA_DeleteOnClose);
- connect(m_dialog.data(), &QDialog::accepted, this, [this, chooseKeyDialog = m_dialog.data()] {
+ auto chooseKeyDialog = new ChooseKeyDialog(m_folder);
+ chooseKeyDialog->show();
+ chooseKeyDialog->activateWindow();
+ chooseKeyDialog->raise();
+ chooseKeyDialog->setAttribute(Qt::WA_DeleteOnClose);
+ // must also go, if we get cancelled
+ connect(this, &QObject::destroyed, chooseKeyDialog, &QObject::deleteLater);
+ connect(chooseKeyDialog, &QDialog::accepted, this, [this, chooseKeyDialog] {
m_currentKeys = chooseKeyDialog->currentKeys();
m_unencryptedMode = chooseKeyDialog->unencryptedMode();
- auto progressDialog = new QDialog;
- auto layout = new QVBoxLayout(progressDialog);
+ m_progress_dialog = new QDialog();
+ auto layout = new QVBoxLayout(m_progress_dialog);
m_title = new QLabel(i18n("Creating folder"));
layout->addWidget(m_title);
auto listView = new QListView;
listView->setModel(m_itemModel);
listView->setItemDelegate(new QStyledItemDelegate);
layout->addWidget(listView);
auto debugLogLabel = new QLabel(i18nc("@label", "Debug logs:"));
layout->addWidget(debugLogLabel);
m_debugLog = new QPlainTextEdit;
layout->addWidget(m_debugLog);
- progressDialog->show();
- progressDialog->setAttribute(Qt::WA_DeleteOnClose);
- connect(progressDialog, &QDialog::destroyed, this, [this]() {
- if (this->m_finished) {
- this->deleteLater();
- return;
+ // sync job lifetime with lifetime of the progress dialog from here on
+ setAutoDelete(false);
+ connect(m_progress_dialog.data(), &QDialog::destroyed, this, &QObject::deleteLater);
+ connect(this, &ReencryptJob::result, this, [this]() {
+ if (!m_progress_dialog->isVisible()) {
+ m_progress_dialog->deleteLater();
+ } else {
+ m_progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
}
- this->setAutoDelete(true);
});
createNewFolder();
+ tryRaiseDialog();
});
- connect(m_dialog.data(), &QDialog::rejected, this, [this] {
+ connect(chooseKeyDialog, &QDialog::rejected, this, [this] {
emitResult();
});
- tryRaiseDialog();
});
request->start();
}
void ReencryptJob::createNewFolder(int index)
{
EwsFolder newFolder;
newFolder.setType(EwsFolderTypeMail);
newFolder.setField(
EwsFolderFieldDisplayName,
u"%1 - reencrypted%2"_s.arg(m_folder[EwsItemFields::EwsFolderFieldDisplayName].toString(), index == 0 ? QString{} : u" %1"_s.arg(index)));
auto request = new EwsCreateFolderRequest(m_ewsClient, this);
request->setFolders({newFolder});
if (m_folder.hasField(EwsItemFieldParentFolderId)) {
request->setParentFolderId(m_folder[EwsItemFieldParentFolderId].value<EwsId>());
} else {
request->setParentFolderId(EwsId{EwsDIdMsgFolderRoot});
}
connect(request, &EwsCreateFolderRequest::finished, this, [this, index, request](KJob *) {
if (request->error() != KJob::NoError) {
setError(request->error());
setErrorText(request->errorText());
qWarning() << "folder not created" << request->errorText();
emitResult();
return;
}
if (request->responses().at(0).folderId().id().isNull()) {
createNewFolder(index + 1);
return;
}
m_newFolderId = request->responses().at(0).folderId();
m_getFolderContentRequest = new EwsGetFolderContentRequest(m_folderId, m_ewsClient);
m_getFolderContentRequest->setAutoDelete(false);
connect(m_getFolderContentRequest, &EwsGetFolderContentRequest::fetchedItems, this, [this](const QList<EwsItem> &items) {
for (const auto &item : items) {
m_itemsToReencrypt.enqueue(item);
}
m_title->setText(i18nc("@title:dialog",
"Fetched %1 and processed %2 from %3 emails",
m_getFolderContentRequest->offset(),
m_countReencrypted,
m_getFolderContentRequest->totalItems()));
reencrypt();
});
connect(m_getFolderContentRequest, &EwsGetFolderRequest::result, m_getFolderContentRequest, [this](KJob *) {
m_finished = true;
reencrypt();
});
m_getFolderContentRequest->start();
});
request->start();
}
template<typename T>
const T *findHeader(KMime::Content *content)
{
auto header = content->header<T>();
if (header || !content->parent()) {
return header;
}
return findHeader<T>(content->parent());
}
void ReencryptJob::reencrypt()
{
if (m_reencrypting) {
return;
}
if (m_itemsToReencrypt.isEmpty()) {
if (m_finished) {
m_getFolderContentRequest->deleteLater();
emitResult();
}
return;
}
m_countReencrypted++;
m_title->setText(i18nc("@title:dialog",
"Fetched %1 and processed %2 from %3 emails",
m_getFolderContentRequest->offset(),
m_countReencrypted,
m_getFolderContentRequest->totalItems()));
const auto item = m_itemsToReencrypt.dequeue();
m_reencrypting = true;
const auto mimeContent = item[EwsItemFieldMimeContent].toString().toUtf8();
if (mimeContent.isEmpty()) {
m_reencrypting = false;
reencrypt();
return;
}
const auto mailData = KMime::CRLFtoLF(mimeContent);
const auto msg = KMime::Message::Ptr(new KMime::Message);
msg->setContent(mailData);
msg->parse();
auto subJobs = std::make_shared<ReencryptSubJobs>();
auto subject = findHeader<KMime::Headers::Subject>(msg.get());
subJobs->modelItem = new QStandardItem(subject ? subject->asUnicodeString() : u"no subject"_s);
subJobs->modelItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
subJobs->message = msg;
subJobs->item = item;
m_itemModel->appendRow(subJobs->modelItem);
parseParts(item, msg.get(), subJobs);
if (!subJobs->isEncrypted()) {
saveCopy(item[EwsItemFieldItemId].value<EwsId>(), subJobs->modelItem);
} else {
QTimer::singleShot(10s, [this, subJobs]() {
// Check if reencryption was finished
if (!subJobs->encryptJobs.isEmpty()) {
subJobs->modelItem->setIcon(QIcon::fromTheme(u"data-error"_s));
subJobs->modelItem->setToolTip(i18n("Decryption timeout"));
m_debugLog->appendPlainText(i18n("Unable to reencrypt \"%1\". Timing out in GPG. Treating as unencrypted", subJobs->modelItem->text()));
saveCopy(subJobs->item[EwsItemFieldItemId].value<EwsId>(), subJobs->modelItem);
}
});
}
}
void ReencryptJob::saveCopy(const EwsId &itemId, QStandardItem *index)
{
if (m_unencryptedMode == UnencryptedMode::Copy) {
auto copyItemRequest = new EwsCopyItemRequest(m_ewsClient, this);
copyItemRequest->setItemIds({itemId});
copyItemRequest->setDestinationFolderId(m_newFolderId);
connect(copyItemRequest, &EwsCopyItemRequest::finished, this, [this, index, copyItemRequest]() {
m_reencrypting = false;
reencrypt();
if (copyItemRequest->error() != KJob::NoError) {
setError(KJob::UserDefinedError);
setErrorText(errorText() + copyItemRequest->errorString() + u'\n');
index->setIcon(QIcon::fromTheme(u"data-error"_s));
m_debugLog->appendPlainText(copyItemRequest->errorString());
return;
}
if (index->icon().name() != "data-error"_L1) {
index->setIcon(QIcon::fromTheme(u"data-success"_s));
}
});
copyItemRequest->start();
} else {
if (index->icon().name() != "data-error"_L1) {
index->setIcon(QIcon::fromTheme(u"data-information"_s));
index->setToolTip(i18nc("Skipped processing email", "Skipped processing email"));
}
m_reencrypting = false;
reencrypt();
}
}
void ReencryptJob::parseParts(const EwsItem &item, KMime::Content *content, std::shared_ptr<ReencryptSubJobs> subJobs)
{
if (const auto contentType = content->contentType(); contentType) {
const auto mimeType = contentType->mimeType();
if (mimeType == "application/pgp-encrypted") {
const auto parent = content->parent();
if (!parent) {
return;
}
const auto siblings = parent->contents();
for (const auto sibling : siblings) {
if (const auto contentType = sibling->contentType(); contentType) {
const auto mimeType = contentType->mimeType();
if (mimeType == "application/octet-stream") {
const auto encryptedContent = sibling->decodedContent();
auto job = QGpgME::openpgp()->encryptJob(true, true);
#ifdef GPGME2
job->setEncryptionFlags(GpgME::Context::AddRecipient);
#endif
job->setInputEncoding(GpgME::Data::ArmorEncoding);
subJobs->modelItem->setIcon(QIcon::fromTheme(u"view-refresh-symbolic"_s));
qWarning() << "Started reencrypting" << subJobs->modelItem->text() << "size" << encryptedContent.size();
connect(job,
&QGpgME::EncryptJob::result,
this,
[this, sibling, job, subJobs](const GpgME::EncryptionResult &result,
const QByteArray &cipherText,
const QString &,
const GpgME::Error &) {
subJobs->encryptJobs.removeAll(job);
if (result.error()) {
// TODO: If we ever want to process things we can't decrypt other than skip item
// code is needed here somehow to deal with it
qWarning() << "Finished reencrypting" << subJobs->modelItem->text() << "with error"
<< Kleo::Formatting::errorAsString(result.error());
setError(KJob::UserDefinedError);
setErrorText(errorText() + Kleo::Formatting::errorAsString(result.error()) + u'\n');
subJobs->modelItem->setIcon(QIcon::fromTheme(u"data-error"_s));
subJobs->modelItem->setToolTip(Kleo::Formatting::errorAsString(result.error()));
m_debugLog->appendPlainText(Kleo::Formatting::errorAsString(result.error()));
if (subJobs->encryptJobs.isEmpty()) {
m_reencrypting = false;
reencrypt();
}
return;
}
qWarning() << "Finished reencrypting" << subJobs->modelItem->text();
sibling->setBody(cipherText);
if (subJobs->encryptJobs.isEmpty()) {
subJobs->message->assemble();
const auto newMessage = subJobs->message->encodedContent();
saveAsNew(subJobs->item[EwsItemFieldItemId].value<EwsId>(), newMessage, subJobs->modelItem);
}
});
auto error = job->start(m_currentKeys, encryptedContent);
if (error) {
qWarning() << "Coult not start reencrypting" << subJobs->modelItem->text() << "with error"
<< Kleo::Formatting::errorAsString(error);
setError(KJob::UserDefinedError);
setErrorText(errorText() + Kleo::Formatting::errorAsString(error) + u'\n');
subJobs->modelItem->setIcon(QIcon::fromTheme(u"data-error"_s));
m_debugLog->appendPlainText(Kleo::Formatting::errorAsString(error));
continue;
}
subJobs->modelItem->setIcon(QIcon::fromTheme(u"cloud-upload-symbolic"_s));
subJobs->encryptJobs.append(job);
}
}
}
}
}
for (const auto &content : content->contents()) {
parseParts(item, content, subJobs);
}
}
void ReencryptJob::saveAsNew(const EwsId &itemId, const QByteArray &copy, QStandardItem *index)
{
auto copyItemRequest = new EwsCopyItemRequest(m_ewsClient, this);
copyItemRequest->setItemIds({itemId});
copyItemRequest->setDestinationFolderId(m_newFolderId);
connect(copyItemRequest, &EwsCopyItemRequest::finished, this, [this, copy, index, copyItemRequest]() {
if (copyItemRequest->error() != KJob::NoError) {
setError(KJob::UserDefinedError);
setErrorText(errorText() + copyItemRequest->errorString() + u'\n');
index->setIcon(QIcon::fromTheme(u"data-error"_s));
m_debugLog->appendPlainText(copyItemRequest->errorString());
m_reencrypting = false;
reencrypt();
return;
}
auto updateItemRequest = new EwsUpdateItemRequest(m_ewsClient, this);
EwsUpdateItemRequest::ItemChange itemChange(copyItemRequest->responses().at(0).itemId(), EwsItemTypeMessage);
EwsPropertyField field(u"item:MimeContent"_s);
itemChange.addUpdate(new EwsUpdateItemRequest::SetUpdate(field, QString::fromUtf8(copy.toBase64())));
updateItemRequest->addItemChange(itemChange);
updateItemRequest->setSavedFolderId(m_folderId);
updateItemRequest->setConflictResolution(EwsConflictResolution::EwsResolAlwaysOverwrite);
connect(updateItemRequest, &EwsUpdateItemRequest::finished, this, [this, index](KJob *job) {
if (job->error()) {
setError(KJob::UserDefinedError);
setErrorText(errorText() + job->errorText() + u'\n');
index->setIcon(QIcon::fromTheme(u"data-error"_s));
index->setText(index->text() + u' ' + job->errorText());
m_debugLog->appendPlainText(job->errorText());
m_reencrypting = false;
reencrypt();
emitResult();
return;
}
index->setIcon(QIcon::fromTheme(u"data-success"_s));
m_reencrypting = false;
reencrypt();
});
updateItemRequest->start();
});
copyItemRequest->start();
}
diff --git a/client/reencrypt/reencryptjob.h b/client/reencrypt/reencryptjob.h
index d1a77b8..71d78e5 100644
--- a/client/reencrypt/reencryptjob.h
+++ b/client/reencrypt/reencryptjob.h
@@ -1,82 +1,83 @@
// SPDX-FileCopyrightText: 2025 g10 code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "choosekeydialog.h"
#include "ews/ewsclient.h"
#include "ews/ewsfolder.h"
#include "ews/ewsid.h"
#include "ews/ewsitem.h"
#include <KJob>
#include <KMime/Message>
#include <QGpgME/EncryptJob>
#include <QLabel>
#include <QPointer>
#include <QQueue>
#include <QUrl>
#include <QVariant>
class QPlainTextEdit;
class QStandardItemModel;
class QStandardItem;
class WebsocketClient;
class EwsGetFolderContentRequest;
class ChooseKeyDialog;
namespace QGpgME
{
class EncryptJob;
}
struct ReencryptSubJobs {
EwsItem item;
QList<QGpgME::EncryptJob *> encryptJobs;
QStandardItem *modelItem;
KMime::Message::Ptr message;
bool isEncrypted() const
{
return !encryptJobs.isEmpty();
}
};
class ReencryptJob : public KJob
{
public:
explicit ReencryptJob(WebsocketClient *websocketClient, const EwsId &folderId, const EwsClient &client);
void start() override;
+ bool hasStarted() const;
void reencrypt();
void tryRaiseDialog();
private:
void createNewFolder(int index = 0);
void parseParts(const EwsItem &item, KMime::Content *content, std::shared_ptr<ReencryptSubJobs> subJobs);
void encryptionFinished(const EwsItem &item, QGpgME::EncryptJob *job, const GpgME::EncryptionResult &result, const QByteArray &cipherText);
void saveAsNew(const EwsId &itemId, const QByteArray &mimeContent, QStandardItem *index);
void saveCopy(const EwsId &item, QStandardItem *index);
std::vector<GpgME::Key> m_currentKeys;
UnencryptedMode m_unencryptedMode = UnencryptedMode::Skip;
QUrl m_backupFolder;
QList<QGpgME::EncryptJob *> m_encryptJobs;
QStandardItemModel *const m_itemModel;
QLabel *m_title;
QPlainTextEdit *m_debugLog;
WebsocketClient *m_websocketClient;
EwsId m_folderId;
EwsId m_newFolderId;
EwsFolder m_folder;
EwsClient m_ewsClient;
QQueue<EwsItem> m_itemsToReencrypt;
EwsGetFolderContentRequest *m_getFolderContentRequest = nullptr;
bool m_reencrypting = false;
int m_countReencrypted = 0;
bool m_finished = false;
- QPointer<ChooseKeyDialog> m_dialog;
+ QPointer<QDialog> m_progress_dialog;
};
diff --git a/client/websocketclient.cpp b/client/websocketclient.cpp
index 1b46052..17a5ae0 100644
--- a/client/websocketclient.cpp
+++ b/client/websocketclient.cpp
@@ -1,465 +1,469 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "websocketclient.h"
// Qt headers
#include <QFile>
#include <QHostInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStandardPaths>
#include <QTimer>
#include <QUuid>
// KDE headers
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMime/Message>
#include <KSharedConfig>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <MimeTreeParserCore/ObjectTreeParser>
// gpgme headers
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <gpgme++/global.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include "config.h"
#include "draft/draftmanager.h"
#include "editor/composerwindow.h"
#include "editor/composerwindowfactory.h"
#include "emailviewer.h"
#include "ews/ewsclient.h"
#include "ews/ewsgetitemrequest.h"
#include "ews/ewsitemshape.h"
#include "gpgol_client_debug.h"
#include "gpgolweb_version.h"
#include "protocol.h"
#include "reencrypt/reencryptjob.h"
#include "websocket_debug.h"
using namespace Qt::Literals::StringLiterals;
WebsocketClient &WebsocketClient::self(const QUrl &url, const QString &clientId)
{
static WebsocketClient *client = nullptr;
if (!client && url.isEmpty()) {
qFatal() << "Unable to create a client without an url";
} else if (!client) {
client = new WebsocketClient(url, clientId);
}
return *client;
};
WebsocketClient::WebsocketClient(const QUrl &url, const QString &clientId)
: QObject(nullptr)
, m_webSocket(QWebSocket(QStringLiteral("Client")))
, m_url(url)
, m_clientId(clientId)
, m_state(NotConnected)
, m_stateDisplay(i18nc("@info", "Loading..."))
{
auto job = QGpgME::openpgp()->keyListJob();
connect(job, &QGpgME::KeyListJob::result, this, &WebsocketClient::slotKeyListingDone);
job->start({}, true);
qCWarning(GPGOL_CLIENT_LOG) << "Found the following trusted emails" << m_emails;
connect(&m_webSocket, &QWebSocket::connected, this, &WebsocketClient::slotConnected);
connect(&m_webSocket, &QWebSocket::disconnected, this, [this] {
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Connection to outlook lost due to a disconnection with the broker.");
Q_EMIT stateChanged(m_stateDisplay);
});
connect(&m_webSocket, &QWebSocket::errorOccurred, this, &WebsocketClient::slotErrorOccurred);
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &WebsocketClient::slotTextMessageReceived);
connect(&m_webSocket, QOverload<const QList<QSslError> &>::of(&QWebSocket::sslErrors), this, [this](const QList<QSslError> &errors) {
// TODO remove
m_webSocket.ignoreSslErrors(errors);
});
QSslConfiguration sslConfiguration;
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
Q_ASSERT(!certPath.isEmpty());
QFile certFile(certPath);
if (!certFile.open(QIODevice::ReadOnly)) {
qFatal() << "Couldn't read certificate" << certPath;
}
QSslCertificate certificate(&certFile, QSsl::Pem);
certFile.close();
sslConfiguration.addCaCertificate(certificate);
m_webSocket.setSslConfiguration(sslConfiguration);
m_webSocket.open(url);
}
void WebsocketClient::slotKeyListingDone(const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error &error)
{
Q_UNUSED(result);
Q_UNUSED(error);
if (error) {
m_stateDisplay = i18nc("@info", "Connection to the Outlook extension lost. Make sure the extension pane is open.");
Q_EMIT stateChanged(m_stateDisplay);
return;
}
QStringList oldEmails = m_emails;
for (const auto &key : keys) {
for (const auto &userId : key.userIDs()) {
const auto email = QString::fromLatin1(userId.email()).toLower();
if (!m_emails.contains(email)) {
m_emails << email;
}
}
}
if (m_emails == oldEmails) {
return;
}
qCWarning(GPGOL_CLIENT_LOG) << "Found the following trusted emails" << m_emails;
if (m_webSocket.state() == QAbstractSocket::ConnectedState) {
slotConnected();
}
}
void WebsocketClient::slotConnected()
{
qCInfo(WEBSOCKET_LOG) << "websocket connected";
// clang-format off
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Register)},
{"arguments"_L1, QJsonObject{
{"emails"_L1, QJsonArray::fromStringList(m_emails)},
{"type"_L1, "native"_L1},
{"id"_L1, getId() },
{"name"_L1, QString(QHostInfo::localHostName() + u" - GpgOL/Web ("_s + QStringLiteral(GPGOLWEB_VERSION_STRING) + u')') },
}},
});
// clang-format on
m_webSocket.sendTextMessage(QString::fromUtf8(doc.toJson()));
m_state = NotConnected; /// We still need to connect to the web client
m_stateDisplay = i18nc("@info", "Waiting for web client.");
Q_EMIT stateChanged(m_stateDisplay);
}
void WebsocketClient::slotErrorOccurred(QAbstractSocket::SocketError error)
{
qCWarning(WEBSOCKET_LOG) << error << m_webSocket.errorString();
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Could not reach the Outlook extension.");
Q_EMIT stateChanged(m_stateDisplay);
reconnect();
}
bool WebsocketClient::sendEWSRequest(const QString &fromEmail, const QString &requestId, const QString &requestBody)
{
KMime::Types::Mailbox mailbox;
mailbox.fromUnicodeString(fromEmail);
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::Ews)},
{"arguments"_L1,
QJsonObject{
{"body"_L1, requestBody},
{"email"_L1, QString::fromUtf8(mailbox.address())},
{"requestId"_L1, requestId},
}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
return true;
}
void WebsocketClient::slotTextMessageReceived(QString message)
{
const auto doc = QJsonDocument::fromJson(message.toUtf8());
if (!doc.isObject()) {
qCWarning(WEBSOCKET_LOG) << "invalid text message received" << message;
return;
}
const auto object = doc.object();
const auto command = Protocol::commandFromString(object["command"_L1].toString());
const auto args = object["arguments"_L1].toObject();
switch (command) {
case Protocol::Disconnection:
// disconnection of the web client
m_state = NotConnected;
m_stateDisplay = i18nc("@info", "Connection to the Outlook extension lost. Make sure the extension pane is open.");
Q_EMIT stateChanged(m_stateDisplay);
return;
case Protocol::Connection:
// reconnection of the web client
m_state = Connected;
m_stateDisplay = i18nc("@info", "Connected.");
Q_EMIT stateChanged(m_stateDisplay);
return;
case Protocol::View: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const EwsId id(args["itemId"_L1].toString());
const auto content = m_cachedMime[id];
if (content.isEmpty()) {
return;
}
const auto ewsAccessToken = args["ewsAccessToken"_L1].toString().toUtf8();
if (!m_emailViewer) {
m_emailViewer = new EmailViewer(content, email, displayName, ewsAccessToken);
m_emailViewer->setAttribute(Qt::WA_DeleteOnClose);
} else {
m_emailViewer->view(content, email, displayName, ewsAccessToken);
}
m_emailViewer->show();
m_emailViewer->activateWindow();
m_emailViewer->raise();
return;
}
case Protocol::RestoreAutosave: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto ewsAccessToken = args["ewsAccessToken"_L1].toString().toUtf8();
ComposerWindowFactory::self().restoreAutosave(email, displayName, ewsAccessToken);
return;
}
case Protocol::EwsResponse: {
// confirmation that the email was sent
const auto args = object["arguments"_L1].toObject();
Q_EMIT ewsResponseReceived(args["requestId"_L1].toString(), args["body"_L1].toString());
return;
}
case Protocol::Composer:
case Protocol::Reply:
case Protocol::Forward:
case Protocol::OpenDraft: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto ewsAccessToken = args["ewsAccessToken"_L1].toString().toUtf8();
auto dialog = ComposerWindowFactory::self().create(email, displayName, ewsAccessToken);
if (command == Protocol::Reply || command == Protocol::Forward) {
const EwsId id(args["itemId"_L1].toString());
const auto content = m_cachedMime[id];
if (content.isEmpty()) {
return;
}
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(content.toUtf8()));
message->parse();
if (command == Protocol::Reply) {
dialog->reply(message);
} else {
dialog->forward(message);
}
} else if (command == Protocol::OpenDraft) {
const auto draftId = args["id"_L1].toString();
if (draftId.isEmpty()) {
return;
}
const auto draft = DraftManager::self().draftById(draftId.toUtf8());
dialog->setMessage(draft.mime());
}
dialog->show();
dialog->activateWindow();
dialog->raise();
return;
}
case Protocol::DeleteDraft: {
const auto draftId = args["id"_L1].toString();
if (draftId.isEmpty()) {
qWarning() << "Draft not valid";
return;
}
const auto draft = DraftManager::self().draftById(draftId.toUtf8());
if (!draft.isValid()) {
qWarning() << "Draft not valid";
return;
}
if (!DraftManager::self().remove(draft)) {
qCWarning(GPGOL_CLIENT_LOG) << "Could not delete draft";
return;
}
return;
}
case Protocol::Reencrypt: {
reencrypt(args);
return;
}
case Protocol::Info: {
info(args);
return;
}
default:
qCWarning(WEBSOCKET_LOG) << "Unhandled command" << command;
}
}
void WebsocketClient::reencrypt(const QJsonObject &args)
{
const EwsId folderId(args["folderId"_L1].toString());
EwsClient client(args["email"_L1].toString(), args["ewsAccessToken"_L1].toString().toUtf8());
if (m_reencryptJob) {
- m_reencryptJob->tryRaiseDialog();
- } else {
- m_reencryptJob = new ReencryptJob(this, folderId, client);
- m_reencryptJob->start();
+ if (m_reencryptJob->hasStarted()) {
+ m_reencryptJob->tryRaiseDialog();
+ return;
+ }
+ m_reencryptJob->deleteLater();
}
+
+ m_reencryptJob = new ReencryptJob(this, folderId, client);
+ m_reencryptJob->start();
}
void WebsocketClient::reconnect()
{
QTimer::singleShot(1000ms, this, [this]() {
m_webSocket.open(m_url);
});
}
WebsocketClient::State WebsocketClient::state() const
{
return m_state;
}
QString WebsocketClient::stateDisplay() const
{
return m_stateDisplay;
}
void WebsocketClient::sendViewerClosed(const QString &email)
{
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::ViewerClosed)},
{"arguments"_L1, QJsonObject{{"email"_L1, email}}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
}
void WebsocketClient::sendViewerOpened(const QString &email)
{
const QJsonObject json{
{"command"_L1, Protocol::commandToString(Protocol::ViewerOpened)},
{"arguments"_L1, QJsonObject{{"email"_L1, email}}},
};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
}
void WebsocketClient::info(const QJsonObject &args)
{
const EwsId id(args["itemId"_L1].toString());
if (m_cachedInfo.contains(id)) {
auto info = QJsonDocument::fromJson(m_cachedInfo[id].toUtf8()).object();
QJsonArray features;
if (Config::self()->reencrypt()) {
features << u"reencrypt"_s;
}
auto arguments = info["arguments"_L1].toObject();
arguments["features"_L1] = features;
arguments["viewerOpen"_L1] = !m_emailViewer.isNull();
info["arguments"_L1] = arguments;
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(info).toJson()));
return;
}
EwsClient client(args["email"_L1].toString(), args["ewsAccessToken"_L1].toString().toUtf8());
EwsItemShape itemShape(EwsShapeIdOnly);
itemShape.setFlags(EwsItemShape::IncludeMimeContent);
itemShape << EwsPropertyField(u"item:ParentFolderId"_s);
auto request = new EwsGetItemRequest(client, this);
request->setItemIds({id});
request->setItemShape(itemShape);
qCWarning(GPGOL_CLIENT_LOG) << "Info for" << id << "requested";
connect(request, &EwsGetItemRequest::finished, this, [this, id, args, request]() {
if (request->error() != KJob::NoError) {
const QJsonObject json{{"command"_L1, Protocol::commandToString(Protocol::Error)},
{"arguments"_L1,
QJsonObject{
{"error"_L1, request->errorString()},
{"email"_L1, args["email"_L1]},
}}};
const auto result = QString::fromUtf8(QJsonDocument(json).toJson());
m_webSocket.sendTextMessage(result);
return;
}
qCWarning(GPGOL_CLIENT_LOG) << "Info for" << id << "fetched";
const auto responses = request->responses();
if (responses.isEmpty()) {
return;
}
const auto item = responses.at(0).item();
const auto mimeContent = item[EwsItemFieldMimeContent].toString();
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(mimeContent.toUtf8()));
message->parse();
MimeTreeParser::ObjectTreeParser treeParser;
treeParser.parseObjectTree(message.get());
QJsonArray features;
if (Config::self()->reencrypt()) {
features << u"reencrypt"_s;
}
const QJsonObject json{{"command"_L1, Protocol::commandToString(Protocol::InfoFetched)},
{"arguments"_L1,
QJsonObject{
{"itemId"_L1, args["itemId"_L1]},
{"folderId"_L1, item[EwsItemFieldParentFolderId].value<EwsId>().id()},
{"email"_L1, args["email"_L1]},
{"encrypted"_L1, treeParser.hasEncryptedParts()},
{"signed"_L1, treeParser.hasSignedParts()},
{"drafts"_L1, DraftManager::self().toJson()},
{"viewerOpen"_L1, !m_emailViewer.isNull()},
{"version"_L1, QStringLiteral(GPGOLWEB_VERSION_STRING)},
{"features"_L1, features},
}}};
const auto result = QString::fromUtf8(QJsonDocument(json).toJson());
m_cachedInfo[id] = result;
m_cachedMime[id] = mimeContent;
m_webSocket.sendTextMessage(result);
});
request->start();
}
QString WebsocketClient::getId() const
{
auto config = KSharedConfig::openStateConfig();
auto machineGroup = config->group(u"Machine"_s);
if (machineGroup.exists() && machineGroup.hasKey(u"Id"_s)) {
return machineGroup.readEntry(u"Id"_s);
}
const auto id = QUuid::createUuid().toString(QUuid::WithoutBraces);
machineGroup.writeEntry("Id", id);
config->sync();
return id;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 17, 2:26 AM (10 h, 40 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9b/60/afd9bad4137b8861a2c93f7ab2c6

Event Timeline