Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34572373
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
37 KB
Subscribers
None
View Options
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 ©, 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
Details
Attached
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
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment