Page MenuHome GnuPG

No OneTemporary

diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index dbf7452..8847668 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -1,281 +1,285 @@
# SPDX-FileCopyrightText: 2023 g10 code GmbH
# SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
add_library(gpgol-client-static STATIC)
target_sources(gpgol-client-static PRIVATE
websocketclient.cpp
websocketclient.h
webserver.h
webserver.cpp
+ messagedispatcher.cpp
+ messagedispatcher.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
# HTTP Controller
controllers/emailcontroller.cpp
controllers/emailcontroller.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/ewsmailfactory.cpp
ews/ewsmailfactory.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
# Editor
editor/addresseelineedit.cpp
editor/addresseelineedit.h
editor/addresseelineeditmanager.cpp
editor/addresseelineeditmanager.h
editor/composer.cpp
editor/composer.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/richtextcomposerng.cpp
editor/richtextcomposerng.h
editor/richtextcomposersignatures.cpp
editor/richtextcomposersignatures.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/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
)
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 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)
install(FILES composerui.rc DESTINATION ${KDE_INSTALL_KXMLGUIDIR}/gpgol-client)
target_sources(gpgol-client-static PUBLIC ${gpgol-client-static_SRCS})
target_link_libraries(gpgol-client-static PUBLIC
common
Qt6::HttpServer
Qt6::Widgets
Qt6::PrintSupport
Qt6::WebSockets
KF6::JobWidgets
KF6::CalendarCore
KF6::ConfigCore
KF6::ConfigGui
KF6::Contacts
KF6::Completion
KF6::CoreAddons
KF6::ColorScheme
KF6::Codecs
KF6::GuiAddons
KF6::SonnetUi
KF6::WidgetsAddons
KF6::XmlGui
KF6::Archive
KF6::TextAutoCorrectionCore
KPim6::MimeTreeParserWidgets
KPim6::Libkleo
KPim6::Libkdepim
KPim6::PimTextEdit
)
add_executable(gpgol-client main.cpp)
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})
diff --git a/client/editor/composerviewbase.cpp b/client/editor/composerviewbase.cpp
index 372d2b6..f31e2f1 100644
--- a/client/editor/composerviewbase.cpp
+++ b/client/editor/composerviewbase.cpp
@@ -1,1441 +1,1411 @@
/*
SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "composerviewbase.h"
-#include "../qnam.h"
#include "attachment/attachmentcontrollerbase.h"
#include "attachment/attachmentmodel.h"
#include "composer.h"
-#include "ews/ewsmailfactory.h"
#include "mailtemplates.h"
+#include "messagedispatcher.h"
#include "nodehelper.h"
#include "part/globalpart.h"
#include "part/infopart.h"
#include "richtextcomposerng.h"
#include "richtextcomposersignatures.h"
#include "signaturecontroller.h"
#include "util.h"
#include "util_p.h"
#include "messagecomposersettings.h"
#include "recipientseditor.h"
#include "identity/identity.h"
#include <KCursorSaver>
#include <KEmailAddress>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerImages>
#include <MimeTreeParserCore/ObjectTreeParser>
#include <Sonnet/DictionaryComboBox>
#include "editor_debug.h"
#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyResolverCore>
#include <libkleo/enum.h>
#include <libkleo/keyresolver.h>
#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/Protocol>
#include <global.h>
#include <gpgme++/context.h>
#include <gpgme++/importresult.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QDir>
-#include <QNetworkReply>
#include <QSaveFile>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTimer>
#include <QUuid>
-#include <qjsondocument.h>
#include "draft/draftmanager.h"
using namespace MessageComposer;
using namespace Qt::Literals::StringLiterals;
ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
: QObject(parent)
, m_msg(KMime::Message::Ptr(new KMime::Message))
, m_parentWidget(parentGui)
, m_cryptoMessageFormat(Kleo::AutoFormat)
, m_autoSaveInterval(60000) // default of 1 min
{
m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
initAutoSave();
connect(this, &ComposerViewBase::composerCreated, this, &ComposerViewBase::slotComposerCreated);
}
ComposerViewBase::~ComposerViewBase() = default;
bool ComposerViewBase::isComposing() const
{
return !m_composers.isEmpty();
}
void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg)
{
if (m_attachmentModel) {
const auto attachments{m_attachmentModel->attachments()};
for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
if (!m_attachmentModel->removeAttachment(attachment)) {
qCWarning(EDITOR_LOG) << "Attachment not found.";
}
}
}
m_msg = msg;
if (m_recipientsEditor) {
m_recipientsEditor->clear();
bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), Recipient::To);
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), Recipient::Cc);
}
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), Recipient::Bcc);
}
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), Recipient::ReplyTo);
}
m_recipientsEditor->setFocusBottom();
Q_EMIT tooManyRecipient(resultTooManyRecipients);
}
m_subject = m_msg->subject()->asUnicodeString();
// First, we copy the message and then parse it to the object tree parser.
// The otp gets the message text out of it, in textualContent(), and also decrypts
// the message if necessary.
auto msgContent = new KMime::Content;
msgContent->setContent(m_msg->encodedContent());
msgContent->parse();
// Load the attachments
const auto attachments{msgContent->attachments()};
for (const auto &att : attachments) {
addAttachmentPart(att);
}
// Set the HTML text and collect HTML images
bool isHtml = false;
const auto body = MailTemplates::body(msg, isHtml);
if (isHtml) {
Q_EMIT enableHtml();
editor()->activateRichText();
qWarning() << "called" << body;
} else {
editor()->switchToPlainText();
Q_EMIT disableHtml(LetUserConfirm);
}
editor()->setText(body);
if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
}
delete msgContent;
}
void ComposerViewBase::saveMailSettings()
{
auto header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(m_identity.uoid()), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Identity-Name");
header->fromUnicodeString(m_identity.identityName(), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Dictionary");
header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
m_msg->setHeader(header);
// Save the quote prefix which is used for this message. Each message can have
// a different quote prefix, for example depending on the original sender.
if (m_editor->quotePrefixName().isEmpty()) {
m_msg->removeHeader("X-KMail-QuotePrefix");
} else {
header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
m_msg->setHeader(header);
}
if (m_editor->composerControler()->isFormattingUsed()) {
qCDebug(EDITOR_LOG) << "HTML mode";
header = new KMime::Headers::Generic("X-KMail-Markup");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
m_msg->setHeader(header);
} else {
m_msg->removeHeader("X-KMail-Markup");
qCDebug(EDITOR_LOG) << "Plain text";
}
}
void ComposerViewBase::clearFollowUp()
{
mFollowUpDate = QDate();
}
void ComposerViewBase::send()
{
KCursorSaver saver(Qt::WaitCursor);
saveMailSettings();
if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
const QString keepBtnText =
m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
"<p>do you want to delete your markup?</p></qt>"),
i18nc("@title:window", "Sign/Encrypt Message?"),
KGuiItem(yesBtnText),
KGuiItem(keepBtnText));
if (KMessageBox::Cancel == ret) {
return;
}
if (KMessageBox::ButtonCode::SecondaryAction == ret) {
m_encrypt = false;
m_sign = false;
} else {
Q_EMIT disableHtml(NoConfirmationNeeded);
}
}
readyForSending();
}
void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
{
m_customHeader = customHeader;
}
void ComposerViewBase::readyForSending()
{
qCDebug(EDITOR_LOG) << "Entering readyForSending";
if (!m_msg) {
qCDebug(EDITOR_LOG) << "m_msg == 0!";
return;
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(EDITOR_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
return;
}
mExpandedFrom = from();
mExpandedTo = m_recipientsEditor->recipientStringList(Recipient::To);
mExpandedCc = m_recipientsEditor->recipientStringList(Recipient::Cc);
mExpandedBcc = m_recipientsEditor->recipientStringList(Recipient::Bcc);
mExpandedReplyTo = m_recipientsEditor->recipientStringList(Recipient::ReplyTo);
Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
// checks for emptiness before calling it
// so just ensure it actually is empty
// and document it
// we first figure out if we need to create multiple messages with different crypto formats
// if so, we create a composer per format
// if we aren't signing or encrypting, this just returns a single empty message
if (m_neverEncrypt) {
auto composer = new MessageComposer::Composer;
composer->setNoCrypto(true);
m_composers.append(composer);
slotComposerCreated();
} else {
generateCryptoMessages();
}
}
void ComposerViewBase::slotComposerCreated()
{
if (m_composers.isEmpty()) {
Q_EMIT failed(i18n("It was not possible to create a message composer."));
return;
}
// Compose each message and prepare it for queueing, sending, or storing
// working copy in case composers instantly emit result
const auto composers = m_composers;
for (MessageComposer::Composer *composer : composers) {
fillComposer(composer, UseExpandedRecipients, false);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
composer->start();
qCDebug(EDITOR_LOG) << "Started a composer for sending!";
}
}
namespace
{
// helper methods for reading encryption settings
inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
} // nameless namespace
Kleo::KeyResolver *ComposerViewBase::fillKeyResolver(bool encryptSomething)
{
auto keyResolverCore = new Kleo::KeyResolver(m_encrypt, m_sign, GpgME::UnknownProtocol, m_encrypt);
keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);
QStringList signingKeys, encryptionKeys;
if (m_cryptoMessageFormat & Kleo::AnyOpenPGP) {
if (!m_identity.pgpSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(m_identity.pgpSigningKey()));
}
if (!m_identity.pgpEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(m_identity.pgpEncryptionKey()));
}
}
if (m_cryptoMessageFormat & Kleo::AnySMIME) {
if (!m_identity.smimeSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(m_identity.smimeSigningKey()));
}
if (!m_identity.smimeEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(m_identity.smimeEncryptionKey()));
}
}
keyResolverCore->setSender(m_identity.fullEmailAddr());
const auto normalized = GpgME::UserID::addrSpecFromString(m_identity.fullEmailAddr().toUtf8().constData());
const auto normalizedSender = QString::fromUtf8(normalized.c_str());
keyResolverCore->setSigningKeys(signingKeys);
keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{normalizedSender, encryptionKeys}}}});
if (encryptSomething) {
QStringList recipients;
const auto lst = m_recipientsEditor->lines();
for (auto line : lst) {
auto recipient = line->data().dynamicCast<Recipient>();
recipients.push_back(recipient->email());
}
keyResolverCore->setRecipients(recipients);
}
return keyResolverCore;
}
void ComposerViewBase::generateCryptoMessages()
{
bool canceled = false;
qCDebug(EDITOR_LOG) << "filling crypto info";
connect(expiryChecker().get(),
&Kleo::ExpiryChecker::expiryMessage,
this,
[&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
if (!isNewMessage) {
return;
}
if (canceled) {
return;
}
QString title;
QString dontAskAgainName;
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
dontAskAgainName = QStringLiteral("own key expires soon warning");
} else {
dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
}
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
} else {
title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
}
if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
== KMessageBox::Cancel) {
canceled = true;
}
});
bool signSomething = m_sign;
bool doSignCompletely = m_sign;
bool encryptSomething = m_encrypt;
bool doEncryptCompletely = m_encrypt;
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
if (attachment->isSigned()) {
signSomething = true;
} else {
doEncryptCompletely = false;
}
if (attachment->isEncrypted()) {
encryptSomething = true;
} else {
doSignCompletely = false;
}
}
}
qCInfo(EDITOR_LOG) << "Encrypting completely:" << doEncryptCompletely;
qCInfo(EDITOR_LOG) << "Signing completely:" << doSignCompletely;
// No encryption or signing is needed
if (!signSomething && !encryptSomething) {
m_composers = {new MessageComposer::Composer};
Q_EMIT composerCreated();
return;
}
auto keyResolver = fillKeyResolver(encryptSomething);
keyResolver->start(true);
connect(keyResolver, &Kleo::KeyResolver::keysResolved, this, [this, encryptSomething, signSomething, keyResolver](bool success, bool sendUnencrypted) {
if (!success) {
qCDebug(EDITOR_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
return;
}
qCDebug(EDITOR_LOG) << "done resolving keys.";
if (sendUnencrypted) {
m_composers = {new MessageComposer::Composer};
Q_EMIT composerCreated();
return;
}
const auto result = keyResolver->result();
QList<MessageComposer::Composer *> composers;
auto signingKeyFinder = [&result](const GpgME::Protocol protocol) -> std::optional<GpgME::Key> { // clazy:excludeall=lambda-in-connect
for (const auto &key : result.signingKeys) {
if (key.protocol() == protocol) {
return key;
}
}
return std::nullopt;
};
if (encryptSomething || signSomething) {
if (encryptSomething) {
std::vector<GpgME::Key> pgpKeys;
QStringList pgpRecipients;
std::vector<GpgME::Key> smimeKeys;
QStringList smimeRecipients;
for (const auto &[recipient, keys] : result.encryptionKeys.asKeyValueRange()) {
const auto recipientKeys = result.encryptionKeys[recipient];
if (recipientKeys.size() > 1) {
// TODO Carl group handling
} else {
const auto &key = recipientKeys[0];
if (key.protocol() == GpgME::CMS) {
smimeRecipients.append(recipient);
smimeKeys.push_back(recipientKeys[0]);
} else {
pgpRecipients.append(recipient);
pgpKeys.push_back(recipientKeys[0]);
}
}
}
Q_ASSERT(smimeRecipients.count() == (int)smimeKeys.size());
Q_ASSERT(pgpRecipients.count() == (int)pgpKeys.size());
if (pgpRecipients.count() > 0) {
auto composer = new MessageComposer::Composer;
composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>(pgpRecipients, pgpKeys)});
auto pgpSigningKey = signingKeyFinder(GpgME::OpenPGP);
if (signSomething && pgpSigningKey) {
composer->setSigningKeys({*pgpSigningKey});
}
composer->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
composer->setSignAndEncrypt(signSomething, encryptSomething);
composers << composer;
}
if (smimeRecipients.count() > 0) {
auto composer = new MessageComposer::Composer;
composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>(smimeRecipients, smimeKeys)});
auto smimeSigningKey = signingKeyFinder(GpgME::CMS);
if (signSomething && smimeSigningKey) {
composer->setSigningKeys({*smimeSigningKey});
}
composer->setCryptoMessageFormat(Kleo::SMIMEFormat);
composer->setSignAndEncrypt(signSomething, encryptSomething);
composers << composer;
}
} else {
// signing only
Q_ASSERT(signSomething);
Q_ASSERT(!encryptSomething);
auto composer = new MessageComposer::Composer;
composer->setSignAndEncrypt(signSomething, encryptSomething);
Q_ASSERT(result.protocol != GpgME::UnknownProtocol); // No mixed protocol allowed here
const auto signingKey = signingKeyFinder(result.protocol);
Q_ASSERT(signingKey);
composer->setSigningKeys({*signingKey});
qDebug() << result.protocol;
composer->setCryptoMessageFormat(result.protocol == GpgME::OpenPGP ? Kleo::OpenPGPMIMEFormat : Kleo::SMIMEFormat);
composers << composer;
}
} else {
auto composer = new MessageComposer::Composer;
composers.append(composer);
// If we canceled sign or encrypt be sure to change status in attachment.
markAllAttachmentsForSigning(false);
markAllAttachmentsForEncryption(false);
}
if (composers.isEmpty() && (signSomething || encryptSomething)) {
Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
}
m_composers = composers;
Q_EMIT composerCreated();
});
}
void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
{
globalPart->setParentWidgetForGui(m_parentWidget);
globalPart->setCharsets(m_charsets);
globalPart->setMDNRequested(m_mdnRequested);
globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
}
void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
{
// TODO splitAddressList and expandAliases ugliness should be handled by a
// special AddressListEdit widget... (later: see RecipientsEditor)
if (expansion == UseExpandedRecipients) {
infoPart->setFrom(mExpandedFrom);
infoPart->setTo(mExpandedTo);
infoPart->setCc(mExpandedCc);
infoPart->setBcc(mExpandedBcc);
infoPart->setReplyTo(mExpandedReplyTo);
} else {
infoPart->setFrom(from());
infoPart->setTo(m_recipientsEditor->recipientStringList(Recipient::To));
infoPart->setCc(m_recipientsEditor->recipientStringList(Recipient::Cc));
infoPart->setBcc(m_recipientsEditor->recipientStringList(Recipient::Bcc));
infoPart->setReplyTo(m_recipientsEditor->recipientStringList(Recipient::ReplyTo));
}
infoPart->setSubject(subject());
infoPart->setUserAgent(QStringLiteral("KMail"));
infoPart->setUrgent(m_urgent);
if (auto inReplyTo = m_msg->inReplyTo(false)) {
infoPart->setInReplyTo(inReplyTo->asUnicodeString());
}
if (auto references = m_msg->references(false)) {
infoPart->setReferences(references->asUnicodeString());
}
KMime::Headers::Base::List extras;
if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
extras << hdr;
}
if (auto hdr = m_msg->organization(false)) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-Face")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("Face")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
extras << hdr;
}
infoPart->setExtraHeaders(extras);
}
void ComposerViewBase::slotSendComposeResult(KJob *job)
{
Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
auto composer = static_cast<MessageComposer::Composer *>(job);
if (composer->error() != MessageComposer::Composer::NoError) {
qCDebug(EDITOR_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
}
if (composer->error() == MessageComposer::Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully.
qCDebug(EDITOR_LOG) << "NoError.";
const int numberOfMessage(composer->resultMessages().size());
for (int i = 0; i < numberOfMessage; ++i) {
queueMessage(composer->resultMessages().at(i));
}
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(EDITOR_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"));
} else {
qCDebug(EDITOR_LOG) << "other Error." << composer->error();
QString msg;
if (composer->error() == MessageComposer::Composer::BugError) {
msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
} else {
msg = i18n("Could not compose message: %1", job->errorString());
}
Q_EMIT failed(msg);
}
if (!composer->gnupgHome().isEmpty()) {
QDir dir(composer->gnupgHome());
dir.removeRecursively();
}
m_composers.removeAll(composer);
}
-void ComposerViewBase::setBearerToken(const QByteArray &bearerToken)
-{
- m_bearerToken = bearerToken;
-}
-
-void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message)
-{
- qWarning().noquote() << message->encodedContent();
-
- auto soapRequestBody = EwsMailFactory::create(message);
-
- QNetworkRequest sendMailRequest(QUrl(u"https://127.0.0.1:5656/socket-web"_s));
- sendMailRequest.setHeader(QNetworkRequest::ContentTypeHeader, u"application/xml"_s);
- sendMailRequest.setRawHeader("X-TOKEN", m_bearerToken);
- sendMailRequest.setRawHeader("X-EMAIL", from().toUtf8());
-
- const QJsonDocument payload(QJsonObject{
- {"type"_L1, "ews"_L1},
- {"payload"_L1, soapRequestBody},
- {"id"_L1, mailId()},
- });
-
- auto sendMailResponse = qnam->post(sendMailRequest, payload.toJson());
-
- // TODO remove me
- QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &errors) {
- Q_UNUSED(errors);
- reply->ignoreSslErrors();
- });
-
- connect(sendMailResponse, &QNetworkReply::finished, this, [this, sendMailResponse]() {
- qDebug() << sendMailResponse << sendMailResponse->error() << sendMailResponse->errorString();
- if (sendMailResponse->error() != QNetworkReply::NoError) {
- Q_EMIT failed(i18nc("Error message", "There were a problem sending the message: %1", sendMailResponse->errorString()));
- return;
- }
-
- Q_EMIT sentSuccessfully();
- });
-
- qCDebug(EDITOR_LOG) << "Request body" << soapRequestBody;
-}
-
void ComposerViewBase::initAutoSave()
{
qCDebug(EDITOR_LOG) << "initialising autosave";
// Ensure that the autosave directory exists.
QDir dataDirectory(DraftManager::autosaveDirectory());
if (!dataDirectory.exists(QStringLiteral("autosave"))) {
qCDebug(EDITOR_LOG) << "Creating autosave directory.";
dataDirectory.mkdir(QStringLiteral("autosave"));
}
updateAutoSave();
}
QDate ComposerViewBase::followUpDate() const
{
return mFollowUpDate;
}
void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
{
mFollowUpDate = followUpDate;
}
Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
{
return m_dictionary;
}
void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
{
m_dictionary = dictionary;
}
void ComposerViewBase::updateAutoSave()
{
if (m_autoSaveInterval == 0) {
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
} else {
if (!m_autoSaveTimer) {
m_autoSaveTimer = new QTimer(this);
connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
}
m_autoSaveTimer->start(m_autoSaveInterval);
}
}
void ComposerViewBase::cleanupAutoSave()
{
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
if (!mailIdIsEmpty()) {
qCDebug(EDITOR_LOG) << "deleting autosave files" << mailId();
// Delete the autosave files
QDir autoSaveDir(DraftManager::autosaveDirectory());
// Filter out only this composer window's autosave files
const QStringList autoSaveFilter{mailId() + QLatin1String("*")};
autoSaveDir.setNameFilters(autoSaveFilter);
// Return the files to be removed
const QStringList autoSaveFiles = autoSaveDir.entryList();
qCDebug(EDITOR_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
// Delete each file
for (const QString &file : autoSaveFiles) {
autoSaveDir.remove(file);
}
m_autoSaveUUID.clear();
}
}
void ComposerViewBase::generateMessage(std::function<void(QList<KMime::Message::Ptr>)> callback)
{
auto composer = new Composer();
fillComposer(composer);
composer->setAutoSave(true);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, [composer, callback]() {
callback(composer->resultMessages());
});
composer->start();
}
void ComposerViewBase::autoSaveMessage()
{
qCDebug(EDITOR_LOG) << "Autosaving message";
if (m_autoSaveTimer) {
m_autoSaveTimer->stop();
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(EDITOR_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
return;
}
auto composer = new Composer();
fillComposer(composer);
composer->setAutoSave(true);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
composer->start();
}
void ComposerViewBase::slotSaveDraft()
{
qCDebug(EDITOR_LOG) << "Saving draft";
m_composers.clear();
auto composer = new Composer();
fillComposer(composer);
composer->setDraft(true);
composer->setSignAndEncrypt(false, true);
const auto normalized = GpgME::UserID::addrSpecFromString(m_identity.fullEmailAddr().toUtf8().constData());
const auto normalizedSender = QString::fromUtf8(normalized.c_str());
auto encryptionKeys = Kleo::KeyCache::instance()->findByEMailAddress(normalizedSender.toStdString());
std::vector<GpgME::Key> filteredEncryptionKeys;
// ensure all keys are valid
for (const auto &key : encryptionKeys) {
if (strlen(key.userID(0).email()) > 0) {
filteredEncryptionKeys.push_back(key);
}
}
Q_ASSERT(!filteredEncryptionKeys.empty());
composer->setEncryptionKeys({QPair<QStringList, std::vector<GpgME::Key>>{{normalizedSender}, filteredEncryptionKeys}});
composer->setCryptoMessageFormat(m_cryptoMessageFormat);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSaveDraftComposeResult);
composer->start();
}
void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
{
m_autoSaveUUID = fileName;
Q_EMIT modified(true);
}
void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
{
using MessageComposer::Composer;
Q_ASSERT(dynamic_cast<Composer *>(job));
auto composer = static_cast<Composer *>(job);
if (composer->error() == Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully. Only save the first message, there should
// only be one anyway, since crypto is disabled.
qCDebug(EDITOR_LOG) << "NoError.";
writeAutoSaveToDisk(composer->resultMessages().constFirst());
Q_ASSERT(composer->resultMessages().size() == 1);
if (m_autoSaveInterval > 0) {
updateAutoSave();
}
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(EDITOR_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
} else {
qCDebug(EDITOR_LOG) << "other Error.";
Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
}
m_composers.removeAll(composer);
}
void ComposerViewBase::slotSaveDraftComposeResult(KJob *job)
{
using MessageComposer::Composer;
Q_ASSERT(dynamic_cast<Composer *>(job));
auto composer = static_cast<Composer *>(job);
if (composer->error() == Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully. Only save the first message, there should
// only be one anyway, since crypto is disabled.
qCDebug(EDITOR_LOG) << "NoError.";
writeDraftToDisk(composer->resultMessages().constFirst());
Q_ASSERT(composer->resultMessages().size() == 1);
- Q_EMIT sentSuccessfully();
+ Q_EMIT closeWindow();
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(EDITOR_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"), Draft);
} else {
qCDebug(EDITOR_LOG) << "other Error." << composer->error();
Q_EMIT failed(i18n("Could not save message as draft: %1", job->errorString()), Draft);
}
m_composers.removeAll(composer);
}
void ComposerViewBase::writeDraftToDisk(const KMime::Message::Ptr &message)
{
QDir().mkpath(DraftManager::draftDirectory());
const QString filename = DraftManager::draftDirectory() + mailId();
QSaveFile file(filename);
QString errorMessage;
qCDebug(EDITOR_LOG) << "Writing message to disk as" << filename;
if (file.open(QIODevice::WriteOnly)) {
file.setPermissions(QFile::ReadUser | QFile::WriteUser);
if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
errorMessage = i18n("Could not write all data to file.");
} else {
if (!file.commit()) {
errorMessage = i18n("Could not finalize the file.");
}
}
} else {
errorMessage = i18n("Could not open file.");
}
if (!errorMessage.isEmpty()) {
qCWarning(EDITOR_LOG) << "Saving draft failed:" << errorMessage << file.errorString() << "mailId" << mailId();
if (!m_autoSaveErrorShown) {
KMessageBox::error(m_parentWidget,
i18n("Saving the draft message as %1 failed.\n"
"%2\n"
"Reason: %3",
filename,
errorMessage,
file.errorString()),
i18nc("@title:window", "Autosaving Message Failed"));
// Error dialog shown, hide the errors the next time
m_autoSaveErrorShown = true;
}
} else {
// No error occurred, the next error should be shown again
m_autoSaveErrorShown = false;
}
file.commit();
message->clear();
}
void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
{
QDir().mkpath(DraftManager::autosaveDirectory());
const QString filename = DraftManager::autosaveDirectory() + mailId();
QSaveFile file(filename);
QString errorMessage;
qCDebug(EDITOR_LOG) << "Writing message to disk as" << filename;
if (file.open(QIODevice::WriteOnly)) {
file.setPermissions(QFile::ReadUser | QFile::WriteUser);
if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
errorMessage = i18n("Could not write all data to file.");
} else {
if (!file.commit()) {
errorMessage = i18n("Could not finalize the file.");
}
}
} else {
errorMessage = i18n("Could not open file.");
}
if (!errorMessage.isEmpty()) {
qCWarning(EDITOR_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << "mailId=" << mailId();
if (!m_autoSaveErrorShown) {
KMessageBox::error(m_parentWidget,
i18n("Autosaving the message as %1 failed.\n"
"%2\n"
"Reason: %3",
filename,
errorMessage,
file.errorString()),
i18nc("@title:window", "Autosaving Message Failed"));
// Error dialog shown, hide the errors the next time
m_autoSaveErrorShown = true;
}
} else {
// No error occurred, the next error should be shown again
m_autoSaveErrorShown = false;
}
file.commit();
message->clear();
}
void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
{
Q_UNUSED(comment)
qCDebug(EDITOR_LOG) << "adding attachment with url:" << url;
if (sync) {
m_attachmentController->addAttachmentUrlSync(url);
} else {
m_attachmentController->addAttachment(url);
}
}
void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
{
MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
if (!data.isEmpty()) {
attachment->setName(name);
attachment->setFileName(filename);
attachment->setData(data);
attachment->setCharset(charset.toLatin1());
attachment->setMimeType(mimeType);
// TODO what about the other fields?
m_attachmentController->addAttachment(attachment);
}
}
void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
{
MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
// if it is a digest or a full message, use the encodedContent() of the attachment,
// which already has the proper headers
part->setData(partToAttach->encodedContent());
} else {
part->setData(partToAttach->decodedContent());
}
part->setMimeType(partToAttach->contentType(false)->mimeType());
if (auto cd = partToAttach->contentDescription(false)) {
part->setDescription(cd->asUnicodeString());
}
if (auto ct = partToAttach->contentType(false)) {
if (ct->hasParameter(QStringLiteral("name"))) {
part->setName(ct->parameter(QStringLiteral("name")));
}
}
if (auto cd = partToAttach->contentDisposition(false)) {
part->setFileName(cd->filename());
part->setInline(cd->disposition() == KMime::Headers::CDinline);
}
if (part->name().isEmpty() && !part->fileName().isEmpty()) {
part->setName(part->fileName());
}
if (part->fileName().isEmpty() && !part->name().isEmpty()) {
part->setFileName(part->name());
}
m_attachmentController->addAttachment(part);
}
+void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message)
+{
+ Q_ASSERT(m_messageDispatcher);
+ m_messageDispatcher->dispatch(message, from(), mailId());
+}
+
void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
{
fillComposer(composer, UseUnExpandedRecipients, false);
}
void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
{
fillInfoPart(composer->infoPart(), expansion);
fillGlobalPart(composer->globalPart());
m_editor->fillComposerTextPart(composer->textPart());
fillInfoPart(composer->infoPart(), expansion);
if (m_attachmentModel) {
composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
} else {
qDebug() << "fillComposer"
<< "no model";
}
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::to() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::To));
}
return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::cc() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Cc));
}
return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::bcc() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Bcc));
}
return {};
}
QString ComposerViewBase::from() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_from);
}
QString ComposerViewBase::replyTo() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::ReplyTo));
}
return {};
}
QString ComposerViewBase::subject() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_subject);
}
void ComposerViewBase::setIdentity(const KIdentityManagementCore::Identity &identity)
{
m_identity = identity;
}
KIdentityManagementCore::Identity ComposerViewBase::identity() const
{
return m_identity;
}
void ComposerViewBase::setParentWidgetForGui(QWidget *w)
{
m_parentWidget = w;
}
void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
{
m_attachmentController = controller;
}
MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
{
return m_attachmentController;
}
void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
{
m_attachmentModel = model;
}
MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
{
return m_attachmentModel;
}
void ComposerViewBase::setRecipientsEditor(RecipientsEditor *recEditor)
{
m_recipientsEditor = recEditor;
}
RecipientsEditor *ComposerViewBase::recipientsEditor()
{
return m_recipientsEditor;
}
void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
{
m_signatureController = sigController;
}
MessageComposer::SignatureController *ComposerViewBase::signatureController()
{
return m_signatureController;
}
void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, Recipient::Type type)
{
QString oldIdentList;
QString newIdentList;
if (type == Recipient::Bcc) {
oldIdentList = oldIdent.bcc();
newIdentList = ident.bcc();
} else if (type == Recipient::Cc) {
oldIdentList = oldIdent.cc();
newIdentList = ident.cc();
} else if (type == Recipient::ReplyTo) {
oldIdentList = oldIdent.replyToAddr();
newIdentList = ident.replyToAddr();
} else {
return;
}
if (oldIdentList != newIdentList) {
const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
for (const KMime::Types::Mailbox &recipient : oldRecipients) {
m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
}
const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
for (const KMime::Types::Mailbox &recipient : newRecipients) {
m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
}
m_recipientsEditor->setFocusBottom();
}
}
void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
{
updateRecipients(ident, oldIdent, Recipient::Bcc);
updateRecipients(ident, oldIdent, Recipient::Cc);
updateRecipients(ident, oldIdent, Recipient::ReplyTo);
KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
// replace existing signatures
const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
// Just append the signature if there was no old signature
if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
signatureController()->applySignature(newSig);
}
m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
}
void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
{
m_editor = editor;
m_editor->document()->setModified(false);
}
MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
{
return m_editor;
}
void ComposerViewBase::setFrom(const QString &from)
{
m_from = from;
}
void ComposerViewBase::setSubject(const QString &subject)
{
m_subject = subject;
}
void ComposerViewBase::setAutoSaveInterval(int interval)
{
m_autoSaveInterval = interval;
}
void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
{
m_sign = sign;
m_encrypt = encrypt;
m_cryptoMessageFormat = format;
m_neverEncrypt = neverEncryptDrafts;
}
void ComposerViewBase::setCharsets(const QList<QByteArray> &charsets)
{
m_charsets = charsets;
}
void ComposerViewBase::setMDNRequested(bool mdnRequested)
{
m_mdnRequested = mdnRequested;
}
void ComposerViewBase::setUrgent(bool urgent)
{
m_urgent = urgent;
}
int ComposerViewBase::autoSaveInterval() const
{
return m_autoSaveInterval;
}
//-----------------------------------------------------------------------------
void ComposerViewBase::collectImages(KMime::Content *root)
{
if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
KMime::Content *parentnode = n->parent();
if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
while (node) {
if (node->contentType()->isImage()) {
qCDebug(EDITOR_LOG) << "found image in multipart/related : " << node->contentType()->name();
QImage img;
img.loadFromData(node->decodedContent());
m_editor->composerControler()->composerImages()->loadImage(
img,
QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
node->contentType()->name());
}
node = MessageCore::NodeHelper::nextSibling(node);
}
}
}
}
//-----------------------------------------------------------------------------
bool ComposerViewBase::inlineSigningEncryptionSelected() const
{
if (!m_sign && !m_encrypt) {
return false;
}
return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
}
bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
{
if (attachmentKeywords.isEmpty()) {
return false;
}
if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
return false;
}
return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
}
ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
{
if (!hasMissingAttachments(attachmentKeywords)) {
return NoMissingAttachmentFound;
}
const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
i18n("The message you have composed seems to refer to an "
"attached file but you have not attached anything.\n"
"Do you want to attach a file to your message?"),
i18nc("@title:window", "File Attachment Reminder"),
KGuiItem(i18n("&Attach File..."), QLatin1String("mail-attachment")),
KGuiItem(i18n("&Send as Is"), QLatin1String("mail-send")));
if (rc == KMessageBox::Cancel) {
return FoundMissingAttachmentAndCancel;
}
if (rc == KMessageBox::ButtonCode::PrimaryAction) {
m_attachmentController->showAddAttachmentFileDialog();
return FoundMissingAttachmentAndAddedAttachment;
}
return FoundMissingAttachmentAndSending;
}
void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
{
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
attachment->setSigned(sign);
}
}
}
void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
{
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
attachment->setEncrypted(encrypt);
}
}
}
bool ComposerViewBase::requestDeleveryConfirmation() const
{
return m_requestDeleveryConfirmation;
}
void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
{
m_requestDeleveryConfirmation = requestDeleveryConfirmation;
}
KMime::Message::Ptr ComposerViewBase::msg() const
{
return m_msg;
}
std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
encryptKeyNearExpiryWarningThresholdInDays(),
encryptRootCertNearExpiryWarningThresholdInDays(),
encryptChainCertNearExpiryWarningThresholdInDays()}});
}
return mExpiryChecker;
}
QString ComposerViewBase::mailId() const
{
if (m_autoSaveUUID.isEmpty()) {
m_autoSaveUUID = QUuid::createUuid().toString(QUuid::WithoutBraces);
}
return m_autoSaveUUID;
}
bool ComposerViewBase::mailIdIsEmpty() const
{
return m_autoSaveUUID.isEmpty();
}
+MessageDispatcher *ComposerViewBase::messageDispatcher() const
+{
+ return m_messageDispatcher;
+}
+
+void ComposerViewBase::setMessageDispatcher(MessageDispatcher *messageDispatcher)
+{
+ m_messageDispatcher = messageDispatcher;
+}
+
#include "moc_composerviewbase.cpp"
diff --git a/client/editor/composerviewbase.h b/client/editor/composerviewbase.h
index d22374b..49d5c51 100644
--- a/client/editor/composerviewbase.h
+++ b/client/editor/composerviewbase.h
@@ -1,369 +1,371 @@
/*
SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include "recipient.h"
#include <KMime/Message>
#include "identity/identity.h"
#include <Libkleo/Enum>
#include <QList>
#include <QObject>
#include <QUrl>
class QTimer;
class KJob;
class QWidget;
+class MessageDispatcher;
class ComposerViewBaseTest;
namespace Sonnet
{
class DictionaryComboBox;
}
namespace Kleo
{
class ExpiryChecker;
class KeyResolver;
}
class IdentityCombo;
class IdentityManager;
class RecipientsEditor;
namespace MessageComposer
{
class RichTextComposerNg;
class InfoPart;
class GlobalPart;
class Composer;
class AttachmentControllerBase;
class AttachmentModel;
class SignatureController;
/**
* @brief The ComposerViewBase class
*/
class ComposerViewBase : public QObject
{
Q_OBJECT
public:
explicit ComposerViewBase(QObject *parent = nullptr, QWidget *widget = nullptr);
~ComposerViewBase() override;
enum Confirmation {
LetUserConfirm,
NoConfirmationNeeded,
};
enum MissingAttachment {
NoMissingAttachmentFound,
FoundMissingAttachmentAndSending,
FoundMissingAttachmentAndAddedAttachment,
FoundMissingAttachmentAndCancel,
};
enum FailedType {
Sending,
AutoSave,
Draft,
};
- void setBearerToken(const QByteArray &bearerToken);
-
/**
* Set the message to be opened in the composer window, and set the internal data
* structures to keep track of it.
*/
void setMessage(const KMime::Message::Ptr &newMsg);
/**
* Send the message with the specified method, saving it in the specified folder.
*/
void send();
/**
* Returns true if there is at least one composer job running.
*/
[[nodiscard]] bool isComposing() const;
/**
* Add the given attachment to the message.
*/
void addAttachment(const QUrl &url, const QString &comment, bool sync);
void addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType);
void addAttachmentPart(KMime::Content *part);
void fillComposer(MessageComposer::Composer *composer);
/**
* Header fields in recipients editor.
*/
[[nodiscard]] QString to() const;
[[nodiscard]] QString cc() const;
[[nodiscard]] QString bcc() const;
[[nodiscard]] QString from() const;
[[nodiscard]] QString replyTo() const;
[[nodiscard]] QString subject() const;
/// \returns the mail id
///
/// The mail id allows to uniquely identify a message composer dialog. It's used
/// when autosaving a draft and as identifier when sending an email.
[[nodiscard]] QString mailId() const;
/// \returns whether the mail id is empty
[[nodiscard]] bool mailIdIsEmpty() const;
/// \returns the current identitfy
[[nodiscard]] KIdentityManagementCore::Identity identity() const;
/// Set the current identity.
void setIdentity(const KIdentityManagementCore::Identity &identiy);
/// The following are for setting the various options and widgets in the composer.
void setAttachmentModel(MessageComposer::AttachmentModel *model);
[[nodiscard]] MessageComposer::AttachmentModel *attachmentModel();
void setAttachmentController(MessageComposer::AttachmentControllerBase *controller);
[[nodiscard]] MessageComposer::AttachmentControllerBase *attachmentController();
void setRecipientsEditor(RecipientsEditor *recEditor);
[[nodiscard]] RecipientsEditor *recipientsEditor();
void setSignatureController(MessageComposer::SignatureController *sigController);
[[nodiscard]] MessageComposer::SignatureController *signatureController();
void setEditor(MessageComposer::RichTextComposerNg *editor);
[[nodiscard]] MessageComposer::RichTextComposerNg *editor() const;
[[nodiscard]] Sonnet::DictionaryComboBox *dictionary() const;
void setDictionary(Sonnet::DictionaryComboBox *dictionary);
+ [[nodiscard]] MessageDispatcher *messageDispatcher() const;
+ void setMessageDispatcher(MessageDispatcher *messageDispatcher);
+
/**
* Widgets for editing differ in client classes, so
* values are set before sending.
*/
void setFrom(const QString &from);
void setSubject(const QString &subject);
/**
* The following are various settings the user can modify when composing a message. If they are not set,
* the default values will be used.
*/
void setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts = false);
void setCharsets(const QList<QByteArray> &charsets);
void setMDNRequested(bool mdnRequested);
void setUrgent(bool urgent);
void setAutoSaveInterval(int interval);
void setCustomHeader(const QMap<QByteArray, QString> &customHeader);
/**
* Enables/disables autosaving depending on the value of the autosave
* interval.
*/
void updateAutoSave();
/**
* Sets the filename to use when autosaving something. This is used when the client recovers
* the autosave files: It calls this method, so that the composer uses the same filename again.
* That way, the recovered autosave file is properly cleaned up in cleanupAutoSave():
*/
void setAutoSaveFileName(const QString &fileName);
/**
* Stop autosaving and delete the autosaved message.
*/
void cleanupAutoSave();
void setParentWidgetForGui(QWidget *);
/**
* Check if the mail has references to attachments, but no attachments are added to it.
* If missing attachments are found, a dialog to add new attachments is shown.
* @param attachmentKeywords a list with the keywords that indicate an attachment should be present
* @return NoMissingAttachmentFound, if there is attachment in email
* FoundMissingAttachmentAndCancelSending, if mail might miss attachment but sending
* FoundMissingAttachmentAndAddedAttachment, if mail might miss attachment and we added an attachment
* FoundMissingAttachmentAndCancel, if mail might miss attachment and cancel sending
*/
[[nodiscard]] ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords);
[[nodiscard]] bool hasMissingAttachments(const QStringList &attachmentKeywords);
void saveMailSettings();
[[nodiscard]] QDate followUpDate() const;
void setFollowUpDate(const QDate &followUpDate);
void clearFollowUp();
[[nodiscard]] KMime::Message::Ptr msg() const;
[[nodiscard]] bool requestDeleveryConfirmation() const;
void setRequestDeleveryConfirmation(bool requestDeleveryConfirmation);
[[nodiscard]] std::shared_ptr<Kleo::ExpiryChecker> expiryChecker();
public Q_SLOTS:
void identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared = false);
/// Save the message in the autosave folder.
///
/// This message will be reopened automatically in the next section if
/// it wasn't cleaned up with cleanupAutoSave
void autoSaveMessage();
/// Save the message as a draft.
void slotSaveDraft();
/// Generate a KMime::Message::Ptr from the current state of the composer
///
/// \param callback The callback will be called when the message is generated.
void generateMessage(std::function<void(QList<KMime::Message::Ptr>)> callback);
+ /// Send the message
+ void queueMessage(const KMime::Message::Ptr &message);
+
Q_SIGNALS:
- /**
- * Message sending completed successfully.
- */
- void sentSuccessfully();
+ void closeWindow();
/**
* Message sending failed with given error message.
*/
void failed(const QString &errorMessage, MessageComposer::ComposerViewBase::FailedType type = Sending);
/**
* The composer was modified. This can happen behind the users' back
* when, for example, and autosaved message was recovered.
*/
void modified(bool isModified);
/**
* Enabling or disabling HTML in the editor is affected
* by various client options, so when that would otherwise happen,
* hand it off to the client to enact it for real.
*/
void disableHtml(MessageComposer::ComposerViewBase::Confirmation);
void enableHtml();
void tooManyRecipient(bool);
private Q_SLOTS:
void slotSendComposeResult(KJob *);
void slotAutoSaveComposeResult(KJob *job);
void slotSaveDraftComposeResult(KJob *job);
void slotComposerCreated();
Q_SIGNALS:
void composerCreated();
private:
/**
* Searches the mime tree, where root is the root node, for embedded images,
* extracts them froom the body and adds them to the editor.
*/
void collectImages(KMime::Content *root);
[[nodiscard]] bool inlineSigningEncryptionSelected() const;
/**
* Applies the user changes to the message object of the composer
* and signs/encrypts the message if activated.
* Disables the controls of the composer window.
*/
void readyForSending();
enum RecipientExpansion {
UseExpandedRecipients,
UseUnExpandedRecipients,
};
void fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize);
/// Fill the composer with the signing/encryption key of the sender as well as the email
/// addresses from the recipients.
[[nodiscard]] Kleo::KeyResolver *fillKeyResolver(bool encryptSomething);
/// Create one or multiple MessageComposer::Composer depending on the crypto settings
/// Emits composerCreated when finished.
void generateCryptoMessages();
void fillGlobalPart(MessageComposer::GlobalPart *globalPart);
void fillInfoPart(MessageComposer::InfoPart *part, RecipientExpansion expansion);
- void queueMessage(const KMime::Message::Ptr &message);
void updateRecipients(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, Recipient::Type type);
void markAllAttachmentsForSigning(bool sign);
void markAllAttachmentsForEncryption(bool encrypt);
/**
* Writes out autosave data to the disk from the KMime::Message message.
* Also appends the msgNum to the filename as a message can have a number of
* KMime::Messages
*/
void writeAutoSaveToDisk(const KMime::Message::Ptr &message);
/**
* Writes out draft data to the disk from the KMime::Message message.
* Also appends the msgNum to the filename as a message can have a number of
* KMime::Messages
*/
void writeDraftToDisk(const KMime::Message::Ptr &message);
/**
* Returns the autosave interval in milliseconds (as needed for QTimer).
*/
int autoSaveInterval() const;
/**
* Initialize autosaving (timer and filename).
*/
void initAutoSave();
KMime::Message::Ptr m_msg;
MessageComposer::AttachmentControllerBase *m_attachmentController = nullptr;
MessageComposer::AttachmentModel *m_attachmentModel = nullptr;
MessageComposer::SignatureController *m_signatureController = nullptr;
RecipientsEditor *m_recipientsEditor = nullptr;
IdentityCombo *m_identityCombo = nullptr;
KIdentityManagementCore::Identity m_identity;
MessageComposer::RichTextComposerNg *m_editor = nullptr;
Sonnet::DictionaryComboBox *m_dictionary = nullptr;
+ MessageDispatcher *m_messageDispatcher = nullptr;
QWidget *m_parentWidget = nullptr;
// List of active composer jobs. For example, saving as draft, autosaving and printing
// all create a composer, which is added to this list as long as it is active.
// Used mainly to prevent closing the window if a composer is active
QList<MessageComposer::Composer *> m_composers;
bool m_sign = false;
bool m_encrypt = false;
bool m_neverEncrypt = false;
bool m_mdnRequested = false;
bool m_urgent = false;
bool m_requestDeleveryConfirmation = false;
Kleo::CryptoMessageFormat m_cryptoMessageFormat;
QString mExpandedFrom;
QString m_from;
QString m_subject;
QStringList mExpandedTo, mExpandedCc, mExpandedBcc, mExpandedReplyTo;
QList<QByteArray> m_charsets;
QMap<QByteArray, QString> m_customHeader;
int m_pendingQueueJobs = 0;
QTimer *m_autoSaveTimer = nullptr;
mutable QString m_autoSaveUUID;
bool m_autoSaveErrorShown = false; // Stops an error message being shown every time autosave is executed.
int m_autoSaveInterval;
std::shared_ptr<Kleo::ExpiryChecker> mExpiryChecker;
QDate mFollowUpDate;
QByteArray m_bearerToken;
friend ComposerViewBaseTest;
};
} // namespace
diff --git a/client/editor/composerwindow.cpp b/client/editor/composerwindow.cpp
index 12d9bb6..d184e1f 100644
--- a/client/editor/composerwindow.cpp
+++ b/client/editor/composerwindow.cpp
@@ -1,1760 +1,1765 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "composerwindow.h"
// Qt includes
#include <QApplication>
#include <QClipboard>
#include <QCloseEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMimeData>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QPushButton>
#include <QSplitter>
#include <QStatusBar>
#include <QTimer>
#include <QUrlQuery>
#include <QVBoxLayout>
// KDE includes
#include "identity/identity.h"
#include <KActionCollection>
#include <KCursorSaver>
#include <KEmailAddress>
#include <KIconUtils>
#include <KLineEdit>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMime/Message>
#include <KPIMTextEdit/RichTextComposer>
#include <KPIMTextEdit/RichTextComposerActions>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerImages>
#include <KPIMTextEdit/RichTextComposerWidget>
#include <KPIMTextEdit/RichTextExternalComposer>
#include <KToolBar>
#include <MimeTreeParserWidgets/MessageViewer>
#include <MimeTreeParserWidgets/MessageViewerDialog>
#include <Sonnet/DictionaryComboBox>
#include <TextCustomEditor/RichTextEditorWidget>
#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyResolverCore>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/KeySelectionDialog>
// Gpgme includes
#include <QGpgME/Protocol>
#include <gpgme++/tofuinfo.h>
// App includes
#include "../identity/identitydialog.h"
#include "../identity/identitymanager.h"
#include "attachment/attachmentcontroller.h"
#include "attachment/attachmentmodel.h"
#include "attachment/attachmentview.h"
#include "composerviewbase.h"
#include "draft/draftmanager.h"
+#include "ews/ewsmessagedispatcher.h"
#include "job/inserttextfilejob.h"
#include "job/saveasfilejob.h"
#include "kmcomposerglobalaction.h"
#include "mailtemplates.h"
#include "messagecomposersettings.h"
#include "nearexpirywarning.h"
#include "recipientseditor.h"
#include "richtextcomposerng.h"
#include "signaturecontroller.h"
#include "spellcheckerconfigdialog.h"
#include "websocketclient.h"
using namespace Qt::Literals::StringLiterals;
using namespace std::chrono_literals;
namespace
{
inline bool containsSMIME(unsigned int f)
{
return f & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat);
}
inline bool containsOpenPGP(unsigned int f)
{
return f & (Kleo::OpenPGPMIMEFormat | Kleo::InlineOpenPGPFormat);
}
auto findSendersUid(const std::string &addrSpec, const std::vector<GpgME::UserID> &userIds)
{
return std::find_if(userIds.cbegin(), userIds.cend(), [&addrSpec](const auto &uid) {
return uid.addrSpec() == addrSpec || (uid.addrSpec().empty() && std::string(uid.email()) == addrSpec)
|| (uid.addrSpec().empty() && (!uid.email() || !*uid.email()) && uid.name() == addrSpec);
});
}
}
ComposerWindow::ComposerWindow(const QString &from, const QString &name, const QByteArray &bearerToken, QWidget *parent)
: KXmlGuiWindow(parent)
, mFrom(from)
, mMainWidget(new QWidget(this))
, mComposerBase(new MessageComposer::ComposerViewBase(this))
, mHeadersToEditorSplitter(new QSplitter(Qt::Vertical, mMainWidget))
, mHeadersArea(new QWidget(mHeadersToEditorSplitter))
, mGrid(new QGridLayout(mHeadersArea))
, mLblFrom(new QLabel(i18nc("sender address field", "From:"), mHeadersArea))
, mButtonFrom(new QPushButton(mHeadersArea))
, mRecipientEditor(new RecipientsEditor(mHeadersArea))
, mLblSubject(new QLabel(i18nc("@label:textbox Subject of email.", "Subject:"), mHeadersArea))
, mEdtSubject(new QLineEdit(mHeadersArea))
, mRichTextComposer(new MessageComposer::RichTextComposerNg(this))
, mRichTextEditorWidget(new TextCustomEditor::RichTextEditorWidget(mRichTextComposer, mMainWidget))
, mNearExpiryWarning(new NearExpiryWarning(this))
, mGlobalAction(new KMComposerGlobalAction(this, this))
+ , mMessageDispatcher(new EWSMessageDispatcher(this))
, mKeyCache(Kleo::KeyCache::mutableInstance())
{
connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this](const GpgME::KeyListResult &result) {
Q_UNUSED(result);
mRunKeyResolverTimer->start();
});
bool isNew = false;
mIdentity = IdentityManager::self().fromEmail(from, isNew);
mEdtFrom = new QLabel(mHeadersArea);
if (isNew) {
// fill the idenity with default fields
auto dlg = new KMail::IdentityDialog;
mIdentity.setFullName(name);
mIdentity.setWarnNotEncrypt(true);
dlg->setIdentity(mIdentity);
connect(dlg, &KMail::IdentityDialog::keyListingFinished, this, [this, dlg]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
});
}
- mComposerBase->setBearerToken(bearerToken);
+ mMessageDispatcher->setBearerToken(bearerToken);
+ mComposerBase->setMessageDispatcher(mMessageDispatcher);
mMainWidget->resize(800, 600);
setCentralWidget(mMainWidget);
setWindowTitle(i18nc("@title:window", "Composer"));
setMinimumSize(200, 200);
mHeadersToEditorSplitter->setObjectName(QStringLiteral("mHeadersToEditorSplitter"));
mHeadersToEditorSplitter->setChildrenCollapsible(false);
auto v = new QVBoxLayout(mMainWidget);
v->setContentsMargins({});
v->addWidget(mNearExpiryWarning);
v->addWidget(mHeadersToEditorSplitter);
mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding);
mHeadersToEditorSplitter->addWidget(mHeadersArea);
const QList<int> defaultSizes{0};
mHeadersToEditorSplitter->setSizes(defaultSizes);
mGrid->setColumnStretch(0, 1);
mGrid->setColumnStretch(1, 100);
mGrid->setRowStretch(3 + 1, 100);
int row = 0;
mRunKeyResolverTimer = new QTimer(this);
mRunKeyResolverTimer->setSingleShot(true);
mRunKeyResolverTimer->setInterval(500ms);
connect(mRunKeyResolverTimer, &QTimer::timeout, this, &ComposerWindow::runKeyResolver);
// From
mLblFrom->setObjectName(QStringLiteral("fromLineLabel"));
mLblFrom->setFixedWidth(mRecipientEditor->setFirstColumnWidth(0));
mLblFrom->setBuddy(mEdtFrom);
auto fromWrapper = new QWidget(mHeadersArea);
auto fromWrapperLayout = new QHBoxLayout(fromWrapper);
fromWrapperLayout->setContentsMargins({});
mEdtFrom->installEventFilter(this);
mEdtFrom->setText(mFrom);
mEdtFrom->setObjectName(QStringLiteral("fromLine"));
fromWrapperLayout->addWidget(mEdtFrom);
mComposerBase->setIdentity(mIdentity);
mButtonFrom->setText(i18nc("@action:button", "Configure"));
mButtonFrom->setIcon(QIcon::fromTheme(u"configure-symbolic"_s));
connect(mButtonFrom, &QPushButton::clicked, this, &ComposerWindow::slotEditIdentity);
fromWrapperLayout->addWidget(mButtonFrom);
mGrid->addWidget(mLblFrom, row, 0);
mGrid->addWidget(fromWrapper, row, 1);
row++;
// Recipients
mGrid->addWidget(mRecipientEditor, row, 0, 1, 2);
mComposerBase->setRecipientsEditor(mRecipientEditor);
mRecipientEditor->setCompletionMode(KCompletion::CompletionPopup);
connect(mRecipientEditor, &RecipientsEditor::lineAdded, this, [this](KPIM::MultiplyingLine *line) {
slotRecipientEditorLineAdded(qobject_cast<RecipientLineNG *>(line));
});
row++;
// Subject
mEdtSubject->setObjectName(u"subjectLine"_s);
mLblSubject->setObjectName(u"subjectLineLabel"_s);
mLblSubject->setBuddy(mEdtSubject);
mGrid->addWidget(mLblSubject, row, 0);
mGrid->addWidget(mEdtSubject, row, 1);
row++;
auto editorWidget = new QWidget();
auto vLayout = new QVBoxLayout(editorWidget);
vLayout->setContentsMargins({});
vLayout->setSpacing(0);
mHeadersToEditorSplitter->addWidget(editorWidget);
// Message widget
auto connectionLossWidget = new KMessageWidget(this);
connectionLossWidget->hide();
connectionLossWidget->setWordWrap(true);
connectionLossWidget->setPosition(KMessageWidget::Position::Header);
vLayout->addWidget(connectionLossWidget);
auto &websocketClient = WebsocketClient::self();
connect(&websocketClient, &WebsocketClient::closed, this, [connectionLossWidget](const QString &errorMessage) {
connectionLossWidget->setText(errorMessage);
connectionLossWidget->show();
});
connect(&websocketClient, &WebsocketClient::connected, this, [connectionLossWidget]() {
connectionLossWidget->hide();
});
connect(&websocketClient, &WebsocketClient::emailSentSuccessfully, this, [this](const QString &id) {
if (id == mComposerBase->mailId()) {
auto &draftManager = DraftManager::self();
draftManager.remove(draftManager.draftById(id.toUtf8()));
hide();
}
});
// Rich text editor
mRichTextComposer->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
mRichTextComposer->setProperty("_breeze_force_frame", true);
mComposerBase->setEditor(mRichTextComposer);
vLayout->addWidget(mRichTextEditorWidget);
auto attachmentModel = new MessageComposer::AttachmentModel(this);
auto attachmentView = new AttachmentView(attachmentModel, mHeadersToEditorSplitter);
attachmentView->hideIfEmpty();
connect(attachmentView, &AttachmentView::modified, this, &ComposerWindow::setModified);
connect(attachmentView, &AttachmentView::errorOccurred, this, &ComposerWindow::slotHandleError);
auto attachmentController = new AttachmentController(attachmentModel, attachmentView, this);
mComposerBase->setAttachmentController(attachmentController);
mComposerBase->setAttachmentModel(attachmentModel);
auto signatureController = new MessageComposer::SignatureController(this);
connect(signatureController, &MessageComposer::SignatureController::enableHtml, this, &ComposerWindow::enableHtml);
signatureController->setIdentity(mIdentity);
signatureController->setEditor(mComposerBase->editor());
mComposerBase->setSignatureController(signatureController);
connect(signatureController,
&MessageComposer::SignatureController::signatureAdded,
mComposerBase->editor()->externalComposer(),
&KPIMTextEdit::RichTextExternalComposer::startExternalEditor);
setupStatusBar(attachmentView->widget());
setupActions();
setStandardToolBarMenuEnabled(true);
toolBar(u"mainToolBar"_s)->show();
connect(expiryChecker().get(),
&Kleo::ExpiryChecker::expiryMessage,
this,
[&](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
Q_UNUSED(isNewMessage);
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
const auto plainMsg = msg.replace(QStringLiteral("<p>"), QStringLiteral(" "))
.replace(QStringLiteral("</p>"), QStringLiteral(" "))
.replace(QStringLiteral("<p align=center>"), QStringLiteral(" "));
mNearExpiryWarning->addInfo(plainMsg);
mNearExpiryWarning->setWarning(info == Kleo::ExpiryChecker::OwnKeyExpired);
mNearExpiryWarning->animatedShow();
}
const QList<KPIM::MultiplyingLine *> lstLines = mRecipientEditor->lines();
for (KPIM::MultiplyingLine *line : lstLines) {
auto recipient = line->data().dynamicCast<Recipient>();
if (recipient->key().primaryFingerprint() == key.primaryFingerprint()) {
auto recipientLine = qobject_cast<RecipientLineNG *>(line);
QString iconName = QStringLiteral("emblem-warning");
if (info == Kleo::ExpiryChecker::OtherKeyExpired) {
mAcceptedSolution = false;
iconName = QStringLiteral("emblem-error");
const auto showCryptoIndicator = true;
const auto encrypt = mEncryptAction->isChecked();
const bool showAllIcons = showCryptoIndicator && encrypt;
if (!showAllIcons) {
recipientLine->setIcon(QIcon(), msg);
return;
}
}
recipientLine->setIcon(QIcon::fromTheme(iconName), msg);
return;
}
}
});
// TODO make it possible to show this
auto dictionaryComboBox = new Sonnet::DictionaryComboBox(this);
dictionaryComboBox->hide();
mComposerBase->setDictionary(dictionaryComboBox);
slotIdentityChanged();
runKeyResolver();
- connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &ComposerWindow::slotSendSuccessful);
+ connect(mMessageDispatcher, &MessageDispatcher::errorOccurred, this, &ComposerWindow::slotHandleError);
connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &ComposerWindow::slotHandleError);
+ connect(mComposerBase, &MessageComposer::ComposerViewBase::closeWindow, this, &ComposerWindow::slotCloseWindow);
+ connect(mMessageDispatcher, &MessageDispatcher::sentSuccessfully, this, &ComposerWindow::slotCloseWindow);
}
void ComposerWindow::slotHandleError(const QString &errorMessage)
{
KMessageBox::error(this, errorMessage);
}
void ComposerWindow::reset(const QString &fromAddress, const QString &name, const QByteArray &bearerToken)
{
mFrom = fromAddress;
bool isNew = false;
mIdentity = IdentityManager::self().fromEmail(fromAddress, isNew);
if (isNew) {
// fill the idenity with default fields
auto dlg = new KMail::IdentityDialog;
mIdentity.setFullName(name);
dlg->setIdentity(mIdentity);
connect(dlg, &KMail::IdentityDialog::keyListingFinished, this, [this, dlg]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
});
}
- mComposerBase->setBearerToken(bearerToken);
+ mMessageDispatcher->setBearerToken(bearerToken);
mEdtSubject->setText(QString());
mRecipientEditor->clear();
mComposerBase->editor()->setText(QString{});
mComposerBase->attachmentController()->clear();
const QList<KPIM::MultiplyingLine *> lstLines = mRecipientEditor->lines();
for (KPIM::MultiplyingLine *line : lstLines) {
slotRecipientEditorLineAdded(qobject_cast<RecipientLineNG *>(line));
}
}
void ComposerWindow::setupActions()
{
// Save as draft
auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as Encrypted &Draft"), this);
actionCollection()->addAction(QStringLiteral("save_in_drafts"), action);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_S));
connect(action, &QAction::triggered, this, &ComposerWindow::slotSaveDraft);
// Save as file
action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this);
actionCollection()->addAction(QStringLiteral("save_as_file"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotSaveAsFile);
// Insert file
action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this);
actionCollection()->addAction(QStringLiteral("insert_file"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotInsertFile);
// Spellchecking
mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this);
actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction);
const bool spellChecking = MessageComposer::MessageComposerSettings::self()->autoSpellChecking();
mAutoSpellCheckingAction->setChecked(spellChecking);
slotAutoSpellCheckingToggled(spellChecking);
connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &ComposerWindow::slotAutoSpellCheckingToggled);
connect(mComposerBase->editor(), &TextCustomEditor::RichTextEditor::checkSpellingChanged, this, &ComposerWindow::slotAutoSpellCheckingToggled);
action = new QAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Spellchecker..."), this);
action->setIconText(i18n("Spellchecker"));
actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotSpellcheckConfig);
// Recent actions
mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this);
actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction);
connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &ComposerWindow::slotInsertRecentFile);
connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &ComposerWindow::slotRecentListFileClear);
const QStringList urls = MessageComposer::MessageComposerSettings::self()->recentUrls();
for (const QString &url : urls) {
mRecentAction->addUrl(QUrl(url));
}
// print
KStandardAction::print(this, &ComposerWindow::slotPrint, actionCollection());
KStandardAction::printPreview(this, &ComposerWindow::slotPrintPreview, actionCollection());
// Send email action
action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this);
actionCollection()->addAction(QStringLiteral("mail_send"), action);
actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return));
connect(action, &QAction::triggered, this, &ComposerWindow::slotSend);
// Toggle rich text
mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this);
mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
mMarkupAction->setIconText(i18n("Rich Text"));
mMarkupAction->setToolTip(i18n("Toggle rich text editing mode"));
actionCollection()->addAction(QStringLiteral("html"), mMarkupAction);
connect(mMarkupAction, &KToggleAction::triggered, this, &ComposerWindow::slotToggleMarkup);
mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this);
actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction);
mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap());
connect(mWordWrapAction, &KToggleAction::toggled, this, &ComposerWindow::slotWordWrapToggled);
// Encryption action
mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this);
mEncryptAction->setIconText(i18n("Encrypt"));
actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction);
connect(mEncryptAction, &KToggleAction::toggled, this, &ComposerWindow::slotEncryptionButtonIconUpdate);
// Signing action
mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this);
mSignAction->setIconText(i18n("Sign"));
actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction);
connect(mSignAction, &KToggleAction::triggered, this, &ComposerWindow::slotSignToggled);
// Append signature
mAppendSignature = new QAction(i18n("Append S&ignature"), this);
actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature);
connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature);
// Prepend signature
mPrependSignature = new QAction(i18n("Pr&epend Signature"), this);
actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature);
connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature);
mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this);
actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition);
connect(mInsertSignatureAtCursorPosition,
&QAction::triggered,
mComposerBase->signatureController(),
&MessageComposer::SignatureController::insertSignatureAtCursor);
action = new QAction(i18n("Paste as Attac&hment"), this);
actionCollection()->addAction(QStringLiteral("paste_att"), action);
connect(action, &QAction::triggered, this, &ComposerWindow::slotPasteAsAttachment);
action = new QAction(i18n("Cl&ean Spaces"), this);
actionCollection()->addAction(QStringLiteral("clean_spaces"), action);
connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace);
mRichTextComposer->composerActions()->createActions(actionCollection());
KStandardAction::close(this, &ComposerWindow::close, actionCollection());
KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection());
KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection());
KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection());
KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection());
KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection());
mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection());
mFindText = KStandardAction::find(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFind, actionCollection());
mFindNextText = KStandardAction::findNext(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFindNext, actionCollection());
mReplaceText = KStandardAction::replace(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotReplace, actionCollection());
mComposerBase->attachmentController()->createActions();
createGUI(u"composerui.rc"_s);
connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &ComposerWindow::htmlToolBarVisibilityChanged);
const QList<KPIM::MultiplyingLine *> lstLines = mRecipientEditor->lines();
for (KPIM::MultiplyingLine *line : lstLines) {
slotRecipientEditorLineAdded(qobject_cast<RecipientLineNG *>(line));
}
}
void ComposerWindow::setupStatusBar(QWidget *w)
{
statusBar()->addWidget(w);
mStatusbarLabel = new QLabel(this);
mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
statusBar()->addPermanentWidget(mStatusbarLabel);
mCursorLineLabel = new QLabel(this);
mCursorLineLabel->setTextFormat(Qt::PlainText);
mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral(" ")));
statusBar()->addPermanentWidget(mCursorLineLabel);
mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral(" ")));
mCursorColumnLabel->setTextFormat(Qt::PlainText);
statusBar()->addPermanentWidget(mCursorColumnLabel);
connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &ComposerWindow::slotCursorPositionChanged);
slotCursorPositionChanged();
}
void ComposerWindow::reply(const KMime::Message::Ptr &originalMessage)
{
MailTemplates::reply(originalMessage, [this](const KMime::Message::Ptr &message) {
setMessage(message);
Q_EMIT initialized();
});
}
void ComposerWindow::forward(const KMime::Message::Ptr &originalMessage)
{
MailTemplates::forward(originalMessage, [this](const KMime::Message::Ptr &message) {
setMessage(message);
Q_EMIT initialized();
});
}
void ComposerWindow::setMessage(const KMime::Message::Ptr &msg)
{
mEdtSubject->setText(msg->subject()->asUnicodeString());
mComposerBase->setMessage(msg);
}
bool ComposerWindow::isComposerModified() const
{
return mComposerBase->editor()->document()->isModified() || mComposerBase->recipientsEditor()->isModified() || mEdtSubject->isModified();
}
void ComposerWindow::setModified(bool isModified)
{
mIsModified = isModified;
mComposerBase->editor()->document()->setModified(isModified);
if (!isModified) {
mComposerBase->recipientsEditor()->clearModified();
mEdtSubject->setModified(false);
}
}
bool ComposerWindow::isModified() const
{
return mIsModified || isComposerModified();
}
void ComposerWindow::setSigning(bool sign, bool setByUser)
{
const bool wasModified = isModified();
if (setByUser) {
setModified(true);
}
if (!mSignAction->isEnabled()) {
sign = false;
}
// check if the user defined a signing key for the current identity
if (sign && !mLastIdentityHasSigningKey) {
if (setByUser) {
KMessageBox::error(this,
i18n("<qt><p>In order to be able to sign "
"this message you first have to "
"define the (OpenPGP or S/MIME) signing key "
"to use.</p>"
"<p>Please select the key to use "
"in the identity configuration.</p>"
"</qt>"),
i18nc("@title:window", "Undefined Signing Key"));
setModified(wasModified);
}
sign = false;
}
// make sure the mSignAction is in the right state
mSignAction->setChecked(sign);
// mark the attachments for (no) signing
// if (canSignEncryptAttachments()) {
// mComposerBase->attachmentModel()->setSignSelected(sign);
//}
}
std::unique_ptr<Kleo::KeyResolverCore> ComposerWindow::fillKeyResolver()
{
auto keyResolverCore = std::make_unique<Kleo::KeyResolverCore>(true, sign());
keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);
QStringList signingKeys, encryptionKeys;
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (!mIdentity.pgpSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(mIdentity.pgpSigningKey()));
}
if (!mIdentity.pgpEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(mIdentity.pgpEncryptionKey()));
}
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (!mIdentity.smimeSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(mIdentity.smimeSigningKey()));
}
if (!mIdentity.smimeEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(mIdentity.smimeEncryptionKey()));
}
}
keyResolverCore->setSender(mIdentity.fullEmailAddr());
keyResolverCore->setSigningKeys(signingKeys);
keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{keyResolverCore->normalizedSender(), encryptionKeys}}}});
QStringList recipients;
const auto lst = mRecipientEditor->lines();
for (auto line : lst) {
auto recipient = line->data().dynamicCast<Recipient>();
recipients.push_back(recipient->email());
}
keyResolverCore->setRecipients(recipients);
qWarning() << recipients;
return keyResolverCore;
}
void ComposerWindow::slotEncryptionButtonIconUpdate()
{
const auto state = mEncryptAction->isChecked();
auto icon = QIcon::fromTheme(QStringLiteral("document-encrypt"));
QString tooltip;
if (state) {
tooltip = i18nc("@info:tooltip", "Encrypt");
} else {
tooltip = i18nc("@info:tooltip", "Not Encrypt");
icon = QIcon::fromTheme(QStringLiteral("document-decrypt"));
}
if (mAcceptedSolution) {
auto overlay = QIcon::fromTheme(QStringLiteral("emblem-added"));
if (state) {
overlay = QIcon::fromTheme(QStringLiteral("emblem-checked"));
}
icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
} else {
const auto lst = mRecipientEditor->lines();
bool empty = false;
if (lst.size() == 1) {
const auto line = qobject_cast<RecipientLineNG *>(lst.first());
if (line->recipientsCount() == 0) {
empty = true;
}
}
if (state && !empty) {
auto overlay = QIcon::fromTheme(QStringLiteral("emblem-warning"));
icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner);
}
}
mEncryptAction->setIcon(icon);
mEncryptAction->setToolTip(tooltip);
}
void ComposerWindow::runKeyResolver()
{
auto keyResolverCore = fillKeyResolver();
auto result = keyResolverCore->resolve();
const auto lst = mRecipientEditor->lines();
if (lst.size() == 1) {
const auto line = qobject_cast<RecipientLineNG *>(lst.first());
if (line->recipientsCount() == 0) {
mAcceptedSolution = false;
slotEncryptionButtonIconUpdate();
return;
}
}
mAcceptedSolution = result.flags & Kleo::KeyResolverCore::AllResolved;
for (auto line_ : lst) {
auto line = qobject_cast<RecipientLineNG *>(line_);
Q_ASSERT(line);
auto recipient = line->data().dynamicCast<Recipient>();
QString dummy;
QString addrSpec;
if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
addrSpec = recipient->email();
}
auto resolvedKeys = result.solution.encryptionKeys[addrSpec];
GpgME::Key key;
if (resolvedKeys.size() == 0) { // no key found for recipient
// Search for any key, also for not accepted ons, to at least give the user more info.
key = Kleo::KeyCache::instance()->findBestByMailBox(addrSpec.toUtf8().constData(), GpgME::UnknownProtocol, Kleo::KeyCache::KeyUsage::Encrypt);
key.update(); // We need tofu information for key.
recipient->setKey(key);
} else { // A key was found for recipient
key = resolvedKeys.front();
if (recipient->key().primaryFingerprint() != key.primaryFingerprint()) {
key.update(); // We need tofu information for key.
recipient->setKey(key);
}
}
annotateRecipientEditorLineWithCryptoInfo(line);
if (!key.isNull()) {
mExpiryChecker->checkKey(key, Kleo::ExpiryChecker::EncryptionKey);
}
}
slotEncryptionButtonIconUpdate();
}
void ComposerWindow::annotateRecipientEditorLineWithCryptoInfo(RecipientLineNG *line)
{
auto recipient = line->data().dynamicCast<Recipient>();
const auto key = recipient->key();
const auto showCryptoIndicator = true;
const auto encrypt = mEncryptAction->isChecked();
const bool showPositiveIcons = showCryptoIndicator && encrypt;
const bool showAllIcons = showCryptoIndicator && encrypt;
QString dummy;
QString addrSpec;
bool invalidEmail = false;
if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) {
invalidEmail = true;
addrSpec = recipient->email();
}
if (key.isNull()) {
recipient->setEncryptionAction(Kleo::Impossible);
if (showAllIcons && !invalidEmail) {
const auto icon = QIcon::fromTheme(QStringLiteral("emblem-error"));
line->setIcon(icon, i18nc("@info:tooltip", "No key found for the recipient."));
} else {
line->setIcon(QIcon());
}
line->setProperty("keyStatus", invalidEmail ? InProgress : NoKey);
return;
}
CryptoKeyState keyState = KeyOk;
if (recipient->encryptionAction() != Kleo::DoIt) {
recipient->setEncryptionAction(Kleo::DoIt);
}
QString tooltip;
const auto uids = key.userIDs();
const auto _uid = findSendersUid(addrSpec.toStdString(), uids);
GpgME::UserID uid;
if (_uid == uids.cend()) {
uid = key.userID(0);
} else {
uid = *_uid;
}
const auto trustLevel = Kleo::trustLevel(uid);
switch (trustLevel) {
case Kleo::Level0:
if (uid.tofuInfo().isNull()) {
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It hasn't enough validity. "
"You can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
keyState = NoTrusted;
} else {
switch (uid.tofuInfo().validity()) {
case GpgME::TofuInfo::NoHistory:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. "
"It hasn't been used anywhere to guarantee it belongs to the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
break;
case GpgME::TofuInfo::Conflict:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It has conflicting TOFU data. "
"Click the icon for details.");
keyState = NoKey;
break;
case GpgME::TofuInfo::ValidityUnknown:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. It has unknown validity in TOFU data. "
"Click the icon for details.");
keyState = NoKey;
break;
default:
tooltip = i18nc("@info:tooltip",
"The encryption key is not trusted. The key is marked as bad. "
"Click the icon for details.");
keyState = NoKey;
}
}
break;
case Kleo::Level1:
tooltip = i18nc("@info:tooltip",
"The encryption key is only marginally trusted and hasn't been used enough time to guarantee it belongs to the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
break;
case Kleo::Level2:
if (uid.tofuInfo().isNull()) {
tooltip = i18nc("@info:tooltip",
"The encryption key is only marginally trusted. "
"You can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
} else {
tooltip = i18nc("@info:tooltip",
"The encryption key is only marginally trusted, but has been used enough times to be very likely controlled by the stated person. "
"By using the key will be trusted more. "
"Or you can sign the key, if you communicated the fingerprint by another channel. "
"Click the icon for details.");
}
break;
case Kleo::Level3:
tooltip = i18nc("@info:tooltip",
"The encryption key is fully trusted. You can raise the security level, by signing the key. "
"Click the icon for details.");
break;
case Kleo::Level4:
tooltip = i18nc("@info:tooltip",
"The encryption key is ultimately trusted or is signed by another ultimately trusted key. "
"Click the icon for details.");
break;
default:
Q_UNREACHABLE();
}
// Ensure the tooltips are word wrapped
tooltip = u"<div>"_s + tooltip + u"</div>"_s;
if (keyState == NoKey) {
mAcceptedSolution = false;
if (showAllIcons) {
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")), tooltip);
} else {
line->setIcon(QIcon());
}
} else if (trustLevel == Kleo::Level0 && encrypt) {
if (keyState == NoTrusted) {
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-question")), tooltip);
} else {
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")), tooltip);
}
} else if (showPositiveIcons) {
// Magically, the icon name maps precisely to each trust level
// line->setIcon(QIcon::fromTheme(QStringLiteral("gpg-key-trust-level-%1").arg(trustLevel)), tooltip);
line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-success")), tooltip);
} else {
line->setIcon(QIcon());
}
if (line->property("keyStatus") != keyState) {
line->setProperty("keyStatus", keyState);
}
}
void ComposerWindow::slotSignToggled(bool on)
{
setSigning(on, true);
}
bool ComposerWindow::sign() const
{
return mSignAction->isChecked();
}
void ComposerWindow::slotSend()
{
mComposerBase->setFrom(mFrom);
mComposerBase->setSubject(mEdtSubject->text());
if (mComposerBase->to().isEmpty()) {
if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) {
KMessageBox::information(this,
i18n("You must specify at least one receiver, "
"either in the To: field or as CC or as BCC."));
return;
} else {
const int rc = KMessageBox::questionTwoActions(this,
i18n("To: field is empty. "
"Send message anyway?"),
i18nc("@title:window", "No To: specified"),
KGuiItem(i18n("S&end as Is"), QLatin1String("mail-send")),
KGuiItem(i18n("&Specify the To field"), QLatin1String("edit-rename")));
if (rc == KMessageBox::ButtonCode::SecondaryAction) {
return;
}
}
}
if (mComposerBase->subject().isEmpty()) {
mEdtSubject->setFocus();
const int rc = KMessageBox::questionTwoActions(this,
i18n("You did not specify a subject. "
"Send message anyway?"),
i18nc("@title:window", "No Subject Specified"),
KGuiItem(i18n("S&end as Is"), QStringLiteral("mail-send")),
KGuiItem(i18n("&Specify the Subject"), QStringLiteral("edit-rename")));
if (rc == KMessageBox::ButtonCode::SecondaryAction) {
return;
}
}
KCursorSaver saver(Qt::WaitCursor);
const bool encrypt = mEncryptAction->isChecked();
mComposerBase->setCryptoOptions(sign(), encrypt, cryptoMessageFormat());
mComposerBase->send();
}
void ComposerWindow::changeCryptoAction()
{
if (!QGpgME::openpgp() && !QGpgME::smime()) {
// no crypto whatsoever
mEncryptAction->setEnabled(false);
mSignAction->setEnabled(false);
setSigning(false);
} else {
setSigning(true);
mEncryptAction->setChecked(true);
}
}
void ComposerWindow::slotToggleMarkup()
{
htmlToolBarVisibilityChanged(mMarkupAction->isChecked());
}
void ComposerWindow::htmlToolBarVisibilityChanged(bool visible)
{
if (visible) {
enableHtml();
} else {
disableHtml(LetUserConfirm);
}
}
void ComposerWindow::enableHtml()
{
if (mForceDisableHtml) {
disableHtml(NoConfirmationNeeded);
return;
}
mRichTextComposer->activateRichText();
if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
// Use singleshot, as we we might actually be called from a slot that wanted to disable the
// toolbar (but the messagebox in disableHtml() prevented that and called us).
// The toolbar can't correctly deal with being enabled right in a slot called from the "disabled"
// signal, so wait one event loop run for that.
QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show);
}
if (!mMarkupAction->isChecked()) {
mMarkupAction->setChecked(true);
}
mRichTextComposer->composerActions()->updateActionStates();
mRichTextComposer->composerActions()->setActionsEnabled(true);
}
void ComposerWindow::disableHtml(Confirmation confirmation)
{
bool forcePlainTextMarkup = false;
if (confirmation == LetUserConfirm && mRichTextComposer->composerControler()->isFormattingUsed()) {
int choice = KMessageBox::warningTwoActionsCancel(this,
i18n("Turning HTML mode off "
"will cause the text to lose the formatting. Are you sure?"),
i18n("Lose the formatting?"),
KGuiItem(i18n("Lose Formatting")),
KGuiItem(i18n("Add Markup Plain Text")),
KStandardGuiItem::cancel(),
QStringLiteral("LoseFormattingWarning"));
switch (choice) {
case KMessageBox::Cancel:
enableHtml();
return;
case KMessageBox::ButtonCode::SecondaryAction:
forcePlainTextMarkup = true;
break;
case KMessageBox::ButtonCode::PrimaryAction:
break;
}
}
mRichTextComposer->forcePlainTextMarkup(forcePlainTextMarkup);
mRichTextComposer->switchToPlainText();
mRichTextComposer->composerActions()->setActionsEnabled(false);
if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) {
// See the comment in enableHtml() why we use a singleshot timer, similar situation here.
QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide);
}
if (mMarkupAction->isChecked()) {
mMarkupAction->setChecked(false);
}
}
inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
{
const int num = 30;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
{
const int num = 14;
return Kleo::chrono::days{qMax(1, num)};
}
std::shared_ptr<Kleo::ExpiryChecker> ComposerWindow::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
encryptKeyNearExpiryWarningThresholdInDays(),
encryptRootCertNearExpiryWarningThresholdInDays(),
encryptChainCertNearExpiryWarningThresholdInDays()}});
}
return mExpiryChecker;
}
Kleo::CryptoMessageFormat ComposerWindow::cryptoMessageFormat() const
{
return Kleo::AutoFormat;
}
void ComposerWindow::slotEditIdentity()
{
QPointer<KMail::IdentityDialog> dlg = new KMail::IdentityDialog();
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setIdentity(mIdentity);
dlg->open();
connect(dlg, &KMail::IdentityDialog::accepted, this, [dlg, this]() {
dlg->updateIdentity(mIdentity);
IdentityManager::self().updateIdentity(mIdentity);
IdentityManager::self().writeConfig();
slotIdentityChanged();
});
}
void ComposerWindow::slotIdentityChanged()
{
mComposerBase->setIdentity(mIdentity);
mLastIdentityHasSigningKey = !mIdentity.pgpSigningKey().isEmpty() || !mIdentity.smimeSigningKey().isEmpty();
mLastIdentityHasEncryptionKey = !mIdentity.pgpEncryptionKey().isEmpty() || !mIdentity.smimeEncryptionKey().isEmpty();
mComposerBase->signatureController()->setIdentity(mIdentity);
mComposerBase->editor()->setAutocorrectionLanguage(mIdentity.autocorrectionLanguage());
mComposerBase->dictionary()->setCurrentByDictionaryName(mIdentity.dictionary());
mComposerBase->editor()->setSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary());
bool bPGPEncryptionKey = !mIdentity.pgpEncryptionKey().isEmpty();
bool bPGPSigningKey = !mIdentity.pgpSigningKey().isEmpty();
bool bSMIMEEncryptionKey = !mIdentity.smimeEncryptionKey().isEmpty();
bool bSMIMESigningKey = !mIdentity.smimeSigningKey().isEmpty();
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (bPGPEncryptionKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.pgpEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
bPGPEncryptionKey = false;
}
}
if (bPGPSigningKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.pgpSigningKey().constData());
if (key.isNull() || !key.canSign()) {
bPGPSigningKey = false;
}
}
} else {
bPGPEncryptionKey = false;
bPGPSigningKey = false;
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (bSMIMEEncryptionKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.smimeEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
bSMIMEEncryptionKey = false;
}
}
if (bSMIMESigningKey) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(mIdentity.smimeSigningKey().constData());
if (key.isNull() || !key.canSign()) {
bSMIMESigningKey = false;
}
}
} else {
bSMIMEEncryptionKey = false;
bSMIMESigningKey = false;
}
bool bNewIdentityHasSigningKey = bPGPSigningKey || bSMIMESigningKey;
bool bNewIdentityHasEncryptionKey = bPGPEncryptionKey || bSMIMEEncryptionKey;
if (!mKeyCache->initialized()) {
// We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
mKeyCache->startKeyListing();
connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
checkOwnKeyExpiry(mIdentity);
runKeyResolver();
});
} else {
checkOwnKeyExpiry(mIdentity);
}
// save the state of the sign and encrypt button
if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) {
mLastEncryptActionState = mEncryptAction->isChecked();
}
mSignAction->setEnabled(bNewIdentityHasSigningKey);
if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) {
mLastSignActionState = sign();
setSigning(false);
}
// restore the last state of the sign and encrypt button
if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) {
setSigning(mLastSignActionState);
}
mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
const KIdentityManagementCore::Signature sig = const_cast<KIdentityManagementCore::Identity &>(mIdentity).signature();
bool isEnabledSignature = sig.isEnabledSignature();
mAppendSignature->setEnabled(isEnabledSignature);
mPrependSignature->setEnabled(isEnabledSignature);
mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature);
changeCryptoAction();
Q_EMIT identityChanged();
}
void ComposerWindow::checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident)
{
mNearExpiryWarning->clearInfo();
mNearExpiryWarning->hide();
if (cryptoMessageFormat() & Kleo::AnyOpenPGP) {
if (!ident.pgpEncryptionKey().isEmpty()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
"Your selected PGP key (%1) doesn't exist in your keyring or is not suitable for encryption.",
QString::fromUtf8(ident.pgpEncryptionKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
}
}
if (!ident.pgpSigningKey().isEmpty()) {
if (ident.pgpSigningKey() != ident.pgpEncryptionKey()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData());
if (key.isNull() || !key.canSign()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint",
"Your selected PGP signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
QString::fromUtf8(ident.pgpSigningKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
}
}
}
}
if (cryptoMessageFormat() & Kleo::AnySMIME) {
if (!ident.smimeEncryptionKey().isEmpty()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData());
if (key.isNull() || !key.canEncrypt()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
"Your selected SMIME key (%1) doesn't exist in your keyring or is not suitable for encryption.",
QString::fromUtf8(ident.smimeEncryptionKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey);
}
}
if (!ident.smimeSigningKey().isEmpty()) {
if (ident.smimeSigningKey() != ident.smimeEncryptionKey()) {
auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData());
if (key.isNull() || !key.canSign()) {
mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint",
"Your selected SMIME signing key (%1) doesn't exist in your keyring or is not suitable for signing.",
QString::fromUtf8(ident.smimeSigningKey())));
mNearExpiryWarning->setWarning(true);
mNearExpiryWarning->show();
} else {
mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey);
}
}
}
}
}
void ComposerWindow::slotCursorPositionChanged()
{
// Change Line/Column info in status bar
const int line = mComposerBase->editor()->linePosition() + 1;
const int col = mComposerBase->editor()->columnNumber() + 1;
QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line);
mCursorLineLabel->setText(temp);
temp = i18n(" Column: %1 ", col);
mCursorColumnLabel->setText(temp);
// Show link target in status bar
if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) {
const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1String(" -> ")
+ mComposerBase->editor()->composerControler()->currentLinkUrl();
mStatusbarLabel->setText(text);
} else {
mStatusbarLabel->clear();
}
}
KIdentityManagementCore::Identity ComposerWindow::identity() const
{
return mIdentity;
}
QString ComposerWindow::subject() const
{
return mEdtSubject->text();
}
QString ComposerWindow::content() const
{
return mComposerBase->editor()->toCleanHtml();
}
RecipientsEditor *ComposerWindow::recipientsEditor() const
{
return mRecipientEditor;
}
void ComposerWindow::addAttachment(const QList<AttachmentInfo> &infos, bool showWarning)
{
QStringList lst;
for (const AttachmentInfo &info : infos) {
if (showWarning) {
lst.append(info.url.toDisplayString());
}
mComposerBase->addAttachment(info.url, info.comment, false);
}
if (showWarning) {
// TODO
// mAttachmentFromExternalMissing->setAttachmentNames(lst);
// mAttachmentFromExternalMissing->animatedShow();
}
}
void ComposerWindow::addAttachment(const QString &name,
KMime::Headers::contentEncoding cte,
const QString &charset,
const QByteArray &data,
const QByteArray &mimeType)
{
Q_UNUSED(cte)
mComposerBase->addAttachment(name, name, charset, data, mimeType);
}
void ComposerWindow::insertUrls(const QMimeData *source, const QList<QUrl> &urlList)
{
QStringList urlAdded;
for (const QUrl &url : urlList) {
QString urlStr;
if (url.scheme() == QLatin1String("mailto")) {
urlStr = KEmailAddress::decodeMailtoUrl(url);
} else {
urlStr = url.toDisplayString();
// Workaround #346370
if (urlStr.isEmpty()) {
urlStr = source->text();
}
}
if (!urlAdded.contains(urlStr)) {
mComposerBase->editor()->composerControler()->insertLink(urlStr);
urlAdded.append(urlStr);
}
}
}
bool ComposerWindow::insertFromMimeData(const QMimeData *source, bool forceAttachment)
{
// If this is a PNG image, either add it as an attachment or as an inline image
if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) {
const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html")));
mComposerBase->editor()->insertHtml(html);
return true;
} else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText()
&& !forceAttachment) {
mComposerBase->editor()->insertPlainText(source->text());
return true;
} else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) {
// Get the image data before showing the dialog, since that processes events which can delete
// the QMimeData object behind our back
const QByteArray imageData = source->data(QStringLiteral("image/png"));
if (imageData.isEmpty()) {
return true;
}
if (!forceAttachment) {
if (mComposerBase->editor()->textMode()
== MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) {
auto image = qvariant_cast<QImage>(source->imageData());
QFileInfo fi(source->text());
QMenu menu(this);
const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image"));
menu.addAction(i18n("Add as &Attachment"));
const QAction *selectedAction = menu.exec(QCursor::pos());
if (selectedAction == addAsInlineImageAction) {
// Let the textedit from kdepimlibs handle inline images
mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi);
return true;
} else if (!selectedAction) {
return true;
}
// else fall through
}
}
// Ok, when we reached this point, the user wants to add the image as an attachment.
// Ask for the filename first.
bool ok;
QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
if (!ok) {
return true;
}
attName = attName.trimmed();
if (attName.isEmpty()) {
KMessageBox::error(this, i18n("Attachment name can't be empty"), i18nc("@title:window", "Invalid Attachment Name"));
return true;
}
addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png");
return true;
}
// If this is a URL list, add those files as attachments or text
// but do not offer this if we are pasting plain text containing an url, e.g. from a browser
const QList<QUrl> urlList = source->urls();
if (!urlList.isEmpty()) {
// Search if it's message items.
bool allLocalURLs = true;
for (const QUrl &url : urlList) {
if (!url.isLocalFile()) {
allLocalURLs = false;
}
}
if (allLocalURLs || forceAttachment) {
QList<AttachmentInfo> infoList;
infoList.reserve(urlList.count());
for (const QUrl &url : urlList) {
AttachmentInfo info;
info.url = url;
infoList.append(info);
}
addAttachment(infoList, false);
} else {
QMenu p;
const int sizeUrl(urlList.size());
const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl));
const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl));
const QAction *selectedAction = p.exec(QCursor::pos());
if (selectedAction == addAsTextAction) {
insertUrls(source, urlList);
} else if (selectedAction == addAsAttachmentAction) {
QList<AttachmentInfo> infoList;
for (const QUrl &url : urlList) {
if (url.isValid()) {
AttachmentInfo info;
info.url = url;
infoList.append(info);
}
}
addAttachment(infoList, false);
}
}
return true;
}
return false;
}
void ComposerWindow::slotSaveDraft()
{
mComposerBase->slotSaveDraft();
}
void ComposerWindow::slotSaveAsFile()
{
auto job = new SaveAsFileJob(this);
job->setParentWidget(this);
job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich);
job->setTextDocument(mComposerBase->editor()->document());
job->start();
}
QUrl ComposerWindow::insertFile()
{
const auto fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Insert File"));
return QUrl::fromUserInput(fileName);
}
void ComposerWindow::slotInsertFile()
{
const QUrl u = insertFile();
if (u.isEmpty()) {
return;
}
mRecentAction->addUrl(u);
// Prevent race condition updating list when multiple composers are open
{
QUrlQuery query(u);
QStringList urls = MessageComposer::MessageComposerSettings::self()->recentUrls();
// Prevent config file from growing without bound
// Would be nicer to get this constant from KRecentFilesAction
const int mMaxRecentFiles = 30;
while (urls.count() > mMaxRecentFiles) {
urls.removeLast();
}
urls.prepend(u.toDisplayString());
MessageComposer::MessageComposerSettings::self()->setRecentUrls(urls);
MessageComposer::MessageComposerSettings::self()->save();
}
slotInsertRecentFile(u);
}
void ComposerWindow::slotRecentListFileClear()
{
MessageComposer::MessageComposerSettings::self()->setRecentUrls({});
MessageComposer::MessageComposerSettings::self()->save();
}
void ComposerWindow::slotInsertRecentFile(const QUrl &u)
{
if (u.fileName().isEmpty()) {
return;
}
auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u);
job->start();
}
void ComposerWindow::slotPrint()
{
QPrinter printer;
QPrintDialog dialog(&printer, this);
dialog.setWindowTitle(i18nc("@title:window", "Print Document"));
if (dialog.exec() != QDialog::Accepted)
return;
printInternal(&printer);
}
void ComposerWindow::slotPrintPreview()
{
auto dialog = new QPrintPreviewDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(800, 750);
dialog->setWindowTitle(i18nc("@title:window", "Print Document"));
QObject::connect(dialog, &QPrintPreviewDialog::paintRequested, this, [this](QPrinter *printer) {
printInternal(printer);
});
dialog->open();
}
void ComposerWindow::printInternal(QPrinter *printer)
{
mComposerBase->setFrom(mFrom);
mComposerBase->setSubject(mEdtSubject->text());
mComposerBase->generateMessage([printer](const QList<KMime::Message::Ptr> &messages) {
if (messages.isEmpty()) {
return;
}
MimeTreeParser::Widgets::MessageViewer messageViewer;
messageViewer.setMessage(messages[0]);
QPainter painter;
painter.begin(printer);
const auto pageLayout = printer->pageLayout();
const auto pageRect = pageLayout.paintRectPixels(printer->resolution());
const double xscale = pageRect.width() / double(messageViewer.width());
const double yscale = pageRect.height() / double(messageViewer.height());
const double scale = qMin(qMin(xscale, yscale), 1.);
painter.translate(pageRect.x(), pageRect.y());
painter.scale(scale, scale);
messageViewer.print(&painter, pageRect.width());
});
}
void ComposerWindow::slotPasteAsAttachment()
{
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
if (!mimeData) {
return;
}
if (insertFromMimeData(mimeData, true)) {
return;
}
if (mimeData->hasText()) {
bool ok;
const QString attName =
QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok);
if (ok) {
mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain");
}
return;
}
}
void ComposerWindow::slotWordWrapToggled(bool on)
{
if (on) {
mComposerBase->editor()->enableWordWrap(validateLineWrapWidth());
} else {
disableWordWrap();
}
}
int ComposerWindow::validateLineWrapWidth() const
{
int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth();
if ((lineWrap == 0) || (lineWrap > 78)) {
lineWrap = 78;
} else if (lineWrap < 30) {
lineWrap = 30;
}
return lineWrap;
}
void ComposerWindow::disableWordWrap()
{
mComposerBase->editor()->disableWordWrap();
}
void ComposerWindow::slotAutoSpellCheckingToggled(bool enabled)
{
mAutoSpellCheckingAction->setChecked(enabled);
if (mComposerBase->editor()->checkSpellingEnabled() != enabled) {
mComposerBase->editor()->setCheckSpellingEnabled(enabled);
}
// mStatusBarLabelSpellCheckingChangeMode->setToggleMode(enabled);
}
void ComposerWindow::slotSpellcheckConfig()
{
QPointer<SpellCheckerConfigDialog> dialog = new SpellCheckerConfigDialog(this);
if (!mComposerBase->editor()->spellCheckingLanguage().isEmpty()) {
dialog->setLanguage(mComposerBase->editor()->spellCheckingLanguage());
}
if (dialog->exec()) {
mComposerBase->editor()->setSpellCheckingLanguage(dialog->language());
}
delete dialog;
}
void ComposerWindow::closeEvent(QCloseEvent *event)
{
event->ignore();
ComposerWindowFactory::self().clear(this);
}
bool ComposerWindow::queryClose()
{
if (isModified()) {
const QString savebut = i18n("&Save as Draft");
const QString savetext = i18n("Save this message encrypted in your drafts folder. It can then be edited and sent at a later time.");
const int rc = KMessageBox::warningTwoActionsCancel(this,
i18n("Do you want to save the message for later or discard it?"),
i18nc("@title:window", "Close Composer"),
KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
if (rc == KMessageBox::Cancel) {
return false;
} else if (rc == KMessageBox::ButtonCode::PrimaryAction) {
// doSend will close the window. Just return false from this method
slotSaveDraft();
return false;
}
// else fall through: return true
}
mComposerBase->cleanupAutoSave();
return true;
}
void ComposerWindow::slotRecipientEditorLineAdded(RecipientLineNG *line)
{
connect(line, &RecipientLineNG::countChanged, this, [this, line]() {
slotRecipientAdded(line);
});
connect(line, &RecipientLineNG::iconClicked, this, [this, line]() {
slotRecipientLineIconClicked(line);
});
connect(line, &RecipientLineNG::destroyed, this, &ComposerWindow::slotRecipientEditorFocusChanged, Qt::QueuedConnection);
connect(
line,
&RecipientLineNG::activeChanged,
this,
[this, line]() {
slotRecipientFocusLost(line);
},
Qt::QueuedConnection);
slotRecipientEditorFocusChanged();
}
void ComposerWindow::slotRecipientLineIconClicked(RecipientLineNG *line)
{
const auto recipient = line->data().dynamicCast<Recipient>();
if (!recipient->key().isNull()) {
const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
if (exec.isEmpty()
|| !QProcess::startDetached(exec,
{QStringLiteral("--query"),
QString::fromLatin1(recipient->key().primaryFingerprint()),
QStringLiteral("--parent-windowid"),
QString::number(winId())})) {
qCWarning(EDITOR_LOG) << "Unable to execute kleopatra";
}
return;
}
const auto msg = i18nc(
"if in your language something like "
"'certificate(s)' is not possible please "
"use the plural in the translation",
"<qt>No valid and trusted encryption certificate was "
"found for \"%1\".<br/><br/>"
"Select the certificate(s) which should "
"be used for this recipient. If there is no suitable certificate in the list "
"you can also search for external certificates by clicking the button: "
"search for external certificates.</qt>",
recipient->name().isEmpty() ? recipient->email() : recipient->name());
const bool opgp = containsOpenPGP(cryptoMessageFormat());
const bool x509 = containsSMIME(cryptoMessageFormat());
QPointer<Kleo::KeySelectionDialog> dlg = new Kleo::KeySelectionDialog(
i18n("Encryption Key Selection"),
msg,
recipient->email(),
{},
Kleo::KeySelectionDialog::ValidEncryptionKeys | (opgp ? Kleo::KeySelectionDialog::OpenPGPKeys : 0) | (x509 ? Kleo::KeySelectionDialog::SMIMEKeys : 0),
false, // multi-selection
false); // "remember choice" box;
dlg->open();
connect(dlg, &QDialog::accepted, this, [dlg, recipient, line, this]() {
auto key = dlg->selectedKey();
key.update(); // We need tofu information for key.
recipient->setKey(key);
annotateRecipientEditorLineWithCryptoInfo(line);
});
}
void ComposerWindow::slotRecipientEditorFocusChanged()
{
if (!mEncryptAction->isChecked()) {
return;
}
if (mKeyCache->initialized()) {
mRunKeyResolverTimer->stop();
runKeyResolver();
}
}
void ComposerWindow::slotRecipientAdded(RecipientLineNG *line)
{
if (line->recipientsCount() == 0) {
return;
}
if (!mKeyCache->initialized()) {
if (line->property("keyLookupJob").toBool()) {
return;
}
line->setProperty("keyLookupJob", true);
// We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated.
connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, line]() {
slotRecipientAdded(line);
});
return;
}
if (mKeyCache->initialized()) {
mRunKeyResolverTimer->start();
}
}
void ComposerWindow::slotRecipientFocusLost(RecipientLineNG *line)
{
if (line->recipientsCount() == 0) {
return;
}
if (mKeyCache->initialized()) {
mRunKeyResolverTimer->start();
}
}
-void ComposerWindow::slotSendSuccessful()
+void ComposerWindow::slotCloseWindow()
{
setModified(false);
mComposerBase->cleanupAutoSave();
close();
}
diff --git a/client/editor/composerwindow.h b/client/editor/composerwindow.h
index 390cd61..e8112c6 100644
--- a/client/editor/composerwindow.h
+++ b/client/editor/composerwindow.h
@@ -1,230 +1,232 @@
// SPDX-FileCopyrightText: 2023 g10 code Gmbh
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Qt includes
#include <QUrl>
// KDE includes
#include "identity/identity.h"
#include <KMime/Headers>
#include <KMime/Message>
#include <KXmlGuiWindow>
#include <Libkleo/Enum>
#include <Libkleo/KeyCache>
// App includes
#include "composerwindowfactory.h"
class QSplitter;
class QLabel;
class QPrinter;
class QGridLayout;
class QLineEdit;
class QPushButton;
class KLineEdit;
class RecipientsEditor;
class KToggleAction;
class RecipientLineNG;
class NearExpiryWarning;
class IdentityCombo;
class KMComposerGlobalAction;
class KRecentFilesAction;
+class EWSMessageDispatcher;
namespace KPIMTextEdit
{
class RichTextComposerWidget;
class RichTextComposer;
}
namespace TextCustomEditor
{
class RichTextEditorWidget;
}
namespace MessageComposer
{
class ComposerViewBase;
class RichTextComposerNg;
}
namespace Kleo
{
class KeyResolverCore;
class ExpiryChecker;
}
class ComposerWindow : public KXmlGuiWindow
{
Q_OBJECT
enum Confirmation {
LetUserConfirm,
NoConfirmationNeeded,
};
public:
struct AttachmentInfo {
QString comment;
QUrl url;
};
/// The identity assigned to this message.
KIdentityManagementCore::Identity identity() const;
/// The subject of the message.
QString subject() const;
/// The recipients of the message.
RecipientsEditor *recipientsEditor() const;
/// The content of the message.
QString content() const;
void addAttachment(const QList<AttachmentInfo> &infos, bool showWarning);
void reply(const KMime::Message::Ptr &message);
void forward(const KMime::Message::Ptr &message);
void setMessage(const KMime::Message::Ptr &message);
private Q_SLOTS:
void slotSend();
void slotToggleMarkup();
void slotSignToggled(bool on);
void slotSaveDraft();
void slotSaveAsFile();
void slotInsertFile();
void slotEncryptionButtonIconUpdate();
void slotEditIdentity();
void slotIdentityChanged();
void slotPrint();
void slotPrintPreview();
void slotWordWrapToggled(bool on);
void slotAutoSpellCheckingToggled(bool enabled);
void slotSpellcheckConfig();
void slotHandleError(const QString &errorMessage);
void printInternal(QPrinter *printer);
void enableHtml();
void slotPasteAsAttachment();
void disableHtml(Confirmation confirmation);
void slotCursorPositionChanged();
void slotInsertRecentFile(const QUrl &u);
void slotRecentListFileClear();
void slotRecipientEditorFocusChanged();
void slotRecipientAdded(RecipientLineNG *line);
void slotRecipientFocusLost(RecipientLineNG *line);
void slotRecipientEditorLineAdded(RecipientLineNG *line);
void slotRecipientLineIconClicked(RecipientLineNG *line);
- void slotSendSuccessful();
+ void slotCloseWindow();
void insertUrls(const QMimeData *source, const QList<QUrl> &urlList);
bool insertFromMimeData(const QMimeData *source, bool forceAttachment);
QUrl insertFile();
void addAttachment(const QString &name, KMime::Headers::contentEncoding cte, const QString &charset, const QByteArray &data, const QByteArray &mimeType);
/// Set whether the message will be signed.
void setSigning(bool sign, bool setByUser = false);
/// Set whether the message should be treated as modified or not.
void setModified(bool modified);
std::shared_ptr<Kleo::ExpiryChecker> expiryChecker();
Q_SIGNALS:
void identityChanged();
void initialized();
protected:
friend ComposerWindowFactory;
explicit ComposerWindow(const QString &fromAddress, const QString &name, const QByteArray &bearerToken, QWidget *parent = nullptr);
void reset(const QString &fromAddress, const QString &name, const QByteArray &bearerToken);
void closeEvent(QCloseEvent *event) override;
private:
enum CryptoKeyState {
NoState = 0,
InProgress,
KeyOk,
NoKey,
NoTrusted,
};
/// Ask for confirmation if the message was changed.
[[nodiscard]] bool queryClose() override;
void annotateRecipientEditorLineWithCryptoInfo(RecipientLineNG *line);
void setupActions();
void setupStatusBar(QWidget *w);
void htmlToolBarVisibilityChanged(bool visible);
void changeCryptoAction();
void runKeyResolver();
int validateLineWrapWidth() const;
void disableWordWrap();
void checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident);
std::unique_ptr<Kleo::KeyResolverCore> fillKeyResolver();
/// Returns true if the message was modified by the user.
[[nodiscard]] bool isModified() const;
/// Returns true if the composer content was modified by the user.
[[nodiscard]] bool isComposerModified() const;
/**
* Returns true if the message will be signed.
*/
[[nodiscard]] bool sign() const;
Kleo::CryptoMessageFormat cryptoMessageFormat() const;
KIdentityManagementCore::Identity mIdentity;
QString mFrom;
QWidget *const mMainWidget;
// splitter between the headers area and the actual editor
MessageComposer::ComposerViewBase *const mComposerBase;
QSplitter *const mHeadersToEditorSplitter;
QWidget *const mHeadersArea;
QGridLayout *const mGrid;
QLabel *const mLblFrom;
QPushButton *const mButtonFrom;
RecipientsEditor *const mRecipientEditor;
QLabel *const mLblSubject;
QLineEdit *const mEdtSubject;
MessageComposer::RichTextComposerNg *const mRichTextComposer;
TextCustomEditor::RichTextEditorWidget *const mRichTextEditorWidget;
NearExpiryWarning *const mNearExpiryWarning;
KMComposerGlobalAction *const mGlobalAction;
+ EWSMessageDispatcher *const mMessageDispatcher;
QLabel *mEdtFrom = nullptr;
bool mForceDisableHtml = false;
bool mLastIdentityHasSigningKey = false;
bool mLastIdentityHasEncryptionKey = false;
QAction *mEncryptAction = nullptr;
QAction *mSignAction = nullptr;
QAction *mAppendSignature = nullptr;
QAction *mPrependSignature = nullptr;
QAction *mInsertSignatureAtCursorPosition = nullptr;
QAction *mSelectAll = nullptr;
QAction *mFindText = nullptr;
QAction *mFindNextText = nullptr;
QAction *mReplaceText = nullptr;
QLabel *mStatusbarLabel = nullptr;
QLabel *mCursorLineLabel = nullptr;
QLabel *mCursorColumnLabel = nullptr;
KToggleAction *mMarkupAction = nullptr;
std::shared_ptr<Kleo::ExpiryChecker> mExpiryChecker;
bool mIsModified = false;
bool mAcceptedSolution = false;
KRecentFilesAction *mRecentAction = nullptr;
KToggleAction *mWordWrapAction = nullptr;
KToggleAction *mAutoSpellCheckingAction = nullptr;
bool mLastSignActionState = false;
std::shared_ptr<Kleo::KeyCache> mKeyCache;
bool mLastEncryptActionState = false;
QTimer *mRunKeyResolverTimer = nullptr;
};
diff --git a/client/ews/ewsmessagedispatcher.cpp b/client/ews/ewsmessagedispatcher.cpp
new file mode 100644
index 0000000..0531aa4
--- /dev/null
+++ b/client/ews/ewsmessagedispatcher.cpp
@@ -0,0 +1,68 @@
+// SPDX-FileCopyrightText: 2024 g10 code GmbH
+// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "ewsmessagedispatcher.h"
+#include "editor_debug.h"
+#include "ewsmailfactory.h"
+#include "qnam.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QNetworkReply>
+
+#include <KLocalizedString>
+
+using namespace Qt::StringLiterals;
+
+EWSMessageDispatcher::EWSMessageDispatcher(QObject *parent)
+ : MessageDispatcher(parent)
+{
+}
+
+void EWSMessageDispatcher::dispatch(const KMime::Message::Ptr &message, const QString &from, const QString &mailId)
+{
+ qWarning().noquote() << message->encodedContent();
+
+ auto soapRequestBody = EwsMailFactory::create(message);
+
+ QNetworkRequest sendMailRequest(QUrl(u"https://127.0.0.1:5656/socket-web"_s));
+ sendMailRequest.setHeader(QNetworkRequest::ContentTypeHeader, u"application/xml"_s);
+ sendMailRequest.setRawHeader("X-TOKEN", m_bearerToken);
+ sendMailRequest.setRawHeader("X-EMAIL", from.toUtf8());
+
+ const QJsonDocument payload(QJsonObject{
+ {"type"_L1, "ews"_L1},
+ {"payload"_L1, soapRequestBody},
+ {"id"_L1, mailId},
+ });
+
+ auto sendMailResponse = qnam->post(sendMailRequest, payload.toJson());
+
+ // TODO remove me
+ QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &errors) {
+ Q_UNUSED(errors);
+ reply->ignoreSslErrors();
+ });
+
+ connect(sendMailResponse, &QNetworkReply::finished, this, [this, sendMailResponse]() {
+ if (sendMailResponse->error() != QNetworkReply::NoError) {
+ Q_EMIT errorOccurred(i18nc("Error message", "There were a problem sending the message: %1", sendMailResponse->errorString()));
+ return;
+ }
+
+ Q_EMIT sentSuccessfully();
+ });
+
+ qCDebug(EDITOR_LOG) << "Request body" << soapRequestBody;
+}
+
+QByteArray EWSMessageDispatcher::bearerToken() const
+{
+ return m_bearerToken;
+}
+
+void EWSMessageDispatcher::setBearerToken(const QByteArray &bearerToken)
+{
+ m_bearerToken = bearerToken;
+}
diff --git a/client/ews/ewsmessagedispatcher.h b/client/ews/ewsmessagedispatcher.h
new file mode 100644
index 0000000..035e55d
--- /dev/null
+++ b/client/ews/ewsmessagedispatcher.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2024 g10 code GmbH
+// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include "messagedispatcher.h"
+
+/// Implementation of the MessageDispatcher for the ews service
+///
+/// This will wrap the MIME message inside a SOAP request and forward it
+/// to the web addons.
+class EWSMessageDispatcher : public MessageDispatcher
+{
+ Q_OBJECT
+
+public:
+ /// Default constructor
+ explicit EWSMessageDispatcher(QObject *parent = nullptr);
+
+ /// \copydoc MessageDispatcher::dispatch
+ void dispatch(const KMime::Message::Ptr &message, const QString &from, const QString &mailId) override;
+
+ /// The bearer token to authentificate the http request
+ [[nodiscard]] QByteArray bearerToken() const;
+
+ /// Set the bearer token
+ /// \see bearerToken
+ void setBearerToken(const QByteArray &bearerToken);
+
+private:
+ QByteArray m_bearerToken;
+};
diff --git a/client/messagedispatcher.cpp b/client/messagedispatcher.cpp
new file mode 100644
index 0000000..7a5df56
--- /dev/null
+++ b/client/messagedispatcher.cpp
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2024 g10 code GmbH
+// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "messagedispatcher.h"
+
+MessageDispatcher::MessageDispatcher(QObject *parent)
+ : QObject(parent)
+{
+}
diff --git a/client/messagedispatcher.h b/client/messagedispatcher.h
new file mode 100644
index 0000000..95f9fb4
--- /dev/null
+++ b/client/messagedispatcher.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2024 g10 code GmbH
+// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <KMime/Message>
+#include <QObject>
+
+class MessageDispatcher : public QObject
+{
+ Q_OBJECT
+
+public:
+ /// Default constructor
+ explicit MessageDispatcher(QObject *parent = nullptr);
+
+ /// Get the message that will be sent
+ ///
+ /// Emits sentSucessfully when done
+ /// \param message The message to send
+ /// \param from The sender email address
+ /// \param from The mail id of the message
+ virtual void dispatch(const KMime::Message::Ptr &message, const QString &from, const QString &mailId) = 0;
+
+Q_SIGNALS:
+ /// Message sending completed successfully.
+ void sentSuccessfully();
+
+ /// Message sending completed successfully.
+ void errorOccurred(const QString &errorMessage);
+};
diff --git a/client/sendmessagejob.h b/client/sendmessagejob.h
new file mode 100644
index 0000000..2250844
--- /dev/null
+++ b/client/sendmessagejob.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2024 g10 code GmbH
+// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <KJob>
+#include <KMime/Message>
+
+class SendMessageJob : public KJob
+{
+ Q_OBJECT
+
+public:
+ /// Get the message that will be sent
+ KMime::Message::Ptr message() const;
+
+ /// Set the message that will be sent
+ void setMessage(const KMime::Message::Ptr &message);
+
+private:
+ KMime::Message::Ptr m_message;
+};

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 4, 5:44 AM (19 h, 57 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
85/55/b07323604d516f89bf29d7d45a23

Event Timeline