Page MenuHome GnuPG

No OneTemporary

diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index 1bbf460..103cd3b 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -1,155 +1,157 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
ecm_setup_version(PROJECT
VARIABLE_PREFIX MIMETREEPARSER_WIDGETS
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsConfigVersion.cmake"
SOVERSION 5
)
add_library(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets)
add_library(KPim${KF_MAJOR_VERSION}::MimeTreeParserWidgets
ALIAS KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets)
target_sources(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets PRIVATE
attachmentview_p.h
attachmentview.cpp
+ cryptoutils.h
+ cryptoutils.cpp
messagecontainerwidget_p.h
messagecontainerwidget.cpp
messageviewer.h
messageviewer.cpp
messageviewerdialog.h
messageviewerdialog.cpp
urlhandler_p.h
urlhandler.cpp
)
ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets
HEADER mimetreeparser_widgets_debug.h
IDENTIFIER MIMETREEPARSER_WIDGET_LOG
CATEGORY_NAME org.kde.pim.mimetreeparser.widgets
DESCRIPTION "mimetreeparser (pim lib)"
EXPORT MIMETREEPARSER
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets PROPERTIES UNITY_BUILD ON)
endif()
generate_export_header(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets BASE_NAME mimetreeparser_widgets)
target_include_directories(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParserWidgets>")
target_link_libraries(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets
PUBLIC
Qt${KF_MAJOR_VERSION}::Widgets
KPim${KF_MAJOR_VERSION}::MimeTreeParserCore
PRIVATE
Qt${KF_MAJOR_VERSION}::PrintSupport
KF${KF_MAJOR_VERSION}::Codecs
KF${KF_MAJOR_VERSION}::I18n
KF${KF_MAJOR_VERSION}::CalendarCore
KF${KF_MAJOR_VERSION}::WidgetsAddons
KPim${KF_MAJOR_VERSION}::Libkleo
Gpgme::Gpgme
)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets PROPERTIES
VERSION ${MIMETREEPARSERNG_VERSION}
SOVERSION ${MIMETREEPARSERNG_SOVERSION}
EXPORT_NAME MimeTreeParserWidgets
)
ecm_generate_pri_file(BASE_NAME MimeTreeParserWidgets
LIB_NAME KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets
DEPS "MimeTreeParserWidgets"
FILENAME_VAR PRI_FILENAME
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(TARGETS
KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets
EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
ecm_generate_headers(MimeTreeParserWidgets_CamelCase_HEADERS
HEADER_NAMES
MessageViewer
MessageViewerDialog
REQUIRED_HEADERS MimeTreeParserWidgets_HEADERS
PREFIX MimeTreeParserWidgets
)
install(FILES
${MimeTreeParserWidgets_CamelCase_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParserWidgets/MimeTreeParserWidgets
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_export.h
${MimeTreeParserWidgets_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParserWidgets/mimetreeparserwidgets
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets_QCH
NAME MimeTreeParserWidgets
BASE_NAME KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
# using only public headers, to cover only public API
SOURCES ${MimeTreeParserWidgets_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt${QT_MAJOR_VERSION}Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
MIMETREEPARSER_WIDGETS_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets_QCH
FILE KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserWidgetsConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KPim${KF_MAJOR_VERSION}MimeTreeParserWidgetsTargets.cmake
NAMESPACE KPim${KF_MAJOR_VERSION}::
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParserWidgets
COMPONENT Devel
)
diff --git a/src/widgets/cryptoutils.cpp b/src/widgets/cryptoutils.cpp
new file mode 100644
index 0000000..537e205
--- /dev/null
+++ b/src/widgets/cryptoutils.cpp
@@ -0,0 +1,190 @@
+/*
+ * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+
+#include "cryptoutils.h"
+#include "mimetreeparser_widgets_debug.h"
+
+#include <QGpgME/DecryptJob>
+#include <QGpgME/Protocol>
+#include <QGpgME/VerifyOpaqueJob>
+
+#include <gpgme++/context.h>
+#include <gpgme++/decryptionresult.h>
+#include <gpgme++/verificationresult.h>
+
+using namespace MimeTreeParser::Widgets;
+
+namespace
+{
+
+bool isInlinePGP(const KMime::Content *part)
+{
+ // Find if the message body starts with --BEGIN PGP MESSAGE-- - we can't just
+ // use contains(), because that would also qualify messages that mention the
+ // string, but are not actually encrypted
+ const auto body = part->body();
+ for (auto c = body.cbegin(), end = body.cend(); c != end; ++c) {
+ if (!c) { // huh?
+ return false; // empty body -> not encrypted
+ }
+ // Is it a white space? Let's check next one
+ if (isspace(*c)) {
+ continue;
+ }
+
+ // First non-white space character in the body - if it's BEGIN PGP MESSAGE
+ // then the message is encrypted, otherwise it's not.
+ if (strncmp(c, "-----BEGIN PGP MESSAGE-----", sizeof("-----BEGIN PGP MESSAGE-----") - 1) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool isPGP(const KMime::Content *part, bool allowOctetStream = false)
+{
+ const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
+ return ct && (ct->isSubtype("pgp-encrypted") || ct->isSubtype("encrypted") || (allowOctetStream && ct->isMimeType("application/octet-stream")));
+}
+
+bool isSMIME(const KMime::Content *part)
+{
+ const auto ct = static_cast<KMime::Headers::ContentType *>(part->headerByType("Content-Type"));
+ return ct && (ct->isSubtype("pkcs7-mime") || ct->isSubtype("x-pkcs7-mime"));
+}
+
+void copyHeader(const KMime::Headers::Base *header, KMime::Message::Ptr msg)
+{
+ auto newHdr = KMime::Headers::createHeader(header->type());
+ if (!newHdr) {
+ newHdr = new KMime::Headers::Generic(header->type());
+ }
+ newHdr->from7BitString(header->as7BitString(false));
+ msg->appendHeader(newHdr);
+}
+
+bool isContentHeader(const KMime::Headers::Base *header)
+{
+ return header->is("Content-Type") || header->is("Content-Transfer-Encoding") || header->is("Content-Disposition");
+}
+
+KMime::Message::Ptr assembleMessage(const KMime::Message::Ptr &orig, const KMime::Content *newContent)
+{
+ auto out = KMime::Message::Ptr::create();
+ // Use the new content as message content
+ out->setBody(const_cast<KMime::Content *>(newContent)->encodedBody());
+ out->parse();
+
+ // remove default explicit content headers added by KMime::Content::parse()
+ QVector<KMime::Headers::Base *> headers = out->headers();
+ for (const auto hdr : std::as_const(headers)) {
+ if (isContentHeader(hdr)) {
+ out->removeHeader(hdr->type());
+ }
+ }
+
+ // Copy over headers from the original message, except for CT, CTE and CD
+ // headers, we want to preserve those from the new content
+ headers = orig->headers();
+ for (const auto hdr : std::as_const(headers)) {
+ if (isContentHeader(hdr)) {
+ continue;
+ }
+
+ copyHeader(hdr, out);
+ }
+
+ // Overwrite some headers by those provided by the new content
+ headers = newContent->headers();
+ for (const auto hdr : std::as_const(headers)) {
+ if (isContentHeader(hdr)) {
+ copyHeader(hdr, out);
+ }
+ }
+
+ out->assemble();
+ out->parse();
+
+ return out;
+}
+}
+
+KMime::Message::Ptr CryptoUtils::decryptMessage(const KMime::Message::Ptr &msg, bool &wasEncrypted)
+{
+ GpgME::Protocol protoName = GpgME::UnknownProtocol;
+ bool inlinePGP = false;
+ bool multipart = false;
+ if (msg->contentType(false) && msg->contentType(false)->isMimeType("multipart/encrypted")) {
+ multipart = true;
+ const auto subparts = msg->contents();
+ for (auto subpart : subparts) {
+ if (isPGP(subpart, true)) {
+ protoName = GpgME::OpenPGP;
+ break;
+ } else if (isSMIME(subpart)) {
+ protoName = GpgME::CMS;
+ break;
+ }
+ }
+ } else {
+ if (isPGP(msg.data())) {
+ protoName = GpgME::OpenPGP;
+ } else if (isSMIME(msg.data())) {
+ protoName = GpgME::CMS;
+ } else if (isInlinePGP(msg.data())) {
+ protoName = GpgME::OpenPGP;
+ inlinePGP = true;
+ }
+ }
+
+ if (protoName == GpgME::UnknownProtocol) {
+ // Not encrypted, or we don't recognize the encryption
+ wasEncrypted = false;
+ return {};
+ }
+
+ const auto proto = (protoName == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
+
+ wasEncrypted = true;
+ QByteArray outData;
+ auto inData = multipart ? msg->encodedContent() : msg->decodedContent(); // decodedContent in fact returns decoded body
+ auto decrypt = proto->decryptJob();
+ if (inlinePGP) {
+ auto ctx = QGpgME::Job::context(decrypt);
+ ctx->setDecryptionFlags(GpgME::Context::DecryptUnwrap);
+ }
+ auto result = decrypt->exec(inData, outData);
+ if (result.error()) {
+ // unknown key, invalid algo, or general error
+ qCWarning(MIMETREEPARSER_WIDGET_LOG) << "Failed to decrypt:" << result.error().asString();
+ return {};
+ }
+
+ if (inlinePGP) {
+ inData = outData;
+ auto verify = proto->verifyOpaqueJob(true);
+ auto result = verify->exec(inData, outData);
+ if (result.error()) {
+ qCWarning(MIMETREEPARSER_WIDGET_LOG) << "Failed to verify:" << result.error().asString();
+ return {};
+ }
+ }
+
+ KMime::Content decCt;
+ if (inlinePGP) {
+ decCt.setBody(KMime::CRLFtoLF(outData));
+ } else {
+ decCt.setContent(KMime::CRLFtoLF(outData));
+ }
+ decCt.parse();
+ decCt.assemble();
+
+ return assembleMessage(msg, &decCt);
+}
diff --git a/src/widgets/cryptoutils.h b/src/widgets/cryptoutils.h
new file mode 100644
index 0000000..d6539e8
--- /dev/null
+++ b/src/widgets/cryptoutils.h
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <KMime/Message>
+
+namespace MimeTreeParser
+{
+namespace Widgets
+{
+namespace CryptoUtils
+{
+Q_REQUIRED_RESULT KMime::Message::Ptr decryptMessage(const KMime::Message::Ptr &decrypt, bool &wasEncrypted);
+}
+}
+}
diff --git a/src/widgets/messageviewerdialog.cpp b/src/widgets/messageviewerdialog.cpp
index 91c4220..0e654c3 100644
--- a/src/widgets/messageviewerdialog.cpp
+++ b/src/widgets/messageviewerdialog.cpp
@@ -1,307 +1,363 @@
// SPDX-FileCopyrightText: 2023 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "messageviewerdialog.h"
+#include "cryptoutils.h"
#include "messageviewer.h"
#include "mimetreeparser_widgets_debug.h"
#include <KLocalizedString>
+#include <KMessageBox>
#include <KMessageWidget>
#include <KMime/Message>
#include <QDialogButtonBox>
#include <QFile>
+#include <QFileDialog>
#include <QMenuBar>
#include <QMimeDatabase>
#include <QMimeType>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QPushButton>
+#include <QSaveFile>
+#include <QStandardPaths>
#include <QStyle>
#include <QToolBar>
#include <QVBoxLayout>
#include <memory>
using namespace MimeTreeParser::Widgets;
namespace
{
/// Open first message from file
QVector<KMime::Message::Ptr> openFile(const QString &fileName)
{
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(fileName);
KMime::Message::Ptr message(new KMime::Message);
QFile file(fileName);
file.open(QIODevice::ReadOnly);
if (!file.isOpen()) {
qCWarning(MIMETREEPARSER_WIDGET_LOG) << "Could not open file";
return {};
}
const auto content = file.readAll();
if (content.length() == 0) {
qCWarning(MIMETREEPARSER_WIDGET_LOG) << "File is empty";
return {};
}
if (mime.inherits(QStringLiteral("application/pgp-encrypted")) || fileName.endsWith(QStringLiteral(".asc"))) {
auto contentType = message->contentType();
contentType->setMimeType("multipart/encrypted");
contentType->setBoundary(KMime::multiPartBoundary());
contentType->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
contentType->setCategory(KMime::Headers::CCcontainer);
auto cte = message->contentTransferEncoding();
cte->setEncoding(KMime::Headers::CE7Bit);
cte->setDecoded(true);
auto pgpEncrypted = new KMime::Content;
pgpEncrypted->contentType()->setMimeType("application/pgp-encrypted");
auto contentDisposition = new KMime::Headers::ContentDisposition;
contentDisposition->setDisposition(KMime::Headers::CDattachment);
pgpEncrypted->appendHeader(contentDisposition);
pgpEncrypted->setBody("Version: 1");
message->addContent(pgpEncrypted);
auto encryptedContent = new KMime::Content;
encryptedContent->contentType()->setMimeType("application/octet-stream");
contentDisposition = new KMime::Headers::ContentDisposition;
contentDisposition->setDisposition(KMime::Headers::CDinline);
contentDisposition->setFilename(QStringLiteral("msg.asc"));
encryptedContent->appendHeader(contentDisposition);
encryptedContent->setBody(content);
message->addContent(encryptedContent);
message->assemble();
} else {
int startOfMessage = 0;
if (content.startsWith("From ")) {
startOfMessage = content.indexOf('\n');
if (startOfMessage == -1) {
return {};
}
startOfMessage += 1; // the message starts after the '\n'
}
QVector<KMime::Message::Ptr> listMessages;
// check for multiple messages in the file
int endOfMessage = content.indexOf("\nFrom ", startOfMessage);
while (endOfMessage != -1) {
if (content.indexOf("From ", startOfMessage) == startOfMessage) {
startOfMessage = content.indexOf('\n', startOfMessage) + 1;
}
auto msg = new KMime::Message;
msg->setContent(KMime::CRLFtoLF(content.mid(startOfMessage, endOfMessage - startOfMessage)));
msg->parse();
if (!msg->hasContent()) {
delete msg;
msg = nullptr;
return {};
}
KMime::Message::Ptr mMsg(msg);
listMessages << mMsg;
startOfMessage = endOfMessage + 1;
endOfMessage = content.indexOf("\nFrom ", startOfMessage);
}
if (endOfMessage == -1) {
if (content.indexOf("From ", startOfMessage) == startOfMessage) {
startOfMessage = content.indexOf('\n', startOfMessage) + 1;
}
endOfMessage = content.length();
auto msg = new KMime::Message;
msg->setContent(KMime::CRLFtoLF(content.mid(startOfMessage, endOfMessage - startOfMessage)));
msg->parse();
if (!msg->hasContent()) {
delete msg;
msg = nullptr;
return {};
}
KMime::Message::Ptr mMsg(msg);
listMessages << mMsg;
}
return listMessages;
}
return {message};
}
}
class MessageViewerDialog::Private
{
public:
int currentIndex = 0;
QVector<KMime::Message::Ptr> messages;
MimeTreeParser::Widgets::MessageViewer *messageViewer = nullptr;
QAction *nextAction = nullptr;
QAction *previousAction = nullptr;
void setCurrentIndex(int currentIndex);
QMenuBar *createMenuBar(QWidget *parent);
private:
+ void save(QWidget *parent);
+ void saveDecrypted(QWidget *parent);
void print(QWidget *parent);
void printPreview(QWidget *parent);
void printInternal(QPrinter *printer);
};
void MessageViewerDialog::Private::setCurrentIndex(int index)
{
Q_ASSERT(index >= 0);
Q_ASSERT(index < messages.count());
currentIndex = index;
messageViewer->setMessage(messages[currentIndex]);
previousAction->setEnabled(currentIndex != 0);
nextAction->setEnabled(currentIndex != messages.count() - 1);
}
QMenuBar *MessageViewerDialog::Private::createMenuBar(QWidget *parent)
{
const auto menuBar = new QMenuBar(parent);
// File menu
const auto fileMenu = menuBar->addMenu(i18nc("@action:inmenu", "&File"));
+
const auto saveAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@action:inmenu", "&Save"));
+ QObject::connect(saveAction, &QAction::triggered, parent, [parent, this] {
+ save(parent);
+ });
fileMenu->addAction(saveAction);
+ const auto saveDecryptedAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@action:inmenu", "Save Decrypted"));
+ QObject::connect(saveDecryptedAction, &QAction::triggered, parent, [parent, this] {
+ saveDecrypted(parent);
+ });
+ fileMenu->addAction(saveDecryptedAction);
+
const auto printPreviewAction = new QAction(QIcon::fromTheme(QStringLiteral("document-print-preview")), i18nc("@action:inmenu", "Print Preview"));
QObject::connect(printPreviewAction, &QAction::triggered, parent, [parent, this] {
printPreview(parent);
});
fileMenu->addAction(printPreviewAction);
const auto printAction = new QAction(QIcon::fromTheme(QStringLiteral("document-print")), i18nc("@action:inmenu", "&Print"));
QObject::connect(printAction, &QAction::triggered, parent, [parent, this] {
print(parent);
});
fileMenu->addAction(printAction);
// Navigation menu
const auto navigationMenu = menuBar->addMenu(i18nc("@action:inmenu", "&Navigation"));
previousAction = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18nc("@action:button Previous email", "Previous Message"), parent);
previousAction->setEnabled(false);
navigationMenu->addAction(previousAction);
nextAction = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18nc("@action:button Next email", "Next Message"), parent);
nextAction->setEnabled(false);
navigationMenu->addAction(nextAction);
return menuBar;
}
+void MessageViewerDialog::Private::save(QWidget *parent)
+{
+ const QString fileName = QFileDialog::getSaveFileName(parent,
+ i18nc("@title:window", "Save File"),
+ QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
+ i18nc("File dialog accepted files", "Email files (*.mbox)"));
+
+ QSaveFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ KMessageBox::error(parent, i18n("File %1 could not be created.", fileName), i18n("Error saving message"));
+ return;
+ }
+ file.write(messages[currentIndex]->encodedContent());
+ file.commit();
+}
+
+void MessageViewerDialog::Private::saveDecrypted(QWidget *parent)
+{
+ const QString fileName = QFileDialog::getSaveFileName(parent,
+ i18nc("@title:window", "Save Decrypted File"),
+ QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
+ i18nc("File dialog accepted files", "Email files (*.mbox)"));
+
+ QSaveFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ KMessageBox::error(parent, i18nc("Error message", "File %1 could not be created.", fileName), i18n("Error saving message"));
+ return;
+ }
+ auto message = messages[currentIndex];
+ bool wasEncrypted = false;
+ auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted);
+ if (!wasEncrypted) {
+ decryptedMessage = message;
+ }
+ file.write(decryptedMessage->encodedContent());
+
+ file.commit();
+}
+
void MessageViewerDialog::Private::print(QWidget *parent)
{
QPrinter printer;
QPrintDialog dialog(&printer, parent);
dialog.setWindowTitle(i18nc("@title:window", "Print Document"));
if (dialog.exec() != QDialog::Accepted)
return;
printInternal(&printer);
}
void MessageViewerDialog::Private::printPreview(QWidget *parent)
{
auto dialog = new QPrintPreviewDialog(parent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(800, 750);
dialog->setWindowTitle(i18nc("@title:window", "Print Document"));
QObject::connect(dialog, &QPrintPreviewDialog::paintRequested, parent, [this](QPrinter *printer) {
printInternal(printer);
});
dialog->open();
}
void MessageViewerDialog::Private::printInternal(QPrinter *printer)
{
QPainter painter;
painter.begin(printer);
const auto pageLayout = printer->pageLayout();
const auto pageRect = pageLayout.paintRectPixels(printer->resolution());
const auto paperRect = pageLayout.fullRectPixels(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());
}
MessageViewerDialog::MessageViewerDialog(const QString &fileName, QWidget *parent)
: QDialog(parent)
, d(std::make_unique<Private>())
{
const auto mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins({});
const auto layout = new QVBoxLayout;
layout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr, this),
style()->pixelMetric(QStyle::PM_LayoutTopMargin, nullptr, this),
style()->pixelMetric(QStyle::PM_LayoutRightMargin, nullptr, this),
style()->pixelMetric(QStyle::PM_LayoutBottomMargin, nullptr, this));
const auto menuBar = d->createMenuBar(this);
mainLayout->setMenuBar(menuBar);
d->messages += openFile(fileName);
if (d->messages.isEmpty()) {
auto errorMessage = new KMessageWidget(this);
errorMessage->setMessageType(KMessageWidget::Error);
errorMessage->setText(i18nc("@info", "Unable to read file"));
layout->addWidget(errorMessage);
return;
}
const bool multipleMessages = d->messages.length() > 1;
if (multipleMessages) {
const auto toolBar = new QToolBar(this);
#ifdef Q_OS_UNIX
toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
#else
// on other platforms the default is IconOnly which is bad for
// accessibility and can't be changed by the user.
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
#endif
toolBar->addAction(d->previousAction);
connect(d->previousAction, &QAction::triggered, this, [this](int index) {
d->setCurrentIndex(d->currentIndex - 1);
});
const auto spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
toolBar->addWidget(spacer);
toolBar->addAction(d->nextAction);
connect(d->nextAction, &QAction::triggered, this, [this](int index) {
d->setCurrentIndex(d->currentIndex + 1);
});
d->nextAction->setEnabled(true);
mainLayout->addWidget(toolBar);
}
mainLayout->addLayout(layout);
d->messageViewer = new MimeTreeParser::Widgets::MessageViewer(this);
d->messageViewer->setMessage(d->messages[0]);
layout->addWidget(d->messageViewer);
auto buttonBox = new QDialogButtonBox(this);
auto closeButton = buttonBox->addButton(QDialogButtonBox::Close);
connect(closeButton, &QPushButton::pressed, this, &QDialog::accept);
layout->addWidget(buttonBox);
}
MessageViewerDialog::~MessageViewerDialog() = default;

File Metadata

Mime Type
text/x-diff
Expires
Mon, May 12, 6:19 PM (1 d, 2 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b8/d9/da192d1a35eb8a6061bbb5a60ec4

Event Timeline