Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F23020486
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
26 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment