Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34503819
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
62 KB
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb81b0e..a4ec47d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,93 +1,92 @@
# SPDX-FileCopyrightText: 2023 g10 code Gmbh
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
cmake_minimum_required(VERSION 3.20)
set(VERSION "0.9.0")
project(gpgoljs VERSION ${VERSION})
set(KF_MIN_VERSION "6.0.0")
set(QT_MIN_VERSION "6.6.0")
set(MIMETREEPARSER_VERSION "6.3.41")
set(LIBKLEO_VERSION "6.2.0")
set(LIBKDEPIM_VERSION "6.2.0")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${ECM_MODULE_PATH}
)
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
include(FeatureSummary)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDEGitCommitHooks)
include(KDECMakeSettings)
include(KDEClangFormat)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
include(ECMSetupVersion)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
ecm_setup_version(${VERSION} VARIABLE_PREFIX GPGOLJS VERSION_HEADER
"${CMAKE_CURRENT_BINARY_DIR}/client/gpgoljs_version.h")
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core HttpServer Widgets PrintSupport WebSockets)
set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Contacts Completion CoreAddons WidgetsAddons Config ColorScheme Codecs XmlGui GuiAddons JobWidgets Sonnet CalendarCore Archive)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KPim6Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim6Libkdepim ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED)
find_package(KPim6MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
# Optional packages
if (WIN32)
# Only a replacement available for Windows so this
# is required on other platforms.
find_package(KF6DBusAddons ${KF_MIN_VERSION} CONFIG)
set_package_properties(KF6DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
URL "https://inqlude.org/libraries/kdbusaddons.html"
TYPE OPTIONAL)
else()
find_package(KF6DBusAddons ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF6Auth ${KF_MIN_VERSION} CONFIG REQUIRED)
set(_gpgol_dbusaddons_libs KF6::DBusAddons)
endif()
if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
endif()
add_subdirectory(common)
add_subdirectory(server)
add_subdirectory(client)
-add_subdirectory(gpgol-cert-generator)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES client/*.cpp client/*.h server/*.cpp server/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index baf8e12..af7bc3d 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -1,327 +1,328 @@
# 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_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
# 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/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/ewsrequest.cpp
ews/ewsrequest.h
ews/ewsjob.cpp
ews/ewsjob.h
# 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
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)
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
KPim6::MimeTreeParserCore
KPim6::MimeTreeParserWidgets
KPim6::Libkleo
KPim6::Libkdepim
${_gpgol_dbusaddons_libs}
)
add_executable(gpgol-client main.cpp)
target_link_libraries(gpgol-client PRIVATE gpgol-client-static)
qt_add_resources(gpgol-client
PREFIX
"/"
FILES
assets/64-gpgpass.png
)
if (BUILD_TESTING)
add_subdirectory(autotests)
endif()
install(TARGETS gpgol-client ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES com.gnupg.gpgoljs.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES com.gnupg.gpgoljs.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
diff --git a/client/firsttimedialog.cpp b/client/firsttimedialog.cpp
index 25d0c5e..f339c20 100644
--- a/client/firsttimedialog.cpp
+++ b/client/firsttimedialog.cpp
@@ -1,71 +1,69 @@
// SPDX-FileCopyrightText: 2024 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "firsttimedialog.h"
#include "config.h"
#include "gpgoljs_version.h"
#include "ui_firsttimedialog.h"
-#include "websocketclient.h"
#include <QClipboard>
#include <QCloseEvent>
#include <QDesktopServices>
using namespace Qt::StringLiterals;
FirstTimeDialog::FirstTimeDialog(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::FirstTimeDialog)
{
ui->setupUi(this);
// center vertically
ui->mainLayout->insertStretch(0);
ui->mainLayout->addStretch();
ui->mainLayoutManifest->insertStretch(0);
ui->mainLayoutManifest->addStretch();
const double titleFontSize = QApplication::font().pointSize() * 2;
auto font = ui->title->font();
font.setPointSize(titleFontSize);
ui->title->setFont(font);
QPixmap logo = QIcon::fromTheme(u"com.gnupg.gpgoljs"_s).pixmap(48, 48);
ui->logo->setPixmap(logo);
ui->version->setText(i18nc("@info", "Version: %1", QString::fromLocal8Bit(GPGOLJS_VERSION_STRING)));
- ui->state->setText(WebsocketClient::self().stateDisplay());
connect(ui->setupButton, &QPushButton::clicked, this, [this]() {
ui->stack->setCurrentIndex(1);
});
ui->manifestPath->setText(QLatin1StringView(DATAROUTDIR) + u"/gpgol/manifest.xml"_s);
connect(ui->openOutlookButton, &QPushButton::clicked, this, []() {
QDesktopServices::openUrl(QUrl(u"https://outlook.office.com/mail/jsmvvmdeeplink/?path=/options/manageapps&bO=4"_s));
});
connect(ui->manifestPathCopy, &QPushButton::clicked, this, [this]() {
QGuiApplication::clipboard()->setText(ui->manifestPath->text());
});
ui->showOnStartup->setChecked(Config::self()->showLauncher());
connect(ui->showOnStartup, &QCheckBox::toggled, this, [](bool checked) {
Config::self()->setShowLauncher(checked);
Config::self()->save();
});
}
-void FirstTimeDialog::slotStateChanged()
+void FirstTimeDialog::slotStateChanged(const QString &stateDisplay)
{
- ui->state->setText(WebsocketClient::self().stateDisplay());
+ ui->state->setText(stateDisplay);
}
void FirstTimeDialog::closeEvent(QCloseEvent *e)
{
e->ignore();
hide();
}
FirstTimeDialog::~FirstTimeDialog() = default;
diff --git a/client/firsttimedialog.h b/client/firsttimedialog.h
index 95a7e2b..2fc29f2 100644
--- a/client/firsttimedialog.h
+++ b/client/firsttimedialog.h
@@ -1,29 +1,29 @@
// SPDX-FileCopyrightText: 2024 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QMainWindow>
namespace Ui
{
class FirstTimeDialog;
}
class FirstTimeDialog : public QMainWindow
{
Q_OBJECT
public:
explicit FirstTimeDialog(QWidget *parent = nullptr);
~FirstTimeDialog();
public Q_SLOTS:
- void slotStateChanged();
+ void slotStateChanged(const QString &stateDisplay);
protected:
- void closeEvent(QCloseEvent *e);
+ void closeEvent(QCloseEvent *e) override;
private:
QScopedPointer<Ui::FirstTimeDialog> ui;
};
diff --git a/client/main.cpp b/client/main.cpp
index 8e94871..401cc2c 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -1,113 +1,132 @@
// 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 <QHttpServer>
#include <QHttpServerResponse>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPointer>
+#include <QTemporaryDir>
#include <QTimer>
#include <QUuid>
#include <Libkleo/KeyCache>
#include <KAboutData>
+#include <KJob>
#include <KLocalizedString>
#include "config.h"
#include "firsttimedialog.h"
#include "gpgol_client_debug.h"
#include "gpgoljs_version.h"
#include "qnam.h"
+#include "rootcagenerator/controller.h"
#include "utils/kuniqueservice.h"
#include "utils/systemtrayicon.h"
#include "websocketclient.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__;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("gpgol"));
KAboutData about(QStringLiteral("gpgol-client"),
i18nc("@title:window", "GnuPG Outlook Add-in"),
QStringLiteral(GPGOLJS_VERSION_STRING),
i18nc("@info", "GPG Outlook add-in"),
KAboutLicense::GPL,
i18nc("@info:credit", "© 2023-2024 g10 Code GmbH"));
about.setDesktopFileName(u"com.gnupg.gpgoljs"_s);
about.setProgramLogo(QIcon::fromTheme(u"com.gnupg.gpgoljs"_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;
STARTUP_TIMING << "Service created";
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &) {
reply->ignoreSslErrors();
});
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
- const auto clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
-
- STARTUP_TIMING << "KeyCache creation";
- WebsocketClient::self(QUrl(u"wss://localhost:5657/"_s), clientId);
- auto keyCache = Kleo::KeyCache::mutableInstance();
- keyCache->startKeyListing();
-
QPointer<FirstTimeDialog> launcher = new FirstTimeDialog;
SystemTrayIcon systemTrayIcon(QIcon::fromTheme(u"com.gnupg.gpgoljs"_s));
systemTrayIcon.setMainWindow(launcher);
systemTrayIcon.show();
- QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, launcher.get(), &FirstTimeDialog::slotStateChanged);
- QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, &systemTrayIcon, &SystemTrayIcon::slotStateChanged);
+ const auto gnupghome = qgetenv("GNUPGHOME");
- if (Config::self()->showLauncher()) {
- launcher->show();
- }
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(false);
+
+ qputenv("GNUPGHOME", tempDir.path().toUtf8());
+
+ auto controller = new Controller;
+ QObject::connect(controller, &Controller::finished, controller, [gnupghome, launcher, &systemTrayIcon](KJob *) {
+ qputenv("GNUPGHOME", gnupghome);
+
+ const auto clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+ WebsocketClient::self(QUrl(u"wss://localhost:5657/"_s), clientId);
+
+ QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, launcher.get(), &FirstTimeDialog::slotStateChanged);
+ QObject::connect(&WebsocketClient::self(), &WebsocketClient::stateChanged, &systemTrayIcon, &SystemTrayIcon::slotStateChanged);
+
+ systemTrayIcon.slotStateChanged(WebsocketClient::self().stateDisplay());
+ launcher->slotStateChanged(WebsocketClient::self().stateDisplay());
+
+ if (Config::self()->showLauncher()) {
+ launcher->show();
+ }
+ });
+ controller->start();
+
+ STARTUP_TIMING << "KeyCache creation";
+ auto keyCache = Kleo::KeyCache::mutableInstance();
+ keyCache->startKeyListing();
return app.exec();
}
diff --git a/client/rootcagenerator/CMakeLists.txt b/client/rootcagenerator/CMakeLists.txt
index a80bc5b..b705633 100644
--- a/client/rootcagenerator/CMakeLists.txt
+++ b/client/rootcagenerator/CMakeLists.txt
@@ -1,43 +1,43 @@
# SPDX-FileCopyrightText: 2024 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_library(rootcagenerator STATIC)
target_sources(rootcagenerator PRIVATE
controller.cpp
controller.h
truststore.cpp
truststore.h
)
if (WIN32)
target_sources(rootcagenerator PRIVATE
truststore_win.cpp
truststore_win.h
)
install(FILES install.ps1 DESTINATION ${KDE_INSTALL_BINDIR})
else()
target_sources(rootcagenerator PRIVATE
truststore_linux.cpp
truststore_linux.h
)
target_link_libraries(rootcagenerator PRIVATE KF6::AuthCore)
add_executable(truststore_linux_helper
truststore_linux_helper.cpp
truststore_linux_helper.h
)
target_link_libraries(truststore_linux_helper KF6::I18n KF6::AuthCore Qt::Core)
install(TARGETS truststore_linux_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
kauth_install_actions(com.gnupg.gpgolweb.truststore com.gnupg.gpgolweb.truststore.actions)
kauth_install_helper_files(truststore_linux_helper com.gnupg.gpgolweb.truststore root)
endif()
-target_link_libraries(rootcagenerator PUBLIC KPim6::Libkleo KF6::I18n)
+target_link_libraries(rootcagenerator PUBLIC KPim6::Libkleo KF6::I18n KF6::CoreAddons)
add_executable(gpgol-cert-generator main.cpp)
target_link_libraries(gpgol-cert-generator PUBLIC rootcagenerator)
install(TARGETS gpgol-cert-generator ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/client/rootcagenerator/controller.cpp b/client/rootcagenerator/controller.cpp
index 6e3ae51..0b9b00e 100644
--- a/client/rootcagenerator/controller.cpp
+++ b/client/rootcagenerator/controller.cpp
@@ -1,291 +1,304 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "controller.h"
#include "truststore.h"
#include <QDate>
#include <QSaveFile>
#include <QStandardPaths>
#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/KeyGenerationJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <Libkleo/Formatting>
#include <Libkleo/KeyParameters>
#include <Libkleo/KeyUsage>
#include <KLocalizedString>
using namespace Qt::StringLiterals;
using namespace Kleo;
using namespace GpgME;
static KeyParameters createRootCaParms()
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Certify});
keyParameters.setDN(u"CN=RootGpgolJs"_s);
keyParameters.setEmail(u"localroot@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setUseRandomSerial();
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
static KeyParameters createTlsCertParms(QLatin1StringView keyGrip)
{
KeyParameters keyParameters(KeyParameters::CMS);
keyParameters.setKeyType(GpgME::Subkey::PubkeyAlgo::AlgoRSA);
keyParameters.setKeyUsage(KeyUsage{KeyUsage::Sign | KeyUsage::Encrypt});
keyParameters.setDN(u"CN=LocalGpgolJs"_s);
keyParameters.setEmail(u"local@gpgoljs.local"_s);
keyParameters.setKeyLength(3072);
keyParameters.setExpirationDate(QDate(2060, 10, 10));
keyParameters.setIssuerDN(u"CN=RootGpgolJs"_s);
keyParameters.setSigningKey(keyGrip);
keyParameters.setUseRandomSerial();
keyParameters.setControlStatements({u"%no-protection"_s});
return keyParameters;
}
Controller::Controller(QObject *parent)
- : QObject(parent)
+ : KJob(parent)
{
}
QString Controller::caUniqueName() const
{
return u"GPGOL2 CA "_s + QString::fromLatin1(m_ca.issuerSerial());
}
QByteArray Controller::caCert() const
{
return m_publicCA;
}
void Controller::start()
{
+ auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
+
+ if (!certPath.isEmpty()) {
+ emitResult();
+ return;
+ }
+
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotRootCaCreatedSlot);
keyGenerationJob->start(createRootCaParms().toString());
}
void Controller::slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
- qWarning() << "created root ca";
Q_UNUSED(auditLog)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotRootCaImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
qWarning() << "got root ca imported";
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
// Get the keygrip
auto keyListJob = QGpgME::smime()->keyListJob();
connect(keyListJob, &QGpgME::KeyListJob::result, this, &Controller::slotKeyGripOptained);
keyListJob->start({u"RootGpgolJs"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_publicCA = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_secretCA = keyData;
checkFinished();
});
}
void Controller::slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError)
{
qWarning() << "got key grip";
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not get keygrip : %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not get keygrip : %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_ca = keys[0];
auto keyGenerationJob = QGpgME::smime()->keyGenerationJob();
connect(keyGenerationJob, &QGpgME::KeyGenerationJob::result, this, &Controller::slotCertCreatedSlot);
keyGenerationJob->start(createTlsCertParms(QLatin1StringView(keys[0].subkey(0).keyGrip())).toString());
}
void Controller::slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData, const QString &auditLog)
{
qWarning() << "created tls cert";
Q_UNUSED(auditLog)
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not create key pair for cert: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.")
+ : i18n("Could not create key pair for cert: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto importJob = QGpgME::smime()->importJob();
connect(importJob, &QGpgME::ImportJob::result, this, &Controller::slotCertImportedSlot);
importJob->start(pubKeyData);
}
void Controller::slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError)
{
Q_UNUSED(auditLogAsHtml)
Q_UNUSED(auditLogError)
qWarning() << "got cert imported";
if (result.error().code()) {
- qWarning() << (result.error().isCanceled() ? i18n("Operation canceled.")
- : i18n("Could not import cert: %1", Formatting::errorAsString(result.error())));
- Q_EMIT finished(1);
+ setErrorText(result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not import cert: %1", Formatting::errorAsString(result.error())));
+ setError(UserDefinedError);
+ emitResult();
return;
}
auto keyListJob = QGpgME::smime()->keyListJob();
connect(keyListJob,
&QGpgME::KeyListJob::result,
this,
[this](const GpgME::KeyListResult &result, const std::vector<GpgME::Key> &keys, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) {
m_tls = keys[0];
checkFinished();
});
keyListJob->start({u"RootGpgolJs"_s}, true);
// Export public key
auto exportJob = QGpgME::smime()->publicKeyExportJob(true);
const auto imports = result.imports();
const auto fingerprint = imports[0].fingerprint();
exportJob->start({QString::fromLatin1(fingerprint)});
connect(exportJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export public key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_publicTLS = keyData;
checkFinished();
});
// Export private key
auto exportSecretJob = QGpgME::smime()->secretKeyExportJob(true);
exportSecretJob->start({QString::fromLatin1(fingerprint)});
connect(exportSecretJob, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &keyData) {
if (error.code()) {
- qWarning() << (error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
- Q_EMIT finished(1);
+ setErrorText(error.isCanceled() ? i18n("Operation canceled.") : i18n("Could not export secret key: %1", Formatting::errorAsString(error)));
+ setError(UserDefinedError);
+ emitResult();
return;
}
m_secretTLS = keyData;
checkFinished();
});
}
void Controller::checkFinished()
{
if (!m_secretCA.isEmpty() && !m_publicCA.isEmpty() && !m_publicTLS.isEmpty() && !m_secretTLS.isEmpty() && !m_ca.isNull() && !m_tls.isNull()) {
install();
- Q_EMIT finished();
+ emitResult();
}
}
void Controller::install()
{
qWarning() << "installing";
auto keyPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate-key.pem"));
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
QSaveFile rootCaPub(u"rootCA.pem"_s);
if (rootCaPub.open(QIODeviceBase::WriteOnly)) {
rootCaPub.write(m_publicCA);
rootCaPub.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile rootCaKey(u"rootCA-key.pem"_s);
if (rootCaKey.open(QIODeviceBase::WriteOnly)) {
rootCaKey.write(m_secretCA);
rootCaKey.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile localhostPub(u"localhost.pem"_s);
if (localhostPub.open(QIODeviceBase::WriteOnly)) {
localhostPub.write(m_publicTLS);
localhostPub.commit();
} else {
qDebug() << "no permission to write";
}
QSaveFile localhostKey(u"localhost-key.pem"_s);
if (localhostKey.open(QIODeviceBase::WriteOnly)) {
localhostKey.write(m_secretTLS);
localhostKey.commit();
} else {
qDebug() << "no permission to write";
}
auto trustStore = TrustStoreFactory::getPlatformTrustStore();
trustStore->install(*this);
}
\ No newline at end of file
diff --git a/client/rootcagenerator/controller.h b/client/rootcagenerator/controller.h
index c7f10c2..5e6ac15 100644
--- a/client/rootcagenerator/controller.h
+++ b/client/rootcagenerator/controller.h
@@ -1,56 +1,53 @@
// SPDX-FileCopyrightText: 2024 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <QObject>
+#include <KJob>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/keylistresult.h>
static const QLatin1StringView ROOT_NAME = QLatin1StringView("rootCA.pem");
-class Controller : public QObject
+class Controller : public KJob
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
QString caUniqueName() const;
QByteArray caCert() const;
- void start();
-
-Q_SIGNALS:
- void finished(int errorCode = 0);
+ void start() override;
private:
void slotRootCaCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotRootCaImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void slotKeyGripOptained(const GpgME::KeyListResult &result,
const std::vector<GpgME::Key> &keys,
const QString &auditLogAsHtml,
const GpgME::Error &auditLogError);
void slotCertCreatedSlot(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog);
void slotCertImportedSlot(const GpgME::ImportResult &result, const QString &auditLogAsHtml, const GpgME::Error &auditLogError);
void checkFinished();
void install();
private:
GpgME::Key m_ca;
QByteArray m_publicCA;
QByteArray m_secretCA;
GpgME::Key m_tls;
QByteArray m_publicTLS;
QByteArray m_secretTLS;
};
diff --git a/client/rootcagenerator/main.cpp b/client/rootcagenerator/main.cpp
index 90fe97f..9b0f45c 100644
--- a/client/rootcagenerator/main.cpp
+++ b/client/rootcagenerator/main.cpp
@@ -1,40 +1,46 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCoreApplication>
#include <QTemporaryDir>
#include <KLocalizedString>
#include "controller.h"
#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif
using namespace Qt::StringLiterals;
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QTemporaryDir tempDir;
tempDir.setAutoRemove(false);
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
qWarning() << tempDir.path();
qputenv("GNUPGHOME", tempDir.path().toUtf8());
Controller controller;
- QObject::connect(&controller, &Controller::finished, &app, &QCoreApplication::exit);
+ QObject::connect(&controller, &KJob::finished, &app, [&app](KJob *job) {
+ if (job->error()) {
+ app.exit(1);
+ } else {
+ app.exit(0);
+ }
+ });
controller.start();
return app.exec();
}
\ No newline at end of file
diff --git a/client/utils/systemtrayicon.cpp b/client/utils/systemtrayicon.cpp
index 67a2774..da58047 100644
--- a/client/utils/systemtrayicon.cpp
+++ b/client/utils/systemtrayicon.cpp
@@ -1,289 +1,286 @@
// SPDX-FileCopyrightText: 2007, 2009 Klarälvdalens Datakonsult AB
// SPDX-License-Identifier: GPL-2.0-or-later
#include "systemtrayicon.h"
#include "gpgol_client_debug.h"
-#include "websocketclient.h"
#include <QCoreApplication>
#include <QEvent>
#include <QMenu>
#include <QPointer>
#include <QTimer>
#include <QWidget>
#include <KAboutApplicationDialog>
#include <KAboutData>
#include <KLocalizedString>
static const int ATTENTION_ANIMATION_FRAMES_PER_SEC = 1;
class SystemTrayIcon::Private
{
friend class ::SystemTrayIcon;
SystemTrayIcon *const q;
public:
explicit Private(SystemTrayIcon *qq);
~Private();
private:
bool attentionWanted() const
{
return attentionAnimationTimer.isActive();
}
void setAttentionWantedImpl(bool on)
{
if (on) {
attentionAnimationTimer.start();
} else {
attentionAnimationTimer.stop();
attentionIconShown = false;
q->setIcon(normalIcon);
}
}
void slotActivated(ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger) {
q->doActivated();
}
}
void slotAttentionAnimationTimerTimout()
{
if (attentionIconShown) {
attentionIconShown = false;
q->setIcon(normalIcon);
} else {
attentionIconShown = true;
q->setIcon(attentionIcon);
}
}
private:
bool attentionIconShown;
QMenu menu;
QIcon normalIcon, attentionIcon;
QTimer attentionAnimationTimer;
QPointer<QWidget> mainWindow;
QPointer<QWidget> attentionWindow;
QAction aboutAction;
QAction quitAction;
KAboutApplicationDialog *aboutDialog = nullptr;
};
SystemTrayIcon::Private::Private(SystemTrayIcon *qq)
: q(qq)
, attentionIconShown(false)
, attentionAnimationTimer()
, mainWindow()
, attentionWindow()
, aboutAction(QIcon::fromTheme(QStringLiteral("kleopatra")),
xi18nc("@action:inmenu", "&About <application>%1</application>...", KAboutData::applicationData().displayName()),
q)
, quitAction(QIcon::fromTheme(QStringLiteral("application-exit")),
xi18nc("@action:inmenu", "&Shutdown <application>%1</application>", KAboutData::applicationData().displayName()),
q)
{
Q_SET_OBJECT_NAME(attentionAnimationTimer);
Q_SET_OBJECT_NAME(aboutAction);
Q_SET_OBJECT_NAME(quitAction);
attentionAnimationTimer.setSingleShot(false);
attentionAnimationTimer.setInterval(1000 * ATTENTION_ANIMATION_FRAMES_PER_SEC / 2);
menu.addAction(&aboutAction);
menu.addAction(&quitAction);
q->setContextMenu(&menu);
connect(q, &QSystemTrayIcon::activated, q, [this](QSystemTrayIcon::ActivationReason reason) {
slotActivated(reason);
});
connect(&attentionAnimationTimer, &QTimer::timeout, q, [this]() {
slotAttentionAnimationTimerTimout();
});
connect(&aboutAction, &QAction::triggered, q, &SystemTrayIcon::slotAbout);
connect(&quitAction, &QAction::triggered, QCoreApplication::instance(), &QCoreApplication::quit);
-
- q->setToolTip(WebsocketClient::self().stateDisplay());
}
SystemTrayIcon::Private::~Private()
{
}
SystemTrayIcon::SystemTrayIcon(QObject *p)
: QSystemTrayIcon(p)
, d(std::make_unique<Private>(this))
{
}
SystemTrayIcon::SystemTrayIcon(const QIcon &icon, QObject *p)
: QSystemTrayIcon(icon, p)
, d(std::make_unique<Private>(this))
{
d->normalIcon = d->attentionIcon = icon;
}
SystemTrayIcon::~SystemTrayIcon() = default;
void SystemTrayIcon::setMainWindow(QWidget *mw)
{
if (d->mainWindow) {
return;
}
d->mainWindow = mw;
if (mw) {
mw->installEventFilter(this);
}
doMainWindowSet(mw);
slotEnableDisableActions();
}
QWidget *SystemTrayIcon::mainWindow() const
{
return d->mainWindow;
}
void SystemTrayIcon::setAttentionWindow(QWidget *mw)
{
if (d->attentionWindow) {
return;
}
d->attentionWindow = mw;
if (mw) {
mw->installEventFilter(this);
}
slotEnableDisableActions();
}
QWidget *SystemTrayIcon::attentionWindow() const
{
return d->attentionWindow;
}
bool SystemTrayIcon::eventFilter(QObject *o, QEvent *e)
{
if (o == d->mainWindow)
switch (e->type()) {
case QEvent::Close:
doMainWindowClosed(static_cast<QWidget *>(o));
// fall through:
[[fallthrough]];
case QEvent::Show:
case QEvent::DeferredDelete:
QMetaObject::invokeMethod(this, "slotEnableDisableActions", Qt::QueuedConnection);
default:;
}
else if (o == d->attentionWindow)
switch (e->type()) {
case QEvent::Close:
doAttentionWindowClosed(static_cast<QWidget *>(o));
// fall through:
[[fallthrough]];
case QEvent::Show:
case QEvent::DeferredDelete:
QMetaObject::invokeMethod(this, "slotEnableDisableActions", Qt::QueuedConnection);
default:;
}
return false;
}
void SystemTrayIcon::setAttentionWanted(bool on)
{
if (d->attentionWanted() == on) {
return;
}
qCDebug(GPGOL_CLIENT_LOG) << d->attentionWanted() << "->" << on;
d->setAttentionWantedImpl(on);
}
bool SystemTrayIcon::attentionWanted() const
{
return d->attentionWanted();
}
void SystemTrayIcon::setNormalIcon(const QIcon &icon)
{
if (d->normalIcon.cacheKey() == icon.cacheKey()) {
return;
}
d->normalIcon = icon;
if (!d->attentionWanted() || !d->attentionIconShown) {
setIcon(icon);
}
}
QIcon SystemTrayIcon::normalIcon() const
{
return d->normalIcon;
}
void SystemTrayIcon::setAttentionIcon(const QIcon &icon)
{
if (d->attentionIcon.cacheKey() == icon.cacheKey()) {
return;
}
d->attentionIcon = icon;
if (d->attentionWanted() && d->attentionIconShown) {
setIcon(icon);
}
}
QIcon SystemTrayIcon::attentionIcon() const
{
return d->attentionIcon;
}
void SystemTrayIcon::doMainWindowSet(QWidget *)
{
}
void SystemTrayIcon::doMainWindowClosed(QWidget *)
{
}
void SystemTrayIcon::doAttentionWindowClosed(QWidget *)
{
}
void SystemTrayIcon::slotAbout()
{
if (!d->aboutDialog) {
d->aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData());
d->aboutDialog->setAttribute(Qt::WA_DeleteOnClose);
}
if (d->aboutDialog->isVisible()) {
d->aboutDialog->raise();
} else {
d->aboutDialog->show();
}
}
void SystemTrayIcon::slotEnableDisableActions()
{
}
void SystemTrayIcon::doActivated()
{
Q_ASSERT(mainWindow());
if (mainWindow()->isVisible()) {
mainWindow()->hide();
} else {
mainWindow()->show();
mainWindow()->raise();
}
}
-void SystemTrayIcon::slotStateChanged()
+void SystemTrayIcon::slotStateChanged(const QString &state)
{
- setToolTip(WebsocketClient::self().stateDisplay());
+ setToolTip(state);
}
#include "moc_systemtrayicon.cpp"
diff --git a/client/utils/systemtrayicon.h b/client/utils/systemtrayicon.h
index d3f7cce..4906e12 100644
--- a/client/utils/systemtrayicon.h
+++ b/client/utils/systemtrayicon.h
@@ -1,50 +1,50 @@
// SPDX-FileCopyrightText: 2007, 2009 Klarälvdalens Datakonsult AB
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QSystemTrayIcon>
#include <memory>
class SystemTrayIcon : public QSystemTrayIcon
{
Q_OBJECT
public:
explicit SystemTrayIcon(QObject *parent = nullptr);
explicit SystemTrayIcon(const QIcon &icon, QObject *parent = nullptr);
~SystemTrayIcon() override;
void setMainWindow(QWidget *w);
QWidget *mainWindow() const;
void setAttentionWindow(QWidget *w);
QWidget *attentionWindow() const;
QIcon attentionIcon() const;
QIcon normalIcon() const;
bool attentionWanted() const;
public Q_SLOTS:
void setAttentionIcon(const QIcon &icon);
void setNormalIcon(const QIcon &icon);
void setAttentionWanted(bool);
void slotAbout();
- void slotStateChanged();
+ void slotStateChanged(const QString &state);
protected Q_SLOTS:
virtual void slotEnableDisableActions();
private:
virtual void doMainWindowSet(QWidget *);
virtual void doMainWindowClosed(QWidget *);
virtual void doAttentionWindowClosed(QWidget *);
virtual void doActivated();
private:
bool eventFilter(QObject *, QEvent *) override;
private:
class Private;
const std::unique_ptr<Private> d;
};
diff --git a/client/websocketclient.cpp b/client/websocketclient.cpp
index b9318c1..6cd7c96 100644
--- a/client/websocketclient.cpp
+++ b/client/websocketclient.cpp
@@ -1,343 +1,343 @@
// 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 <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QStandardPaths>
#include <QTimer>
// KDE headers
#include <KLocalizedString>
#include <Libkleo/KeyCache>
#include <MimeTreeParserCore/ObjectTreeParser>
// gpgme headers
#include <gpgme++/key.h>
#include <kmime/message.h>
#include <quuid.h>
#include "draft/draftmanager.h"
#include "editor/composerwindow.h"
#include "editor/composerwindowfactory.h"
#include "emailviewer.h"
#include "ews/ewstypes.h"
#include "gpgol_client_debug.h"
#include "gpgoljs_version.h"
#include "protocol.h"
#include "qnam.h"
#include "websocket_debug.h"
#include "ews/ewsitem.h"
#include "ews/ewspropertyfield.h"
#include "ews/ewsserverversion.h"
#include "ews/ewsxml.h"
using namespace Qt::Literals::StringLiterals;
namespace
{
QStringList trustedEmails(const std::shared_ptr<const Kleo::KeyCache> &keyCache)
{
QStringList emails;
const auto keys = keyCache->keys();
for (const auto &key : keys) {
for (const auto &userId : key.userIDs()) {
if (key.ownerTrust() == GpgME::Key::Ultimate) {
emails << QString::fromLatin1(userId.email()).toLower();
break;
}
}
}
return emails;
}
}
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 globalKeyCache = Kleo::KeyCache::instance();
m_emails = trustedEmails(globalKeyCache);
if (m_emails.isEmpty()) {
qWarning() << "No ultimately keys found in keychain";
return;
}
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();
+ 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);
// TODO remove me
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &errors) {
reply->ignoreSslErrors();
});
}
void WebsocketClient::slotConnected()
{
qCInfo(WEBSOCKET_LOG) << "websocket connected";
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Register)},
{
"arguments"_L1,
QJsonObject{{"emails"_L1, QJsonArray::fromStringList(m_emails)}, {"type"_L1, "nativeclient"_L1}},
},
});
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();
+ 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();
+ 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();
+ 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();
+ Q_EMIT stateChanged(m_stateDisplay);
return;
case Protocol::View: {
const auto email = args["email"_L1].toString();
const auto displayName = args["displayName"_L1].toString();
const auto content = args["body"_L1].toString();
const auto token = args["token"_L1].toString();
if (!m_emailViewer) {
m_emailViewer = new EmailViewer(content, email, displayName, token);
m_emailViewer->setAttribute(Qt::WA_DeleteOnClose);
} else {
m_emailViewer->view(content, email, displayName, token);
}
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 bearerToken = args["token"_L1].toString().toUtf8();
ComposerWindowFactory::self().restoreAutosave(email, displayName, bearerToken);
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 bearerToken = args["token"_L1].toString().toUtf8();
auto dialog = ComposerWindowFactory::self().create(email, displayName, bearerToken);
if (command == Protocol::Reply || command == Protocol::Forward) {
const auto content = args["body"_L1].toString();
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::Info: {
const auto content = args["body"_L1].toString();
KMime::Message::Ptr message(new KMime::Message());
message->setContent(KMime::CRLFtoLF(content.toUtf8()));
message->parse();
MimeTreeParser::ObjectTreeParser treeParser;
treeParser.parseObjectTree(message.get());
const QJsonObject json{{"command"_L1, Protocol::commandToString(Protocol::InfoFetched)},
{"arguments"_L1,
QJsonObject{
{"itemId"_L1, args["itemId"_L1]},
{"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(GPGOLJS_VERSION_STRING)},
}}};
m_webSocket.sendTextMessage(QString::fromUtf8(QJsonDocument(json).toJson()));
return;
}
default:
qCWarning(WEBSOCKET_LOG) << "Unhandled command" << command;
}
}
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()));
}
diff --git a/client/websocketclient.h b/client/websocketclient.h
index a7d2dc6..7719ce1 100644
--- a/client/websocketclient.h
+++ b/client/websocketclient.h
@@ -1,64 +1,64 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QPointer>
#include <QWebSocket>
#include <chrono>
class EmailViewer;
using namespace std::chrono;
class WebsocketClient : public QObject
{
Q_OBJECT
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(QString stateDisplay READ stateDisplay NOTIFY stateChanged)
public:
enum State {
Connected,
NotConnected,
};
static WebsocketClient &self(const QUrl &url = {}, const QString &clientId = {});
State state() const;
QString stateDisplay() const;
/// \params fromEmail The email address who should send this EWS request.
/// \params requestId Identifier of the request, will be send back in ewsResponseReceived
/// \params requestBody The SOAP request body.
bool sendEWSRequest(const QString &fromEmail, const QString &requestId, const QString &requestBody);
void sendViewerClosed(const QString &fromEmail);
void sendViewerOpened(const QString &fromEmail);
Q_SIGNALS:
- void stateChanged();
+ void stateChanged(const QString &state);
void ewsResponseReceived(const QString &requestId, const QString &responseBody);
private Q_SLOTS:
void slotConnected();
void slotErrorOccurred(QAbstractSocket::SocketError error);
void slotTextMessageReceived(QString message);
private:
explicit WebsocketClient(const QUrl &url, const QString &clientId);
void reconnect();
bool m_wasConnected = false;
QWebSocket m_webSocket;
QUrl m_url;
QStringList m_emails;
QString m_clientId;
std::chrono::milliseconds m_delay = 2000ms;
State m_state = NotConnected;
QString m_stateDisplay;
QPointer<EmailViewer> m_emailViewer;
};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jan 12, 10:31 PM (1 d, 7 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c2/d3/a4ae0e6881e9d5221d22e6d9fc11
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment