Page MenuHome GnuPG

No OneTemporary

diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 8075659..e71f3d5 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -1,391 +1,392 @@
# SPDX-FileCopyrightText: 2023 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DDATAROUTDIR="${KDE_INSTALL_FULL_DATAROOTDIR}")
add_definitions(-DTRANSLATION_DOMAIN=\"gpgol-js-native\")
add_subdirectory(rootcagenerator)
add_library(gpgol-client-static STATIC)
target_sources(gpgol-client-static PRIVATE
emailviewer.cpp
emailviewer.h
websocketclient.cpp
websocketclient.h
messagedispatcher.cpp
messagedispatcher.h
firsttimedialog.cpp
firsttimedialog.h
mailapi.cpp
securitylevelinfo.cpp
+ importablekeyswidget.cpp
# Identity
identity/addressvalidationjob.cpp
identity/addressvalidationjob.h
identity/identitymanager.cpp
identity/identitymanager.h
identity/identitydialog.cpp
identity/identitydialog.h
identity/identity.cpp
identity/identity.h
identity/signature.h
identity/signature.cpp
identity/signatureconfigurator.cpp
identity/signatureconfigurator.h
identity/signaturerichtexteditor.cpp
identity/signaturerichtexteditor_p.h
# Draft
draft/draft.cpp
draft/draft.h
draft/draftmanager.cpp
draft/draftmanager.h
# EWS integration
ews/ewsattachment.cpp
ews/ewsattachment.h
ews/ewsattendee.cpp
ews/ewsattendee.h
ews/ewsclient_debug.cpp
ews/ewsclient_debug.h
ews/ewsid.cpp
ews/ewsid.h
ews/ewsitem.cpp
ews/ewsitem.h
ews/ewsitemshape.cpp
ews/ewsitemshape.h
ews/ewsitembase.cpp
ews/ewsitembase.h
ews/ewsitembase_p.h
ews/ewsmailbox.cpp
ews/ewsmailbox.h
ews/ewsmessagedispatcher.cpp
ews/ewsmessagedispatcher.h
ews/ewsoccurrence.cpp
ews/ewsoccurrence.h
ews/ewspropertyfield.cpp
ews/ewspropertyfield.h
ews/ewsrecurrence.cpp
ews/ewsrecurrence.h
ews/ewsserverversion.cpp
ews/ewsserverversion.h
ews/ewstypes.cpp
ews/ewstypes.h
ews/ewsxml.cpp
ews/ewsxml.h
ews/ewscreateitemrequest.cpp
ews/ewscreateitemrequest.h
ews/ewsgetfolderrequest.cpp
ews/ewsgetfolderrequest.h
ews/ewsupdateitemrequest.cpp
ews/ewsupdateitemrequest.h
ews/ewsrequest.cpp
ews/ewsrequest.h
ews/ewsjob.cpp
ews/ewsjob.h
ews/ewsclient.cpp
ews/ewsclient.h
ews/ewsfinditemrequest.cpp
ews/ewsfinditemrequest.h
ews/ewsgetitemrequest.cpp
ews/ewsgetitemrequest.h
ews/ewsgetfolderrequest.cpp
ews/ewsgetfolderrequest.h
ews/ewsfolder.cpp
ews/ewsfolder.h
ews/ewscreatefolderrequest.cpp
ews/ewscreatefolderrequest.h
ews/ewseffectiverights.h
ews/ewseffectiverights.cpp
ews/ewsfoldershape.cpp
ews/ewsfoldershape.h
ews/ewscopyitemrequest.cpp
ews/ewscopyitemrequest.h
ews/ewsgetfoldercontentrequest.cpp
ews/ewsgetfoldercontentrequest.h
reencrypt/reencryptjob.cpp
reencrypt/reencryptjob.h
reencrypt/choosekeydialog.cpp
reencrypt/choosekeydialog.h
reencrypt/certificatelineedit.cpp
reencrypt/certificatelineedit.h
reencrypt/reencryptprogressdialog.cpp
# Editor
editor/addresseelineedit.cpp
editor/addresseelineedit.h
editor/addresseelineeditmanager.cpp
editor/addresseelineeditmanager.h
editor/bodytexteditor.cpp
editor/bodytexteditor.h
editor/composerviewbase.cpp
editor/composerviewbase.h
editor/composerwindow.cpp
editor/composerwindow.h
editor/composerwindowfactory.cpp
editor/composerwindowfactory.h
editor/cryptostateindicatorwidget.cpp
editor/cryptostateindicatorwidget.h
editor/kmcomposerglobalaction.cpp
editor/kmcomposerglobalaction.h
editor/nearexpirywarning.cpp
editor/nearexpirywarning.h
editor/mailtemplates.cpp
editor/mailtemplates.h
editor/recipient.cpp
editor/recipient.h
editor/recipientline.cpp
editor/recipientline.h
editor/recipientseditor.cpp
editor/recipientseditor.h
editor/util.h
editor/util.cpp
editor/kmailcompletion.cpp
editor/kmailcompletion.h
editor/composersignatures.cpp
editor/composersignatures.h
editor/nodehelper.cpp
editor/nodehelper.h
editor/signaturecontroller.cpp
editor/signaturecontroller.h
editor/spellcheckerconfigdialog.cpp
editor/spellcheckerconfigdialog.h
# Editor job
editor/job/abstractencryptjob.h
editor/job/autocryptheadersjob.h
editor/job/contentjobbase.h
editor/job/contentjobbase_p.h
editor/job/composerjob.cpp
editor/job/composerjob.h
editor/job/encryptjob.h
editor/job/inserttextfilejob.h
editor/job/itipjob.h
editor/job/jobbase.h
editor/job/jobbase_p.h
editor/job/maintextjob.h
editor/job/multipartjob.h
editor/job/protectedheadersjob.h
editor/job/signencryptjob.h
editor/job/signjob.h
editor/job/singlepartjob.h
editor/job/skeletonmessagejob.h
editor/job/transparentjob.h
editor/job/autocryptheadersjob.cpp
editor/job/contentjobbase.cpp
editor/job/encryptjob.cpp
editor/job/inserttextfilejob.cpp
editor/job/itipjob.cpp
editor/job/jobbase.cpp
editor/job/maintextjob.cpp
editor/job/multipartjob.cpp
editor/job/protectedheadersjob.cpp
editor/job/saveasfilejob.cpp
editor/job/saveasfilejob.h
editor/job/signencryptjob.cpp
editor/job/signjob.cpp
editor/job/singlepartjob.cpp
editor/job/skeletonmessagejob.cpp
editor/job/transparentjob.cpp
## Editor Part
editor/part/globalpart.h
editor/part/infopart.h
editor/part/itippart.h
editor/part/messagepart.h
editor/part/textpart.h
editor/part/globalpart.cpp
editor/part/infopart.cpp
editor/part/itippart.cpp
editor/part/messagepart.cpp
editor/part/textpart.cpp
## Attachment
editor/attachment/attachmentjob.cpp
editor/attachment/attachmentjob.h
editor/attachment/attachmentclipboardjob.cpp
editor/attachment/attachmentclipboardjob.h
editor/attachment/attachmentcompressjob.cpp
editor/attachment/attachmentcompressjob.h
editor/attachment/attachmentcontroller.cpp
editor/attachment/attachmentcontroller.h
editor/attachment/attachmentcontrollerbase.cpp
editor/attachment/attachmentcontrollerbase.h
editor/attachment/attachmentfromfolderjob.cpp
editor/attachment/attachmentfromfolderjob.h
editor/attachment/attachmentfrommimecontentjob.cpp
editor/attachment/attachmentfrommimecontentjob.h
editor/attachment/attachmentfromurlbasejob.cpp
editor/attachment/attachmentfromurlbasejob.h
editor/attachment/attachmentfromurljob.cpp
editor/attachment/attachmentfromurljob.h
editor/attachment/attachmentfromurlutils.cpp
editor/attachment/attachmentfromurlutils.h
editor/attachment/attachmentfrompublickeyjob.cpp
editor/attachment/attachmentfrompublickeyjob.h
editor/attachment/attachmentloadjob.cpp
editor/attachment/attachmentloadjob.h
editor/attachment/attachmentmodel.cpp
editor/attachment/attachmentmodel.h
editor/attachment/attachmentpart.cpp
editor/attachment/attachmentpart.h
editor/attachment/attachmentpropertiesdialog.cpp
editor/attachment/attachmentpropertiesdialog.h
editor/attachment/attachmentupdatejob.cpp
editor/attachment/attachmentupdatejob.h
editor/attachment/attachmentview.cpp
editor/attachment/attachmentview.h
# Multiplyingline
multiplyingline/multiplyingline.cpp
multiplyingline/multiplyingline.h
multiplyingline/multiplyinglineeditor.cpp
multiplyingline/multiplyinglineeditor.h
multiplyingline/multiplyinglineview_p.cpp
multiplyingline/multiplyinglineview_p.h
# Utils
utils/kuniqueservice.h
utils/kuniqueservice.cpp
utils/systemtrayicon.h
utils/systemtrayicon.cpp
)
if (WIN32)
add_definitions(-DHAVE_QDBUS=false)
target_sources(gpgol-client-static PRIVATE utils/kuniqueservice_win.cpp)
else()
add_definitions(-DHAVE_QDBUS=true)
target_sources(gpgol-client-static PRIVATE utils/kuniqueservice_dbus.cpp)
endif()
ki18n_wrap_ui(gpgol-client-static
editor/attachment/ui/attachmentpropertiesdialog.ui
editor/attachment/ui/attachmentpropertiesdialog_readonly.ui
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER gpgol_client_debug.h
IDENTIFIER GPGOL_CLIENT_LOG
CATEGORY_NAME org.gpgol.client
DESCRIPTION "General client log"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER websocket_debug.h
IDENTIFIER WEBSOCKET_LOG
CATEGORY_NAME org.gpgol.client.websocket
DESCRIPTION "Websocket connection in the client"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER ewsresource_debug.h
IDENTIFIER EWSRES_LOG
CATEGORY_NAME org.gpgol.ews
DESCRIPTION "Ews mail client"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER ewscli_debug.h
IDENTIFIER EWSCLI_LOG
CATEGORY_NAME org.gpgol.ews.client
DESCRIPTION "ews client (gpgol-client)"
EXPORT GPGOL
)
ecm_qt_declare_logging_category(gpgol-client-static_SRCS
HEADER editor_debug.h
IDENTIFIER EDITOR_LOG
CATEGORY_NAME org.gpgol.editor
DESCRIPTION "mail composer"
EXPORT GPGOL
)
set(WARN_TOOMANY_RECIPIENTS_DEFAULT true)
set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true)
configure_file(editor/settings/messagecomposer.kcfg.in ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg)
kconfig_add_kcfg_files(gpgol-client-static editor/settings/messagecomposersettings.kcfgc config.kcfgc)
install(FILES composerui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgol-client)
target_sources(gpgol-client-static PUBLIC ${gpgol-client-static_SRCS})
ki18n_wrap_ui(gpgol-client-static firsttimedialog.ui)
ki18n_wrap_ui(gpgol-client-static confpagewelcome.ui)
ki18n_wrap_ui(gpgol-client-static confpageinstalladdin.ui)
ki18n_wrap_ui(gpgol-client-static confpageproxyoptions.ui)
ki18n_wrap_ui(gpgol-client-static confpagetlscertificate.ui)
ki18n_wrap_ui(gpgol-client-static reencrypt/choosekeydialog.ui)
target_link_libraries(gpgol-client-static PUBLIC
common
rootcagenerator
LibGpgError::LibGpgError
Qt6::HttpServer
Qt6::Widgets
Qt6::PrintSupport
Qt6::WebSockets
KF6::I18n
KF6::JobWidgets
KF6::CalendarCore
KF6::ConfigCore
KF6::ConfigGui
KF6::Contacts
KF6::Completion
KF6::CoreAddons
KF6::ColorScheme
KF6::Codecs
KF6::GuiAddons
KF6::SonnetUi
KF6::SonnetCore
KF6::WidgetsAddons
KF6::XmlGui
KF6::Archive
KF6::KIOWidgets
KPim6::MimeTreeParserCore
KPim6::MimeTreeParserWidgets
KPim6::Libkleo
${_gpgol_dbusaddons_libs}
)
if(Gpgmepp_VERSION VERSION_GREATER_EQUAL "2.0.0")
target_compile_definitions(gpgol-client-static PUBLIC GPGME2)
endif()
set(GPGOLWEB_ICON_DIR "${CMAKE_CURRENT_SOURCE_DIR}/icons")
file(GLOB ICONS_PNGS "${GPGOLWEB_ICON_DIR}/*gpgolweb.png")
file(GLOB ICONS_SVGS "${GPGOLWEB_ICON_DIR}/*gpgolweb.svg")
ecm_add_app_icon(_gpgol-client_SRCS ICONS ${ICONS_PNGS} ${ICONS_SVGS})
ecm_install_icons(ICONS ${ICONS_PNGS} ${ICONS_SVGS} DESTINATION ${KDE_INSTALL_ICONDIR})
if (ICONS_SVGS)
list(GET ICONS_SVGS 0 app_icon_svg)
configure_file(icons/icons.qrc.in icons.qrc @ONLY)
set(_gpgol-client_SRCS ${_gpgol-client_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/icons.qrc)
endif()
add_executable(gpgol-client main.cpp ${_gpgol-client_SRCS})
qt_add_resources(gpgol-client "manifest"
PREFIX "/gpgol-client"
FILES manifest.xml.in
)
target_link_libraries(gpgol-client PRIVATE gpgol-client-static)
if (BUILD_TESTING)
add_subdirectory(autotests)
endif()
install(TARGETS gpgol-client ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES com.gnupg.gpgolweb.desktop DESTINATION ${KDE_INSTALL_APPDIR})
diff --git a/client/emailviewer.cpp b/client/emailviewer.cpp
index 5a68f53..b756d19 100644
--- a/client/emailviewer.cpp
+++ b/client/emailviewer.cpp
@@ -1,153 +1,153 @@
// SPDX-FileCopyrightText: 2024 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "emailviewer.h"
#include "editor/composerwindow.h"
#include "editor/composerwindowfactory.h"
#include "firsttimedialog.h" // for strongActivateWindow
+#include "gpgol_client_debug.h"
+#include "importablekeyswidget.h"
#include "securitylevelinfo.h"
#include "websocketclient.h"
#include <Libkleo/Compliance>
#include <KColorScheme>
#include <KLocalizedString>
#include <KMessageDialog>
#include <mimetreeparser_widgets_version.h>
+#include <MimeTreeParserCore/MessagePart>
+#include <MimeTreeParserCore/ObjectTreeParser>
+#include <MimeTreeParserCore/PartModel>
#include <QApplication>
#include <QLabel>
#include <QMenuBar>
#include <QStatusBar>
#include <QToolBar>
#include <QToolButton>
#include <QToolTip>
#include <QVBoxLayout>
using namespace Qt::Literals::StringLiterals;
static KMime::Message::Ptr toMessage(const QString &content)
{
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(content.toUtf8()));
message->parse();
return message;
}
-EmailViewer::EmailViewer(const QString &content, const QString &accountEmail, const QString &displayName)
+EmailViewer::EmailViewer()
: MimeTreeParser::Widgets::MessageViewerWindow()
- , m_email(accountEmail)
- , m_displayName(displayName)
, m_secLevelButton(nullptr)
{
- setMessages({toMessage(content)});
-
- auto message = messages().at(0);
toolBar()->show();
// spacer
QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
toolBar()->addWidget(spacer);
auto messageMenu = menuBar()->findChild<QMenu *>("messageMenu");
Q_ASSERT(messageMenu);
messageMenu->addSeparator();
+ m_importableKeysWidget = new ImportableKeysWidget();
+ toolBar()->addWidget(m_importableKeysWidget);
m_secLevelButtonContainer = new QWidget();
auto l = new QVBoxLayout(m_secLevelButtonContainer);
l->setContentsMargins(0, 0, 0, 0);
toolBar()->addWidget(m_secLevelButtonContainer);
- updateSecLevelButton();
// reply
auto replyAction = new QAction(QIcon::fromTheme(u"mail-reply-sender-symbolic"_s), i18nc("@action:button", "Reply"), toolBar());
connect(replyAction, &QAction::triggered, this, [this](bool) {
auto dialog = ComposerWindowFactory::self().create(m_email, m_displayName);
dialog->reply(messages().at(0));
FirstTimeDialog::strongActivateWindow(dialog);
});
toolBar()->addAction(replyAction);
messageMenu->addAction(replyAction);
auto widget = qobject_cast<QToolButton *>(toolBar()->widgetForAction(replyAction));
widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
// forward
auto forwardAction = new QAction(QIcon::fromTheme(u"mail-forward-symbolic"_s), i18nc("@action:button", "Forward"), toolBar());
connect(forwardAction, &QAction::triggered, this, [this](bool) {
auto dialog = ComposerWindowFactory::self().create(m_email, m_displayName);
dialog->reply(messages().at(0));
FirstTimeDialog::strongActivateWindow(dialog);
});
toolBar()->addAction(forwardAction);
messageMenu->addAction(forwardAction);
widget = qobject_cast<QToolButton *>(toolBar()->widgetForAction(forwardAction));
widget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
#if MIMETREEPARSER_WIDGETS_VERSION >= QT_VERSION_CHECK(6, 5, 0)
if (Kleo::DeVSCompliance::isActive()) {
auto statusBar = new QStatusBar(this);
auto statusLbl = std::make_unique<QLabel>(Kleo::DeVSCompliance::name());
{
auto statusPalette = qApp->palette();
KColorScheme::adjustForeground(statusPalette,
Kleo::DeVSCompliance::isCompliant() ? KColorScheme::NormalText : KColorScheme::NegativeText,
statusLbl->foregroundRole(),
KColorScheme::View);
statusLbl->setAutoFillBackground(true);
KColorScheme::adjustBackground(statusPalette,
Kleo::DeVSCompliance::isCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground,
QPalette::Window,
KColorScheme::View);
statusLbl->setPalette(statusPalette);
}
statusBar->addPermanentWidget(statusLbl.release());
setStatusBar(statusBar);
}
#endif
}
void EmailViewer::closeEvent(QCloseEvent *event)
{
MimeTreeParser::Widgets::MessageViewerWindow::closeEvent(event);
WebsocketClient::self().sendStatusUpdate(true);
}
void EmailViewer::showEvent(QShowEvent *event)
{
MimeTreeParser::Widgets::MessageViewerWindow::showEvent(event);
WebsocketClient::self().sendStatusUpdate();
}
void EmailViewer::view(const QString &content, const QString &accountEmail, const QString &displayName)
{
m_email = accountEmail;
m_displayName = displayName;
- setMessages({toMessage(content)});
- updateSecLevelButton();
- toolBar()->show();
-}
+ auto message = toMessage(content);
+ setMessages({message});
+ m_importableKeysWidget->init(message);
-void EmailViewer::updateSecLevelButton()
-{
delete m_secLevelButton;
m_secLevelButton = new QToolButton();
- SecurityLevelInfo secInfo(messages().at(0));
+ SecurityLevelInfo secInfo(message);
m_secLevelButton->setText(secInfo.shortText());
m_secLevelButton->setIcon(secInfo.icon());
m_secLevelButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
m_secLevelButton->setToolTip(secInfo.details());
connect(m_secLevelButton, &QToolButton::clicked, m_secLevelButton, [this, secInfo]() {
KMessageDialog dialog(KMessageDialog::Information, secInfo.details(), m_secLevelButton);
dialog.setWindowTitle(secInfo.shortText());
dialog.setIcon(secInfo.icon());
dialog.exec();
});
m_secLevelButtonContainer->layout()->addWidget(m_secLevelButton);
+
+ toolBar()->show();
}
diff --git a/client/emailviewer.h b/client/emailviewer.h
index 6fb398f..1eccd05 100644
--- a/client/emailviewer.h
+++ b/client/emailviewer.h
@@ -1,30 +1,32 @@
// SPDX-FileCopyrightText: 2024 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <MimeTreeParserWidgets/MessageViewerWindow>
class QToolButton;
+class ImportableKeysWidget;
/// Subclass of MTP's MessageViewerWindow with the added feature of adding a
/// reply and forward buttons, and security level indicator.
class EmailViewer : public MimeTreeParser::Widgets::MessageViewerWindow
{
Q_OBJECT
public:
- explicit EmailViewer(const QString &content, const QString &accountEmail, const QString &displayName);
+ explicit EmailViewer();
void view(const QString &content, const QString &accountEmail, const QString &displayName);
protected:
void closeEvent(QCloseEvent *event) override;
void showEvent(QShowEvent *event) override;
private:
QString m_email;
QString m_displayName;
QWidget *m_secLevelButtonContainer;
QToolButton *m_secLevelButton;
+ ImportableKeysWidget *m_importableKeysWidget;
void updateSecLevelButton();
};
diff --git a/client/importablekeyswidget.cpp b/client/importablekeyswidget.cpp
new file mode 100644
index 0000000..29219ee
--- /dev/null
+++ b/client/importablekeyswidget.cpp
@@ -0,0 +1,152 @@
+// SPDX-FileCopyrightText: 2026 g10 code GmbH
+// SPDX-Contributor: Thomas Friedrichsmeier <thomas.friedrichsmeier@gnupg.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "importablekeyswidget.h"
+
+#include "gpgol_client_debug.h"
+
+#include <gpgme++/data.h>
+#include <gpgme++/key.h>
+
+#include <Libkleo/KeyCache>
+#include <Libkleo/Formatting>
+
+#include <KLocalizedString>
+#include <KMessageBox>
+
+using namespace Qt::Literals::StringLiterals;
+
+ImportableKeysWidget::ImportableKeysWidget(QWidget *parent)
+ : QPushButton(parent)
+{
+ setText(u"Import keys"_s);
+ connect(this, &QPushButton::clicked, this, [this]() {
+ QStringList keylist;
+ for (const auto &key : results) {
+ keylist.append(i18n("Key %1 found in %2 (%3)", QString::fromLatin1(key.key.keyID()) + u": "_s + Kleo::Formatting::prettyNameAndEMail(key.key), key.source == KeyInfo::AutocryptHeader ? i18nc("noun", "Autocrypt message header") : i18n("Attachment"),
+ key.status == KeyInfo::NewKey ? i18nc("@status", "New") : i18nc("@status updated when", "Updated %1", QLocale().toString(QDateTime::fromSecsSinceEpoch(key.key.lastUpdate())))));
+ }
+ auto res = KMessageBox::questionTwoActionsList(parentWidget(), i18n("The following new or updated OpenPGP keys were found in the headers and/or attachments to this message. Do you want to import them?"), keylist, i18n("Import keys"), KGuiItem(i18n("Import")), KStandardGuiItem::cancel());
+ if (res == KMessageBox::PrimaryAction) {
+ // TODO: can we somehow get Kleopatra's dialog, here?
+ auto ctx = GpgME::Context::create(GpgME::Protocol::OpenPGP);
+ int imported = 0;
+ for (const auto &key : results) {
+ imported += ctx->importKeys(key.data).numImported();
+ }
+ KMessageBox::information(parentWidget(), i18n("%1 keys were imported", imported)); // , res.numConsidered(), QString::fromStdString(res.error().asStdString())));
+ }
+ });
+}
+
+void ImportableKeysWidget::init(KMime::Message::Ptr message)
+{
+ results.clear();
+
+ // TODO: avoid parsing&decrypting the same message multiple times (emailviewer, securitylevelinfo, here)
+ MimeTreeParser::ObjectTreeParser parser;
+ parser.parseObjectTree(message.get());
+ parser.decryptAndVerify();
+ findImportableAutocryptKeys(parser);
+ findImportableAttachedKeys(parser);
+
+ setEnabled(!results.isEmpty());
+}
+
+void ImportableKeysWidget::addKeyToResults(KeyInfo::Source source, GpgME::Key key, GpgME::Data data)
+{
+ const QString keyId = QString::fromLatin1(key.keyID());
+ KeyInfo info;
+ info.source = source;
+ info.key = key;
+ info.data = data;
+
+ const auto knownkey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
+ if (knownkey.isNull()) {
+ info.status = KeyInfo::NewKey;
+ } else if (key.lastUpdate() > knownkey.lastUpdate()) {
+ info.status = KeyInfo::UpdatedKey;
+ } else {
+ qCWarning(GPGOL_CLIENT_LOG) << "Ignoring known key" << keyId;
+ return;
+ }
+
+ // In case of multiple keys, use the one given as attachment rather than header,
+ // or the newest one
+ if (!results.contains(keyId)) {
+ results.insert(keyId, info);
+ } else {
+ qCWarning(GPGOL_CLIENT_LOG) << "Duplicate key" << keyId;
+ const auto other = results[keyId];
+ if (other.source != source) {
+ if (source == KeyInfo::Attachment) {
+ results.insert(keyId, info);
+ }
+ } else if (other.key.lastUpdate() < key.lastUpdate()) {
+ results.insert(keyId, info);
+ }
+ }
+}
+
+void ImportableKeysWidget::findImportableAttachedKeys(MimeTreeParser::ObjectTreeParser &parser)
+{
+ const auto attachments = parser.collectAttachmentParts();
+ for (const auto &att : attachments) {
+ if (att->mimeType() == "application/pgp-keys") {
+ auto txt = att->node()->decodedBody();
+ const auto data = GpgME::Data(txt.data(), txt.size());
+ const auto keys = data.toKeys();
+ for (const auto &key : keys) {
+ addKeyToResults(KeyInfo::Attachment, key, data);
+ }
+ }
+ }
+}
+
+void ImportableKeysWidget::getKeyFromAutocryptHeader(KMime::Headers::Base *autocryptHeader, const QString &from)
+{
+ const auto autocryptData = autocryptHeader->asUnicodeString();
+ const auto autocryptFields = autocryptData.split(u"; "_s);
+ if (!(autocryptFields.size() && autocryptFields.first().startsWith(u"addr="_s) && autocryptFields.last().startsWith(u"keydata="_s))) {
+ qCWarning(GPGOL_CLIENT_LOG) << "Autocrypt header format error";
+ return;
+ }
+ const auto autocryptFrom = autocryptFields.first().section(u"addr="_s, 1);
+ if (!from.isEmpty() && from != autocryptFrom) {
+ qCWarning(GPGOL_CLIENT_LOG) << "Autocrypt header from mismatch" << from << "vs" << autocryptFrom;
+ return;
+ }
+ const auto autocryptKeydata = QByteArray::fromBase64(autocryptFields.last().section(u"keydata="_s, 1).toLatin1());
+ const auto data = GpgME::Data(autocryptKeydata.constData(), autocryptKeydata.size());
+ const auto keys = data.toKeys();
+
+ for (const auto &key : keys) {
+ addKeyToResults(KeyInfo::AutocryptHeader, key, data);
+ }
+}
+
+void ImportableKeysWidget::findImportableAutocryptKeys(MimeTreeParser::ObjectTreeParser &parser)
+{
+ // Autocrypt specification / docs: https://docs.autocrypt.org/level1.html
+ // In short: Outer Mail envelope may have exactly one "Autocrypt:" header with the sender's key.
+ // For encrypted mails to several receivers, the inner, encrypted, message may have an arbitrary numer
+ // of "Autocrypt-Gossip:" headers, one per recipient.
+ auto rootPart = parser.parsedPart();
+ auto message = rootPart->node();
+ const auto autocryptHeader = message->headerByType("Autocrypt");
+ if (!autocryptHeader) {
+ qCWarning(GPGOL_CLIENT_LOG) << "No autocrypt header";
+ return;
+ }
+ const auto from = QString::fromLatin1(GpgME::UserID::addrSpecFromString(message->headerByType("from")->as7BitString().constData()));
+ getKeyFromAutocryptHeader(autocryptHeader, from);
+
+ if (rootPart->partMetaData()->isEncrypted && rootPart->subParts().size() == 1) {
+ auto part = rootPart->subParts().at(0);
+ const auto autocryptGossipHeaders = part->node()->headersByType("Autocrypt-Gossip");
+ for (const auto &header : autocryptGossipHeaders) {
+ getKeyFromAutocryptHeader(header, QString());
+ }
+ }
+}
diff --git a/client/importablekeyswidget.h b/client/importablekeyswidget.h
new file mode 100644
index 0000000..509b495
--- /dev/null
+++ b/client/importablekeyswidget.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2026 g10 code GmbH
+// SPDX-Contributor: Thomas Friedrichsmeier <thomas.friedrichsmeier@gnupg.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <KMime/Message>
+#include <MimeTreeParserCore/MessagePart>
+#include <MimeTreeParserCore/ObjectTreeParser>
+#include <MimeTreeParserCore/PartModel>
+
+#include <gpgme++/key.h>
+#include <gpgme++/data.h>
+
+#include <QHash>
+#include <QPushButton>
+#include <QString>
+
+/** Check message for importable keys in Autocrypt headers and / or attachments,
+ offer to import those.
+ While it is not generally harmful to import keys, we do not want to do this automatically,
+ as potentially we might receive unwanted / too many keys by email. */
+class ImportableKeysWidget : public QPushButton
+{
+public:
+ ImportableKeysWidget(QWidget *parent=nullptr);
+ void init(KMime::Message::Ptr message);
+
+ struct KeyInfo
+ {
+ GpgME::Key key;
+ GpgME::Data data;
+ enum Source {
+ Attachment,
+ AutocryptHeader
+ } source;
+ enum {
+ NewKey,
+ UpdatedKey,
+ KnownOrOlderKey // these are currently purged, right away
+ } status;
+ };
+private:
+ QHash<QString, KeyInfo> results;
+
+ void addKeyToResults(KeyInfo::Source source, GpgME::Key key, GpgME::Data data);
+ void findImportableAttachedKeys(MimeTreeParser::ObjectTreeParser &parser);
+ void getKeyFromAutocryptHeader(KMime::Headers::Base *autocryptHeader, const QString &from);
+ void findImportableAutocryptKeys(MimeTreeParser::ObjectTreeParser &parser);
+};
diff --git a/client/main.cpp b/client/main.cpp
index fa9cbdf..91a4593 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -1,136 +1,139 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QApplication>
#include <QCommandLineParser>
#include <QElapsedTimer>
#include <QFile>
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPointer>
#include <QTimer>
#include <QUuid>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
+#include <gpgme++/global.h>
+
#include <KAboutData>
-#include <KJob>
#include <KLocalizedString>
#include "../common/log.h"
#include "config.h"
#include "firsttimedialog.h"
#include "gpgol_client_debug.h"
#include "gpgolweb_version.h"
#include "utils/kuniqueservice.h"
using namespace Qt::Literals::StringLiterals;
using namespace std::chrono;
#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif
#define STARTUP_TIMING qCDebug(GPGOL_CLIENT_LOG) << "Startup timing:" << startupTimer.elapsed() << "ms:"
#define STARTUP_TRACE qCDebug(GPGOL_CLIENT_LOG) << "Startup timing:" << startupTimer.elapsed() << "ms:" << SRCNAME << __func__ << __LINE__;
static void setupLogging(std::shared_ptr<Kleo::Log> log)
{
const QByteArray dirNative = qgetenv("GPGOL_CLIENT_LOGDIR");
if (dirNative.isEmpty()) {
return;
}
log->setOutputDirectory(QFile::decodeName(dirNative));
qInstallMessageHandler(Kleo::Log::messageHandler);
}
int main(int argc, char *argv[])
{
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
// On Windows, broken heuristics appear to be in place WRT whether our app should exit
// when the last QEventLocker goes out of scope, resulting in sudden quits, after handling a
// websocket request while minimized to systray (the specific conditions appear to include that no
// other window besides the FirstTimeDialog had been visible before minimzing).
// We prefer to quit on our own terms, thank you!
app.setQuitLockEnabled(false);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("gpgol-js-native"));
KAboutData about(QStringLiteral("gpgol-client"),
i18nc("@title:window", "GnuPG Outlook Add-in"),
QStringLiteral(GPGOLWEB_VERSION_STRING),
i18nc("@info", "GPG Outlook add-in"),
KAboutLicense::GPL,
i18nc("@info:credit", "© 2023-2025 g10 Code GmbH"));
about.setDesktopFileName(u"com.gnupg.gpgolweb"_s);
about.setProgramLogo(QIcon::fromTheme(u"com.gnupg.gpgolweb"_s));
about.addAuthor(i18nc("@info:credit", "Carl Schwan"), i18nc("@info:credit", "Maintainer"), u"carl.schwan@gnupg.com"_s, u"https://carlschwan.eu"_s);
about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
about.setOrganizationDomain("gnupg.com");
about.setBugAddress("https://dev.gnupg.org/maniphest/task/edit/form/1/");
QCommandLineParser parser;
KAboutData::setApplicationData(about);
about.setupCommandLine(&parser);
parser.process(app);
about.processCommandLine(&parser);
QElapsedTimer startupTimer;
startupTimer.start();
STARTUP_TIMING << "Application created";
/* Create the unique service ASAP to prevent double starts if
* the application is started twice very quickly. */
KUniqueService service;
QObject::connect(&service, &KUniqueService::activateRequested, &service, [&service](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(arguments);
Q_UNUSED(workingDirectory);
service.setExitValue(0);
});
+ GpgME::initializeLibrary(); // This will probably also happen via libkleo, but
+ // as we're also using gpgpme++, directly, let's do it the clean way.
auto log = Kleo::Log::mutableInstance();
setupLogging(log);
STARTUP_TIMING << "Service created";
QPointer<FirstTimeDialog> launcher = new FirstTimeDialog;
if (Config::self()->showLauncher()) {
launcher->show();
}
STARTUP_TIMING << "KeyCache creation";
auto keyCache = Kleo::KeyCache::mutableInstance();
auto fsWatcher = std::make_shared<Kleo::FileSystemWatcher>();
fsWatcher->whitelistFiles(Kleo::gnupgFileWhitelist());
fsWatcher->addPaths(Kleo::gnupgFolderWhitelist());
fsWatcher->setDelay(1000);
keyCache->addFileSystemWatcher(fsWatcher);
keyCache->startKeyListing();
return app.exec();
}
diff --git a/client/websocketclient.cpp b/client/websocketclient.cpp
index 42e3e21..521b4c6 100644
--- a/client/websocketclient.cpp
+++ b/client/websocketclient.cpp
@@ -1,458 +1,457 @@
// 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 "firsttimedialog.h"
#include "gpgol_client_debug.h"
#include "gpgolweb_version.h"
#include "mailapi.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";
sendCommand(Protocol::Register, QJsonObject{
{"emails"_L1, QJsonArray::fromStringList(m_emails)}, // TODO: keep this?
{"type"_L1, "native"_L1},
{"name"_L1, QString(QHostInfo::localHostName() + u" - GpgOL/Web ("_s + QStringLiteral(GPGOLWEB_VERSION_STRING) + u')') }, // TODO: unused
});
sendStatusUpdate(); // in case web client was started before native client
m_state = ConnectedToProxy; /// 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 = (m_webSocket.state() == QAbstractSocket::ConnectedState) ? ConnectedToProxy : NotConnected;
m_stateDisplay = i18nc("@info", "Could not reach the Outlook extension.");
Q_EMIT stateChanged(m_stateDisplay);
reconnect();
}
void WebsocketClient::enterPairingMode()
{
sendCommand(Protocol::PairingRequest, QJsonObject{
{"type"_L1, "native-start-pairing"_L1},
});
}
void WebsocketClient::quitPairingMode()
{
sendCommand(Protocol::PairingRequest, QJsonObject{
{"type"_L1, "native-end-pairing"_L1},
});
}
bool WebsocketClient::sendEWSRequest(const QString &fromEmail, const QString &requestId, const QString &requestBody)
{
KMime::Types::Mailbox mailbox;
mailbox.fromUnicodeString(fromEmail);
sendCommand(Protocol::Ews, QJsonObject{
{"body"_L1, requestBody},
{"email"_L1, QString::fromUtf8(mailbox.address())},
{"requestId"_L1, requestId}
});
return true;
}
// TODO: We should really centralize all calls to this as a single call in the connection stage. Afterwards
// the webclient will not send a new token, anyway.
// However, fixing this is currently on hold pending changes in the pairing process,
// so for now, calls to this are littered all around various requests, but at least we can indentify them
// easily as calls to this function.
void WebsocketClient::initMailApiFromArgs(const QJsonObject &args)
{
MailApiController::init(
(args[u"api"_s].toString() == u"ews"_s) ? MailApiController::EWSApi : MailApiController::GraphApi,
args[u"apiendpoint"_s].toString(),
args[u"ewsAccessToken"_s].toString()
);
}
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 = ConnectedToProxy;
m_stateDisplay = i18nc("@info", "Connection to the Outlook extension lost. Make sure the extension pane is open.");
Q_EMIT stateChanged(m_stateDisplay);
// TODO: handle multiple clients
return;
case Protocol::PairingRequest: {
const auto token = args["token"_L1].toString();
if (token.isEmpty()) {
Q_EMIT pairingStatusChanged(QString(), false);
qCWarning(GPGOL_CLIENT_LOG) << "Pairing complete";
} else {
Q_EMIT pairingStatusChanged(token, true);
}
return;
}
case Protocol::Connection:
// reconnection of the web client
m_state = ConnectedToWebclient;
m_stateDisplay = i18nc("@info", "Connected.");
Q_EMIT stateChanged(m_stateDisplay);
sendStatusUpdate();
return;
case Protocol::View: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto id = args["itemId"_L1].toString();
const auto content = m_cachedMime[id];
if (content.isEmpty()) {
return;
}
initMailApiFromArgs(args);
if (!m_emailViewer) {
- m_emailViewer = new EmailViewer(QString::fromUtf8(content), email, displayName);
+ m_emailViewer = new EmailViewer();
m_emailViewer->setAttribute(Qt::WA_DeleteOnClose);
- } else {
- m_emailViewer->view(QString::fromUtf8(content), email, displayName);
}
+ m_emailViewer->view(QString::fromUtf8(content), email, displayName);
FirstTimeDialog::strongActivateWindow(m_emailViewer);
return;
}
case Protocol::RestoreAutosave: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
initMailApiFromArgs(args);
ComposerWindowFactory::self().restoreAutosave(email, displayName);
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();
initMailApiFromArgs(args);
auto dialog = ComposerWindowFactory::self().create(email, displayName);
if (command == Protocol::Reply || command == Protocol::Forward) {
const auto 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));
message->parse();
if (command == Protocol::Reply) {
dialog->reply(message);
} else {
dialog->forward(message);
}
} else if (command == Protocol::OpenDraft) {
const auto draftId = args["draftId"_L1].toString();
if (draftId.isEmpty()) {
return;
}
const auto draft = DraftManager::self().draftById(draftId.toUtf8());
dialog->setMessage(draft.mime());
}
FirstTimeDialog::strongActivateWindow(dialog);
return;
}
case Protocol::DeleteDraft: {
const auto draftId = args["draftId"_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;
}
sendStatusUpdate();
return;
}
case Protocol::Reencrypt: {
initMailApiFromArgs(args);
reencrypt(args);
return;
}
case Protocol::Info: {
initMailApiFromArgs(args);
info(args);
return;
}
default:
qCWarning(WEBSOCKET_LOG) << "Unhandled command" << command;
}
}
void WebsocketClient::reencrypt(const QJsonObject &args)
{
if (m_reencryptJob) {
if (m_reencryptJob->hasStarted()) {
m_reencryptJob->tryRaiseDialog();
return;
}
m_reencryptJob->deleteLater();
}
// TODO: Looking up the folderId by itemId would allow some simplification in the web.js
m_reencryptJob = new ReencryptJob(args["folderId"_L1].toString());
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::sendCommand(Protocol::Command command, const QJsonObject &arguments)
{
const auto json = Protocol::makeCommand(command, arguments, getId());
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
}
void WebsocketClient::sendStatusUpdate(bool viewerJustClosed)
{
QJsonArray features;
if (Config::self()->reencrypt()) {
features << u"reencrypt"_s;
}
sendCommand(Protocol::StatusUpdate, QJsonObject{
{"drafts"_L1, DraftManager::self().toJson()},
{"viewerOpen"_L1, !viewerJustClosed && !m_emailViewer.isNull()},
{"features"_L1, features}
});
}
void WebsocketClient::info(const QJsonObject &args)
{
const auto email = args["email"_L1].toString();
sendStatusUpdate(false); // web client expects to know that info before info-fetched
const QString id(args["itemId"_L1].toString());
qCWarning(GPGOL_CLIENT_LOG) << "Info for" << id << "requested";
if (m_cachedInfo.contains(id)) {
sendCommand(Protocol::InfoFetched, m_cachedInfo[id]);
return;
}
MailApiController::self().setAccessToken(args["ewsAccessToken"_L1].toString());
auto request = MailApiController::self().getMails({id}, GetMailsJob::GetMimeContent | GetMailsJob::GetParentFolderId);
connect(request, &GetMailsJob::finished, this, [this, id, args, request]() {
if (request->error() != KJob::NoError) {
sendCommand(Protocol::Error, QJsonObject{{"error"_L1, request->errorString()}});
qCWarning(GPGOL_CLIENT_LOG) << "Failure to get mail:" << request->errorText();
return;
}
qCWarning(GPGOL_CLIENT_LOG) << "Info for" << id << "fetched";
const auto responses = request->takeResponses();
if (responses.isEmpty()) {
return;
}
const auto item = responses.first();
const auto mimeContent = GetMailsJob::mimeContent(item);
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(mimeContent));
message->parse();
MimeTreeParser::ObjectTreeParser treeParser;
treeParser.parseObjectTree(message.get());
const auto data = QJsonObject{
{"itemId"_L1, args["itemId"_L1]},
{"folderId"_L1, GetMailsJob::parentFolderId(item)},
{"email"_L1, args["email"_L1]},
{"encrypted"_L1, treeParser.hasEncryptedParts()},
{"signed"_L1, treeParser.hasSignedParts()},
{"version"_L1, QStringLiteral(GPGOLWEB_VERSION_STRING)},
};
m_cachedInfo[id] = data;
m_cachedMime[id] = mimeContent;
sendCommand(Protocol::InfoFetched, data);
});
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
Thu, Feb 26, 7:15 PM (20 h, 44 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7f/49/afca57fc60ccb8e8175561cd1d2a

Event Timeline