Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F19741723
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
34 KB
Subscribers
None
View Options
diff --git a/autotests/core/cryptohelpertest.cpp b/autotests/core/cryptohelpertest.cpp
index 982df83..ed807d7 100644
--- a/autotests/core/cryptohelpertest.cpp
+++ b/autotests/core/cryptohelpertest.cpp
@@ -1,182 +1,186 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "cryptohelpertest.h"
#include "cryptohelper.h"
#include <QTest>
using namespace MimeTreeParser;
QByteArray readMailFromFile(const QString &mailFile)
{
QFile file(QLatin1StringView(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
file.open(QIODevice::ReadOnly);
Q_ASSERT(file.isOpen());
return file.readAll();
}
void CryptoHelperTest::testPMFDEmpty()
{
QCOMPARE(prepareMessageForDecryption("").count(), 0);
}
void CryptoHelperTest::testPMFDWithNoPGPBlock()
{
const QByteArray text = "testblabla";
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 1);
QCOMPARE(blocks[0].text(), text);
QCOMPARE(blocks[0].type(), NoPgpBlock);
}
void CryptoHelperTest::testPGPBlockType()
{
const QString blockText = QStringLiteral("text");
const QString preString = QStringLiteral("before\n");
for (int i = 1; i <= PrivateKeyBlock; ++i) {
QString name;
switch (i) {
case PgpMessageBlock:
name = QStringLiteral("MESSAGE");
break;
case MultiPgpMessageBlock:
name = QStringLiteral("MESSAGE PART");
break;
case SignatureBlock:
name = QStringLiteral("SIGNATURE");
break;
case ClearsignedBlock:
name = QStringLiteral("SIGNED MESSAGE");
break;
case PublicKeyBlock:
name = QStringLiteral("PUBLIC KEY BLOCK");
break;
case PrivateKeyBlock:
name = QStringLiteral("PRIVATE KEY BLOCK");
break;
}
QString text = QLatin1StringView("-----BEGIN PGP ") + name + QLatin1Char('\n') + blockText;
QList<Block> blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1());
QCOMPARE(blocks.count(), 1);
QCOMPARE(blocks[0].type(), UnknownBlock);
text += QLatin1StringView("\n-----END PGP ") + name + QLatin1Char('\n');
blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1());
QCOMPARE(blocks.count(), 2);
QCOMPARE(blocks[1].text(), text.toLatin1());
QCOMPARE(blocks[1].type(), static_cast<PGPBlockType>(i));
}
}
void CryptoHelperTest::testDeterminePGPBlockType()
{
const QString blockText = QStringLiteral("text");
for (int i = 1; i <= PrivateKeyBlock; ++i) {
QString name;
switch (i) {
case PgpMessageBlock:
name = QStringLiteral("MESSAGE");
break;
case MultiPgpMessageBlock:
name = QStringLiteral("MESSAGE PART");
break;
case SignatureBlock:
name = QStringLiteral("SIGNATURE");
break;
case ClearsignedBlock:
name = QStringLiteral("SIGNED MESSAGE");
break;
case PublicKeyBlock:
name = QStringLiteral("PUBLIC KEY BLOCK");
break;
case PrivateKeyBlock:
name = QStringLiteral("PRIVATE KEY BLOCK");
break;
}
const QString text = QLatin1StringView("-----BEGIN PGP ") + name + QLatin1Char('\n') + blockText + QLatin1Char('\n');
const Block block = Block(text.toLatin1());
QCOMPARE(block.text(), text.toLatin1());
QCOMPARE(block.type(), static_cast<PGPBlockType>(i));
}
}
void CryptoHelperTest::testEmbededPGPBlock()
{
const QByteArray text = QByteArray("before\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\nafter");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 3);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after"));
}
void CryptoHelperTest::testClearSignedMessage()
{
const QByteArray text = QByteArray(
"before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\nafter");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 3);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(),
QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after"));
}
void CryptoHelperTest::testMultipleBlockMessage()
{
const QByteArray text = QByteArray(
"before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP "
"SIGNATURE-----\nafter\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 4);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(),
QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after\n"));
QCOMPARE(blocks[3].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n"));
}
void CryptoHelperTest::testDecryptMessage()
{
auto message = KMime::Message::Ptr(new KMime::Message);
message->setContent(readMailFromFile(QLatin1StringView("openpgp-encrypted+signed.mbox")));
message->parse();
bool wasEncrypted = false;
- auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted);
+ GpgME::Protocol protocol;
+ auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted, protocol);
QVERIFY(wasEncrypted);
QVERIFY(decryptedMessage);
QCOMPARE(decryptedMessage->decodedContent(), QByteArray("encrypted message text"));
QCOMPARE(decryptedMessage->encodedContent(),
QByteArray("From test@kolab.org Wed, 08 Sep 2010 17: 02:52 +0200\nFrom: OpenPGP Test <test@kolab.org>\nTo: test@kolab.org\nSubject: OpenPGP "
"encrypted\nDate: Wed, 08 Sep 2010 17:02:52 +0200\nUser-Agent: KMail/4.6 pre (Linux/2.6.34-rc2-2-default; KDE/4.5.60; x86_64; ; "
")\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7Bit\nContent-Type: text/plain; charset=\"us-ascii\"\n\nencrypted message text"));
+ QCOMPARE(protocol, GpgME::OpenPGP);
}
void CryptoHelperTest::testDecryptInlineMessage()
{
auto message = KMime::Message::Ptr(new KMime::Message);
message->setContent(readMailFromFile(QLatin1StringView("openpgp-inline-encrypted+nonenc.mbox")));
message->parse();
qWarning() << message->decodedContent();
bool wasEncrypted = false;
- auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted);
+ GpgME::Protocol protocol;
+ auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted, protocol);
QVERIFY(wasEncrypted);
QVERIFY(decryptedMessage);
QCOMPARE(decryptedMessage->decodedContent(), QByteArray("Not encrypted not signed :(\n\nsome random text\n"));
qWarning() << decryptedMessage->encodedContent();
QCOMPARE(decryptedMessage->encodedContent(),
QByteArray("From test@kolab.org Wed, 25 May 2011 23: 49:40 +0100\nFrom: OpenPGP Test <test@kolab.org>\nTo: test@kolab.org\nSubject: "
"inlinepgpencrypted + non enc text\nDate: Wed, 25 May 2011 23:49:40 +0100\nMessage-ID: "
"<1786696.yKXrOjjflF@herrwackelpudding.localhost>\nX-KMail-Transport: GMX\nX-KMail-Fcc: 28\nX-KMail-Drafts: 7\nX-KMail-Templates: "
"9\nUser-Agent: KMail/4.6 beta5 (Linux/2.6.34.7-0.7-desktop; KDE/4.6.41; x86_64;\n git-0269848; 2011-04-19)\nMIME-Version: "
"1.0\nContent-Type: text/plain; charset=\"us-ascii\"\n\nNot encrypted not signed :(\n\nsome random text\n"));
+ QCOMPARE(protocol, GpgME::OpenPGP);
}
QTEST_APPLESS_MAIN(CryptoHelperTest)
#include "moc_cryptohelpertest.cpp"
diff --git a/src/core/cryptohelper.cpp b/src/core/cryptohelper.cpp
index c728791..9f2088f 100644
--- a/src/core/cryptohelper.cpp
+++ b/src/core/cryptohelper.cpp
@@ -1,317 +1,317 @@
// SPDX-FileCopyrightText: 2001,2002 the KPGP authors
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "cryptohelper.h"
#include "mimetreeparser_core_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;
PGPBlockType Block::determineType() const
{
const QByteArray data = text();
if (data.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) {
return NoPgpBlock;
} else if (data.startsWith("-----BEGIN PGP SIGNED")) {
return ClearsignedBlock;
} else if (data.startsWith("-----BEGIN PGP SIGNATURE")) {
return SignatureBlock;
} else if (data.startsWith("-----BEGIN PGP PUBLIC")) {
return PublicKeyBlock;
} else if (data.startsWith("-----BEGIN PGP PRIVATE") || data.startsWith("-----BEGIN PGP SECRET")) {
return PrivateKeyBlock;
} else if (data.startsWith("-----BEGIN PGP MESSAGE")) {
if (data.startsWith("-----BEGIN PGP MESSAGE PART")) {
return MultiPgpMessageBlock;
} else {
return PgpMessageBlock;
}
} else if (data.startsWith("-----BEGIN PGP ARMORED FILE")) {
return PgpMessageBlock;
} else if (data.startsWith("-----BEGIN PGP ")) {
return UnknownBlock;
} else {
return NoPgpBlock;
}
}
QList<Block> MimeTreeParser::prepareMessageForDecryption(const QByteArray &msg)
{
PGPBlockType pgpBlock = NoPgpBlock;
QList<Block> blocks;
int start = -1; // start of the current PGP block
int lastEnd = -1; // end of the last PGP block
const int length = msg.length();
if (msg.isEmpty()) {
return blocks;
}
if (msg.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) {
return blocks;
}
if (msg.startsWith("-----BEGIN PGP ")) {
start = 0;
} else {
start = msg.indexOf("\n-----BEGIN PGP ") + 1;
if (start == 0) {
blocks.append(Block(msg, NoPgpBlock));
return blocks;
}
}
while (start != -1) {
int nextEnd;
int nextStart;
// is the PGP block a clearsigned block?
if (!strncmp(msg.constData() + start + 15, "SIGNED", 6)) {
pgpBlock = ClearsignedBlock;
} else {
pgpBlock = UnknownBlock;
}
nextEnd = msg.indexOf("\n-----END PGP ", start + 15);
nextStart = msg.indexOf("\n-----BEGIN PGP ", start + 15);
if (nextEnd == -1) { // Missing END PGP line
if (lastEnd != -1) {
blocks.append(Block(msg.mid(lastEnd + 1), UnknownBlock));
} else {
blocks.append(Block(msg.mid(start), UnknownBlock));
}
break;
}
if ((nextStart == -1) || (nextEnd < nextStart) || (pgpBlock == ClearsignedBlock)) {
// most likely we found a PGP block (but we don't check if it's valid)
// store the preceding non-PGP block
if (start - lastEnd - 1 > 0) {
blocks.append(Block(msg.mid(lastEnd + 1, start - lastEnd - 1), NoPgpBlock));
}
lastEnd = msg.indexOf("\n", nextEnd + 14);
if (lastEnd == -1) {
if (start < length) {
blocks.append(Block(msg.mid(start)));
}
break;
} else {
blocks.append(Block(msg.mid(start, lastEnd + 1 - start)));
if ((nextStart != -1) && (nextEnd > nextStart)) {
nextStart = msg.indexOf("\n-----BEGIN PGP ", lastEnd + 1);
}
}
}
start = nextStart;
if (start == -1) {
if (lastEnd + 1 < length) {
// rest of mail is no PGP Block
blocks.append(Block(msg.mid(lastEnd + 1), NoPgpBlock));
}
break;
} else {
start++; // move start behind the '\n'
}
}
return blocks;
}
Block::Block() = default;
Block::Block(const QByteArray &m)
: msg(m)
{
mType = determineType();
}
Block::Block(const QByteArray &m, PGPBlockType t)
: msg(m)
, mType(t)
{
}
QByteArray MimeTreeParser::Block::text() const
{
return msg;
}
PGPBlockType Block::type() const
{
return mType;
}
namespace
{
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()
QList<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)
+KMime::Message::Ptr CryptoUtils::decryptMessage(const KMime::Message::Ptr &msg, bool &wasEncrypted, GpgME::Protocol &protoName)
{
- GpgME::Protocol protoName = GpgME::UnknownProtocol;
+ protoName = GpgME::UnknownProtocol;
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 {
const auto blocks = prepareMessageForDecryption(msg->body());
QByteArray content;
for (const auto &block : blocks) {
if (block.type() == PgpMessageBlock) {
const auto proto = QGpgME::openpgp();
wasEncrypted = true;
QByteArray outData;
auto inData = block.text();
auto decrypt = proto->decryptJob();
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_CORE_LOG) << "Failed to decrypt:" << result.error().asString();
return {};
}
inData = outData;
auto verify = proto->verifyOpaqueJob(true);
auto resultVerify = verify->exec(inData, outData);
if (resultVerify.error()) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to verify:" << resultVerify.error().asString();
return {};
}
content += KMime::CRLFtoLF(outData);
} else if (block.type() == NoPgpBlock) {
content += block.text();
}
}
KMime::Content decCt;
decCt.setBody(content);
decCt.parse();
decCt.assemble();
return assembleMessage(msg, &decCt);
}
}
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();
auto result = decrypt->exec(inData, outData);
if (result.error()) {
// unknown key, invalid algo, or general error
qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to decrypt:" << result.error().asString();
return {};
}
KMime::Content decCt;
decCt.setContent(KMime::CRLFtoLF(outData));
decCt.parse();
decCt.assemble();
return assembleMessage(msg, &decCt);
}
diff --git a/src/core/cryptohelper.h b/src/core/cryptohelper.h
index 2dfceef..99b9369 100644
--- a/src/core/cryptohelper.h
+++ b/src/core/cryptohelper.h
@@ -1,55 +1,56 @@
// SPDX-FileCopyrightText: 2001,2002 the KPGP authors
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "mimetreeparser_core_export.h"
#include <KMime/Message>
#include <QByteArray>
#include <QList>
+#include <gpgme++/global.h>
namespace MimeTreeParser
{
enum PGPBlockType {
UnknownBlock = -1, // BEGIN PGP ???
NoPgpBlock = 0,
PgpMessageBlock = 1, // BEGIN PGP MESSAGE
MultiPgpMessageBlock = 2, // BEGIN PGP MESSAGE, PART X[/Y]
SignatureBlock = 3, // BEGIN PGP SIGNATURE
ClearsignedBlock = 4, // BEGIN PGP SIGNED MESSAGE
PublicKeyBlock = 5, // BEGIN PGP PUBLIC KEY BLOCK
PrivateKeyBlock = 6, // BEGIN PGP PRIVATE KEY BLOCK (PGP 2.x: ...SECRET...)
};
class MIMETREEPARSER_CORE_EXPORT Block
{
public:
Block();
Block(const QByteArray &m);
Block(const QByteArray &m, PGPBlockType t);
[[nodiscard]] QByteArray text() const;
[[nodiscard]] PGPBlockType type() const;
[[nodiscard]] PGPBlockType determineType() const;
QByteArray msg;
PGPBlockType mType = UnknownBlock;
};
/** Parses the given message and splits it into OpenPGP blocks and
Non-OpenPGP blocks.
*/
[[nodiscard]] MIMETREEPARSER_CORE_EXPORT QList<Block> prepareMessageForDecryption(const QByteArray &msg);
namespace CryptoUtils
{
-[[nodiscard]] MIMETREEPARSER_CORE_EXPORT KMime::Message::Ptr decryptMessage(const KMime::Message::Ptr &decrypt, bool &wasEncrypted);
+[[nodiscard]] MIMETREEPARSER_CORE_EXPORT KMime::Message::Ptr decryptMessage(const KMime::Message::Ptr &decrypt, bool &wasEncrypted, GpgME::Protocol &protoName);
}
} // namespace MimeTreeParser
Q_DECLARE_TYPEINFO(MimeTreeParser::Block, Q_MOVABLE_TYPE);
diff --git a/src/widgets/messageviewerdialog.cpp b/src/widgets/messageviewerdialog.cpp
index 54e73fd..165511e 100644
--- a/src/widgets/messageviewerdialog.cpp
+++ b/src/widgets/messageviewerdialog.cpp
@@ -1,321 +1,332 @@
// 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 "messageviewer.h"
#include "mimetreeparser_widgets_debug.h"
#include <MimeTreeParserCore/CryptoHelper>
#include <MimeTreeParserCore/FileOpener>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QMenuBar>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QPushButton>
#include <QRegularExpression>
#include <QSaveFile>
#include <QStandardPaths>
#include <QStyle>
#include <QToolBar>
#include <QVBoxLayout>
using namespace MimeTreeParser::Widgets;
namespace
{
-/// On windows, force the filename to end with .eml
-/// On Linux, do nothing as this is handled by the file picker
-inline QString changeExtension(const QString &fileName)
+inline QString changeExtension(const QString &fileName, const QString &extension)
{
-#ifdef Q_OS_WIN
auto renamedFileName = fileName;
- renamedFileName.replace(QRegularExpression(QStringLiteral("\\.(mbox|p7m|asc)$")), QStringLiteral(".eml"));
+ renamedFileName.replace(QRegularExpression(QStringLiteral("\\.(mbox|p7m|asc)$")), extension);
// In case the file name didn't contain any of the expected extension: mbox, p7m, asc and eml
// or doesn't contains an extension at all.
- if (!renamedFileName.endsWith(QStringLiteral(".eml"))) {
- renamedFileName += QStringLiteral(".eml");
+ if (!renamedFileName.endsWith(extension)) {
+ renamedFileName += extension;
}
return renamedFileName;
-#else
- // Handled automatically by the file picker on linux
- return fileName;
-#endif
}
-
}
class MessageViewerDialog::Private
{
public:
Private(MessageViewerDialog *dialog)
: q(dialog)
{
}
MessageViewerDialog *const q;
int currentIndex = 0;
QList<KMime::Message::Ptr> messages;
QString fileName;
MimeTreeParser::Widgets::MessageViewer *messageViewer = nullptr;
QAction *nextAction = nullptr;
QAction *previousAction = nullptr;
QToolBar *toolBar = 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]);
q->setWindowTitle(messageViewer->subject());
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 location = QFileDialog::getSaveFileName(parent,
- i18nc("@title:window", "Save File"),
- changeExtension(fileName),
- i18nc("File dialog accepted files", "Email files (*.eml *.mbox)"));
+ QString extension;
+ QString alternatives;
+ auto message = messages[currentIndex];
+ bool wasEncrypted = false;
+ GpgME::Protocol protocol;
+ auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted, protocol);
+ Q_UNUSED(decryptedMessage); // we save the message without modifying it
+
+ if (wasEncrypted) {
+ extension = QStringLiteral(".mime");
+ if (protocol == GpgME::OpenPGP) {
+ alternatives = i18nc("File dialog accepted files", "Email files (*.eml *.mbox *.mime)");
+ } else {
+ alternatives = i18nc("File dialog accepted files", "Encrypted S/MIME files (*.p7m)");
+ }
+ } else {
+ extension = QStringLiteral(".eml");
+ alternatives = i18nc("File dialog accepted files", "Email files (*.eml *.mbox *.mime)");
+ }
+
+ const QString location =
+ QFileDialog::getSaveFileName(parent, i18nc("@title:window", "Save File"), changeExtension(fileName, QStringLiteral(".mime")), alternatives);
QSaveFile file(location);
if (!file.open(QIODevice::WriteOnly)) {
KMessageBox::error(parent, i18n("File %1 could not be created.", location), i18n("Error saving message"));
return;
}
file.write(messages[currentIndex]->encodedContent());
file.commit();
}
void MessageViewerDialog::Private::saveDecrypted(QWidget *parent)
{
const QString location = QFileDialog::getSaveFileName(parent,
i18nc("@title:window", "Save Decrypted File"),
- changeExtension(fileName),
- i18nc("File dialog accepted files", "Email files (*.eml *.mbox)"));
+ changeExtension(fileName, QStringLiteral(".eml")),
+ i18nc("File dialog accepted files", "Email files (*.eml *.mbox *mime)"));
QSaveFile file(location);
if (!file.open(QIODevice::WriteOnly)) {
KMessageBox::error(parent, i18nc("Error message", "File %1 could not be created.", location), i18n("Error saving message"));
return;
}
auto message = messages[currentIndex];
bool wasEncrypted = false;
- auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted);
+ GpgME::Protocol protocol;
+ auto decryptedMessage = CryptoUtils::decryptMessage(message, wasEncrypted, protocol);
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 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 QList<KMime::Message::Ptr> &messages, QWidget *parent)
: QDialog(parent)
, d(std::make_unique<Private>(this))
{
d->messages += messages;
initGUI();
}
MessageViewerDialog::MessageViewerDialog(const QString &fileName, QWidget *parent)
: QDialog(parent)
, d(std::make_unique<Private>(this))
{
d->fileName = fileName;
d->messages += MimeTreeParser::Core::FileOpener::openFile(fileName);
initGUI();
}
void MessageViewerDialog::initGUI()
{
const auto mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins({});
mainLayout->setSpacing(0);
const auto layout = new QVBoxLayout;
const auto menuBar = d->createMenuBar(this);
mainLayout->setMenuBar(menuBar);
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;
d->toolBar = new QToolBar(this);
if (multipleMessages) {
#ifdef Q_OS_UNIX
d->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
d->toolBar->addAction(d->previousAction);
connect(d->previousAction, &QAction::triggered, this, [this] {
d->setCurrentIndex(d->currentIndex - 1);
});
const auto spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
d->toolBar->addWidget(spacer);
d->toolBar->addAction(d->nextAction);
connect(d->nextAction, &QAction::triggered, this, [this] {
d->setCurrentIndex(d->currentIndex + 1);
});
d->nextAction->setEnabled(true);
mainLayout->addWidget(d->toolBar);
} else {
mainLayout->addWidget(d->toolBar);
d->toolBar->hide();
}
mainLayout->addLayout(layout);
d->messageViewer = new MimeTreeParser::Widgets::MessageViewer(this);
d->messageViewer->setMessage(d->messages[0]);
setWindowTitle(d->messageViewer->subject());
layout->addWidget(d->messageViewer);
auto buttonBox = new QDialogButtonBox(this);
buttonBox->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));
auto closeButton = buttonBox->addButton(QDialogButtonBox::Close);
connect(closeButton, &QPushButton::pressed, this, &QDialog::accept);
layout->addWidget(buttonBox);
setMinimumSize(300, 300);
resize(600, 600);
}
MessageViewerDialog::~MessageViewerDialog() = default;
QToolBar *MessageViewerDialog::toolBar() const
{
return d->toolBar;
}
QList<KMime::Message::Ptr> MessageViewerDialog::messages() const
{
return d->messages;
}
#include "moc_messageviewerdialog.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 1, 9:08 AM (1 d, 4 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
77/dd/2c2cff1e100d92a0e18644530687
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment