Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 429a7aa..e9ab049 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2023 g10 Code GmbH
# SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-2-Clause
-#add_subdirectory(gnupg_home)
-#add_subdirectory(core)
-#add_subdirectory(widgets)
+add_subdirectory(gnupg_home)
+add_subdirectory(core)
+add_subdirectory(widgets)
diff --git a/autotests/core/cryptohelpertest.cpp b/autotests/core/cryptohelpertest.cpp
index e7f3846..854a6b4 100644
--- a/autotests/core/cryptohelpertest.cpp
+++ b/autotests/core/cryptohelpertest.cpp
@@ -1,184 +1,183 @@
// 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;
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();
bool wasEncrypted = false;
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/autotests/core/mimetreeparsertest.cpp b/autotests/core/mimetreeparsertest.cpp
index 150b1d5..5c6c860 100644
--- a/autotests/core/mimetreeparsertest.cpp
+++ b/autotests/core/mimetreeparsertest.cpp
@@ -1,789 +1,773 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsystems.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
+#include "partmodel.h"
#include <MimeTreeParserCore/ObjectTreeParser>
-#include <QDebug>
#include <QTest>
#include <QTimeZone>
QByteArray readMailFromFile(const QString &mailFile)
{
QFile file(QLatin1StringView(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
file.open(QIODevice::ReadOnly);
Q_ASSERT(file.isOpen());
return file.readAll();
}
class MimeTreeParserTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testTextMail()
{
const auto expectedText = QStringLiteral(
"If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter "
"on our website: http://www.gog.com/newsletter/");
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("plaintext.mbox")));
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QCOMPARE(part->text(), expectedText);
QCOMPARE(part->charset(), QStringLiteral("utf-8").toLocal8Bit());
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
QCOMPARE(otp.collectAttachmentParts().size(), 0);
QCOMPARE(otp.plainTextContent(), expectedText);
QVERIFY(otp.htmlContent().isEmpty());
}
void testAlternative()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("alternative.mbox")));
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->plaintextContent(),
QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to "
"view the newsletter on our website: http://www.gog.com/newsletter/\n"));
QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit());
QCOMPARE(part->htmlContent(), QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>\n\n"));
QCOMPARE(otp.collectAttachmentParts().size(), 0);
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
}
void testTextHtml()
{
auto expectedText = QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>");
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("html.mbox")));
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::HtmlMessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->htmlContent(), expectedText);
QCOMPARE(part->charset(), QStringLiteral("windows-1252").toLocal8Bit());
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
auto contentAttachmentList = otp.collectAttachmentParts();
QCOMPARE(contentAttachmentList.size(), 0);
QCOMPARE(otp.htmlContent(), expectedText);
QVERIFY(otp.plainTextContent().isEmpty());
}
void testSMimeEncrypted()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-encrypted.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog."));
QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit());
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->signatures().size(), 0);
auto contentAttachmentList = otp.collectAttachmentParts();
QCOMPARE(contentAttachmentList.size(), 0);
}
void testOpenPGPEncryptedAttachment()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("test text"));
QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit());
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->signatures().size(), 1);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned);
auto contentAttachmentList = otp.collectAttachmentParts();
QCOMPARE(contentAttachmentList.size(), 2);
// QCOMPARE(contentAttachmentList[0]->availableContents(), QList<QByteArray>() << "text/plain");
// QCOMPARE(contentAttachmentList[0]->content().size(), 1);
QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1);
QCOMPARE(contentAttachmentList[0]->signatures().size(), 1);
QCOMPARE(contentAttachmentList[0]->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(contentAttachmentList[0]->signatureState(), MimeTreeParser::KMMsgFullySigned);
// QCOMPARE(contentAttachmentList[1]->availableContents(), QList<QByteArray>() << "image/png");
// QCOMPARE(contentAttachmentList[1]->content().size(), 1);
QCOMPARE(contentAttachmentList[1]->encryptions().size(), 0);
QCOMPARE(contentAttachmentList[1]->signatures().size(), 0);
QCOMPARE(contentAttachmentList[1]->encryptionState(), MimeTreeParser::KMMsgNotEncrypted);
QCOMPARE(contentAttachmentList[1]->signatureState(), MimeTreeParser::KMMsgNotSigned);
}
void testOpenPGPInline()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-inline-charset-encrypted.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->charset(), QStringLiteral("ISO-8859-15").toLocal8Bit());
QCOMPARE(part->text(), QString::fromUtf8("asdasd asd asd asdf sadf sdaf sadf öäü"));
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->signatures().size(), 1);
QCOMPARE(otp.collectAttachmentParts().size(), 0);
}
void testOpenPPGInlineWithNonEncText()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-inline-encrypted+nonenc.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part1 = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part1));
QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\nsome random text"));
// TODO test if we get the proper subparts with the appropriate encryptions
QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit());
QCOMPARE(part1->encryptionState(), MimeTreeParser::KMMsgPartiallyEncrypted);
QCOMPARE(part1->signatureState(), MimeTreeParser::KMMsgNotSigned);
// QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\n"));
// QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit());
// QCOMPARE(contentList[1]->content(), QStringLiteral("some random text").toLocal8Bit());
// QCOMPARE(contentList[1]->charset(), QStringLiteral("us-ascii").toLocal8Bit());
// QCOMPARE(contentList[1]->encryptions().size(), 1);
// QCOMPARE(contentList[1]->signatures().size(), 0);
QCOMPARE(otp.collectAttachmentParts().size(), 0);
}
void testEncryptionBlock()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part1 = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part1));
QCOMPARE(part1->encryptions().size(), 1);
// auto enc = contentList[0]->encryptions()[0];
// QCOMPARE((int) enc->recipients().size(), 2);
// auto r = enc->recipients()[0];
// QCOMPARE(r->keyid(),QStringLiteral("14B79E26050467AA"));
// QCOMPARE(r->name(),QStringLiteral("kdetest"));
// QCOMPARE(r->email(),QStringLiteral("you@you.com"));
// QCOMPARE(r->comment(),QStringLiteral(""));
// r = enc->recipients()[1];
// QCOMPARE(r->keyid(),QStringLiteral("8D9860C58F246DE6"));
// QCOMPARE(r->name(),QStringLiteral("unittest key"));
// QCOMPARE(r->email(),QStringLiteral("test@kolab.org"));
// QCOMPARE(r->comment(),QStringLiteral("no password"));
auto attachmentList = otp.collectAttachmentParts();
QCOMPARE(attachmentList.size(), 2);
auto attachment1 = attachmentList[0];
QVERIFY(attachment1->node());
QCOMPARE(attachment1->filename(), QStringLiteral("file.txt"));
auto attachment2 = attachmentList[1];
QVERIFY(attachment2->node());
QCOMPARE(attachment2->filename(), QStringLiteral("image.png"));
}
void testSignatureBlock()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
// QCOMPARE(contentList[0]->signatures().size(), 1);
// auto sig = contentList[0]->signatures()[0];
// QCOMPARE(sig->creationDateTime(), QDateTime(QDate(2015,05,01),QTime(15,12,47)));
// QCOMPARE(sig->expirationDateTime(), QDateTime());
// QCOMPARE(sig->neverExpires(), true);
// auto key = sig->key();
// QCOMPARE(key->keyid(),QStringLiteral("8D9860C58F246DE6"));
// QCOMPARE(key->name(),QStringLiteral("unittest key"));
// QCOMPARE(key->email(),QStringLiteral("test@kolab.org"));
// QCOMPARE(key->comment(),QStringLiteral("no password"));
}
void testRelatedAlternative()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("cid-links.mbox")));
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
QCOMPARE(otp.collectAttachmentParts().size(), 1);
}
void testAttachmentPart()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("attachment.mbox")));
otp.print();
auto partList = otp.collectAttachmentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->mimeType(), "image/jpeg");
QCOMPARE(part->filename(), QStringLiteral("aqnaozisxya.jpeg"));
}
void testAttachment2Part()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("attachment2.mbox")));
otp.print();
auto partList = otp.collectAttachmentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->mimeType(), "image/jpeg");
QCOMPARE(part->filename(), QStringLiteral("aqnaozisxya.jpeg"));
}
void testCidLink()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("cid-links.mbox")));
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(bool(part));
auto resolvedContent = otp.resolveCidLinks(part->htmlContent());
QVERIFY(!resolvedContent.contains(QLatin1StringView("cid:")));
}
void testCidLinkInForwardedInline()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("cid-links-forwarded-inline.mbox")));
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(bool(part));
auto resolvedContent = otp.resolveCidLinks(part->htmlContent());
QVERIFY(!resolvedContent.contains(QLatin1StringView("cid:")));
}
void testOpenPGPInlineError()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("inlinepgpgencrypted-error.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::EncryptedMessagePart>();
QVERIFY(bool(part));
QVERIFY(part->error());
}
void testEncapsulated()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("encapsulated-with-attachment.mbox")));
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 2);
auto part = partList[1].dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->from(), QLatin1StringView("Thomas McGuire <dontspamme@gmx.net>"));
QCOMPARE(part->date().toString(), QLatin1StringView("Wed Aug 5 10:57:58 2009 GMT+0200"));
auto subPartList = otp.collectContentParts(part);
QCOMPARE(subPartList.size(), 1);
qWarning() << subPartList[0]->metaObject()->className();
auto subPart = subPartList[0].dynamicCast<MimeTreeParser::TextMessagePart>();
QVERIFY(bool(subPart));
}
void test8bitEncodedInPlaintext()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("8bitencoded.mbox")));
QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("Why Pisa’s Tower")));
QVERIFY(otp.htmlContent().contains(QString::fromUtf8("Why Pisa’s Tower")));
}
void testInlineSigned()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-inline-signed.mbox")));
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QCOMPARE(part->signatures().size(), 1);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgNotEncrypted);
QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned);
QCOMPARE(part->text(), QString::fromUtf8("ohno öäü\n"));
QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("ohno öäü")));
- const auto signaturePart = part->signatures().first();
- const auto signatures = signaturePart->partMetaData()->verificationResult.signatures();
- QVERIFY(signatures.size() > 0);
- const auto signature = signatures.front();
-
- QCOMPARE(signature.summary() & GpgME::Signature::KeyMissing, false);
- QCOMPARE(signature.summary() & GpgME::Signature::KeyExpired, false);
- QCOMPARE(signature.summary() & GpgME::Signature::KeyRevoked, false);
- QCOMPARE(signature.summary() & GpgME::Signature::SigExpired, false);
- QCOMPARE(signature.summary() & GpgME::Signature::CrlMissing, false);
- QCOMPARE(signature.summary() & GpgME::Signature::CrlTooOld, false);
- QCOMPARE(QByteArray(signature.fingerprint()), QByteArray{"8D9860C58F246DE6"});
- // QCOMPARE(signaturePart->partMetaData()->signer, QLatin1StringView{"unittest key (no password) <test@kolab.org>"});
- // QCOMPARE(signaturePart->partMetaData()->signerMailAddresses, QStringList{{QStringLiteral("test@kolab.org")}});
+ const auto details = PartModel::signatureDetails(part.get());
+ QCOMPARE(details,
+ QStringLiteral("With certificate: <a href=\"key:1BA323932B3FAA826132C79E8D9860C58F246DE6\">unittest key (no password) &lt;test@kolab.org&gt; "
+ "(8D98 60C5 8F24 6DE6)</a><br/>The signature is valid and the certificate's validity is ultimately trusted."));
}
void testEncryptedAndSigned()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted+signed.mbox")));
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QCOMPARE(part->signatures().size(), 1);
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned);
QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("encrypted message text")));
- auto signaturePart = part->signatures().first();
- const auto signatures = signaturePart->partMetaData()->verificationResult.signatures();
- QVERIFY(signatures.size() > 0);
- const auto signature = signatures.front();
-
- QCOMPARE(QByteArray(signature.fingerprint()), QByteArray{"8D9860C58F246DE6"});
-
- QCOMPARE(signaturePart->partMetaData()->keyId, QByteArray{"8D9860C58F246DE6"});
- QCOMPARE(signature.summary() & GpgME::Signature::Valid, true);
+ const auto details = PartModel::signatureDetails(part.get());
+ QCOMPARE(details,
+ QStringLiteral("With unavailable certificate:<br>ID: 0xCBD116485DB9560CA3CD91E02E3B7787B1B75920<br/>You can search the certificate on a "
+ "keyserver or import it from a file."));
}
void testOpenpgpMultipartEmbedded()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-multipart-embedded.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(otp.plainTextContent(), QString::fromUtf8("sdflskjsdf\n\n-- \nThis is a HTML signature.\n"));
}
void testOpenpgpMultipartEmbeddedSigned()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-multipart-embedded-signed.mbox")));
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->signatures().size(), 1);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned);
QCOMPARE(otp.plainTextContent(), QString::fromUtf8("test\n\n-- \nThis is a HTML signature.\n"));
- auto signaturePart = part->signatures().first();
- // QVERIFY(signaturePart->partMetaData()->keyId.endsWith(QByteArray{"2E3B7787B1B75920"}));
- //// We lack the public key for this message
- // QCOMPARE(false, signaturePart->partMetaData()->isGoodSignature);
- // QCOMPARE(GpgME::Signature::KeyMissing, signaturePart->partMetaData()->sigSummary);
+ const auto details = PartModel::signatureDetails(part.get());
+ QCOMPARE(details,
+ QStringLiteral("With certificate: <a href=\"key:1BA323932B3FAA826132C79E8D9860C58F246DE6\">unittest key (no password) &lt;test@kolab.org&gt; "
+ "(8D98 60C5 8F24 6DE6)</a><br/>The signature is valid and the certificate's validity is ultimately trusted."));
}
void testAppleHtmlWithAttachments()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("applehtmlwithattachments.mbox")));
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(part);
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
QVERIFY(part->isHtml());
QCOMPARE(otp.plainTextContent(), QString::fromUtf8("Hi,\n\nThis is an HTML message with attachments.\n\nCheers,\nChristian"));
QCOMPARE(otp.htmlContent(),
QString::fromUtf8(
"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=us-ascii\"></head><body style=\"word-wrap: break-word; "
"-webkit-nbsp-mode: space; line-break: after-white-space;\" class=\"\"><meta http-equiv=\"Content-Type\" content=\"text/html; "
"charset=us-ascii\" class=\"\"><div style=\"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;\" "
"class=\"\">Hi,<div class=\"\"><br class=\"\"></div><blockquote style=\"margin: 0 0 0 40px; border: none; padding: 0px;\" class=\"\"><div "
"class=\"\">This is an <b class=\"\">HTML</b> message with attachments.</div></blockquote><div class=\"\"><br class=\"\"></div><div "
"class=\"\">Cheers,</div><div class=\"\">Christian<img apple-inline=\"yes\" id=\"B9EE68A9-83CA-41CD-A3E4-E5BA301F797A\" class=\"\" "
"src=\"cid:F5B62D1D-E4EC-4C59-AA5A-708525C2AC3C\"></div></div></body></html>"));
auto attachments = otp.collectAttachmentParts();
QCOMPARE(attachments.size(), 1);
}
void testAppleHtmlWithAttachmentsMixed()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("applehtmlwithattachmentsmixed.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(part);
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
QVERIFY(part->isHtml());
QCOMPARE(otp.plainTextContent(), QString::fromUtf8("Hello\n\n\n\nRegards\n\nFsdfsdf"));
QCOMPARE(otp.htmlContent(),
QString::fromUtf8(
"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=us-ascii\"></head><body style=\"word-wrap: break-word; "
"-webkit-nbsp-mode: space; line-break: after-white-space;\" class=\"\"><strike class=\"\">Hello</strike><div class=\"\"><br "
"class=\"\"></div><div class=\"\"></div></body></html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; "
"charset=us-ascii\"></head><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;\" "
"class=\"\"><div class=\"\"></div><div class=\"\"><br class=\"\"></div><div class=\"\"><b class=\"\">Regards</b></div><div class=\"\"><b "
"class=\"\"><br class=\"\"></b></div><div class=\"\">Fsdfsdf</div></body></html>"));
auto attachments = otp.collectAttachmentParts();
QCOMPARE(attachments.size(), 1);
}
void testInvitation()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("invitation.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(part);
QCOMPARE(part->encryptions().size(), 0);
QCOMPARE(part->signatures().size(), 0);
QVERIFY(!part->isHtml());
QVERIFY(part->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal));
auto attachments = otp.collectAttachmentParts();
QCOMPARE(attachments.size(), 0);
}
void testGmailInvitation()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("gmail-invitation.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(part);
QCOMPARE(part->encryptions().size(), 0);
qWarning() << part;
QCOMPARE(part->signatures().size(), 0);
QVERIFY(part->isHtml());
QVERIFY(part->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal));
auto attachments = otp.collectAttachmentParts();
QCOMPARE(attachments.size(), 1);
}
void testMemoryHole()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-memoryhole.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("very secret mesage\n"));
QCOMPARE(part->header("subject")->asUnicodeString(), QStringLiteral("hidden subject"));
QCOMPARE(part->header("from")->asUnicodeString(), QStringLiteral("you@example.com"));
QCOMPARE(part->header("to")->asUnicodeString(), QStringLiteral("me@example.com"));
QCOMPARE(part->header("cc")->asUnicodeString(), QStringLiteral("cc@example.com"));
QCOMPARE(part->header("message-id")->asUnicodeString(), QStringLiteral("<myhiddenreference@me>"));
QCOMPARE(part->header("references")->asUnicodeString(), QStringLiteral("<hiddenreference@hidden>"));
QCOMPARE(part->header("in-reply-to")->asUnicodeString(), QStringLiteral("<hiddenreference@hidden>"));
QCOMPARE(static_cast<const KMime::Headers::Date *>(part->header("date"))->dateTime(), QDateTime(QDate(2018, 1, 2), QTime(3, 4, 5), QTimeZone::utc()));
}
/**
* Required special handling because the list replaces the toplevel part.
*/
void testMemoryHoleWithList()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("cid-links-forwarded-inline.mbox")));
const auto parts = otp.collectContentParts();
auto part = parts[0];
QVERIFY(part->header("references"));
QCOMPARE(part->header("references")->asUnicodeString(), QStringLiteral("<a1777ec781546ccc5dcd4918a5e4e03d@info>"));
}
void testMemoryHoleMultipartMixed()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-memoryhole2.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(),
QStringLiteral("\n\n Fsdflkjdslfj\n\n\nHappy Monday!\n\nBelow you will find a quick overview of the current on-goings. Remember\n"));
QCOMPARE(part->header("subject")->asUnicodeString(), QStringLiteral("This is the subject"));
}
void testMIMESignature()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("text+html-maillinglist.mbox")));
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
for (const auto &part : partList) {
qWarning() << "found part " << part->metaObject()->className();
}
QCOMPARE(partList.size(), 2);
// The actual content
{
auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>();
QVERIFY(bool(part));
}
// The signature
{
auto part = partList[1].dynamicCast<MimeTreeParser::TextMessagePart>();
QVERIFY(bool(part));
QVERIFY(part->text().contains(QStringLiteral("bugzilla mailing list")));
}
}
void testCRLFEncryptedWithSignature()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("crlf-encrypted-with-signature.mbox")));
otp.decryptAndVerify();
otp.print();
QCOMPARE(otp.plainTextContent(), QStringLiteral("CRLF file\n\n-- \nThis is a signature\nWith two lines\n\nAand another line\n"));
}
void testCRLFEncryptedWithSignatureMultipart()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("crlf-encrypted-with-signature-multipart.mbox")));
otp.decryptAndVerify();
otp.print();
// QEXPECT_FAIL("", "because MessagePart::parseInternal uses \n\n to detect encapsulated messages (so 'CRLF file' ends up as header)", Continue);
// QCOMPARE(otp.plainTextContent(), QStringLiteral("CRLF file\n\n-- \nThis is a signature\nWith two lines\n\nAand another line\n"));
// QVERIFY(!otp.htmlContent().contains(QStringLiteral("\r\n")));
}
void testCRLFOutlook()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("outlook.mbox")));
otp.decryptAndVerify();
otp.print();
qWarning() << otp.plainTextContent();
QVERIFY(otp.plainTextContent().startsWith(QStringLiteral("Hi Christian,\n\nhabs gerade getestet:\n\n«This is a test")));
QVERIFY(!otp.htmlContent().contains(QLatin1StringView("\r\n")));
}
void testOpenPGPEncryptedSignedThunderbird()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("openpgp-encrypted-signed-thunderbird.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("sdfsdf\n"));
QCOMPARE(part->charset(), QStringLiteral("utf-8").toLocal8Bit());
QCOMPARE(part->encryptions().size(), 1);
QCOMPARE(part->signatures().size(), 1);
- QCOMPARE(part->signatures()[0]->partMetaData()->isGoodSignature, true);
+ QCOMPARE(PartModel::signatureSecurityLevel(part.get()), PartModel::Good);
QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned);
auto contentAttachmentList = otp.collectAttachmentParts();
QCOMPARE(contentAttachmentList.size(), 1);
// QCOMPARE(contentAttachmentList[0]->content().size(), 1);
QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1);
QCOMPARE(contentAttachmentList[0]->signatures().size(), 1);
QCOMPARE(contentAttachmentList[0]->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted);
QCOMPARE(contentAttachmentList[0]->signatureState(), MimeTreeParser::KMMsgFullySigned);
}
void testSignedForwardOpenpgpSignedEncrypted()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("signed-forward-openpgp-signed-encrypted.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 2);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("bla bla bla"));
part = partList[1].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QString());
QCOMPARE(part->charset(), QStringLiteral("UTF-8").toLocal8Bit());
QCOMPARE(part->signatures().size(), 1);
- QCOMPARE(part->signatures()[0]->partMetaData()->isGoodSignature, true);
+ QCOMPARE(PartModel::signatureSecurityLevel(part.get()), PartModel::Good);
auto contentAttachmentList = otp.collectAttachmentParts();
QCOMPARE(contentAttachmentList.size(), 1);
}
void testSmimeOpaqueSign()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-opaque-sign.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("A simple signed only test."));
}
void testSmimeEncrypted()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-encrypted.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog."));
}
void testSmimeSignedApple()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-signed-apple.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
// QCOMPARE(part->text(), QStringLiteral("A simple signed only test."));
}
void testSmimeEncryptedOctetStream()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-encrypted-octet-stream.mbox")));
otp.print();
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog."));
}
void testSmimeOpaqueSignedEncryptedAttachment()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-opaque-signed-encrypted-attachment.mbox")));
otp.print();
QVERIFY(otp.hasEncryptedParts());
otp.decryptAndVerify();
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("This is an Opaque S/MIME encrypted and signed message with attachment"));
}
void testSmimeOpaqueEncSign()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1StringView("smime-opaque-enc+sign.mbox")));
otp.print();
QVERIFY(otp.hasEncryptedParts());
QVERIFY(!otp.hasSignedParts());
otp.decryptAndVerify();
QVERIFY(otp.hasSignedParts());
otp.print();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 1);
auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->text(), QStringLiteral("Encrypted and signed mail."));
}
};
QTEST_GUILESS_MAIN(MimeTreeParserTest)
#include "mimetreeparsertest.moc"
diff --git a/src/core/cryptohelper.cpp b/src/core/cryptohelper.cpp
index 0589155..f7b64cb 100644
--- a/src/core/cryptohelper.cpp
+++ b/src/core/cryptohelper.cpp
@@ -1,318 +1,320 @@
// 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 <Libkleo/Formatting>
+
#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
{
[[nodiscard]] 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")));
}
[[nodiscard]] 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);
}
[[nodiscard]] bool isContentHeader(const KMime::Headers::Base *header)
{
return header->is("Content-Type") || header->is("Content-Transfer-Encoding") || header->is("Content-Disposition");
}
[[nodiscard]] 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, GpgME::Protocol &protoName)
{
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());
+ const auto blocks = prepareMessageForDecryption(msg->decodedContent());
QByteArray content;
for (const auto &block : blocks) {
if (block.type() == PgpMessageBlock) {
const auto proto = QGpgME::openpgp();
protoName = GpgME::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();
+ qWarning() << "Failed to decrypt:" << Kleo::Formatting::errorAsString(result.error());
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();
+ qWarning() << "Failed to verify:" << Kleo::Formatting::errorAsString(resultVerify.error()) << "inData" << inData;
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;
+ qCWarning(MIMETREEPARSER_CORE_LOG) << "Not encrypted, or we don't recognize the encryption";
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/partmodel.cpp b/src/core/partmodel.cpp
index 76700d4..7128375 100644
--- a/src/core/partmodel.cpp
+++ b/src/core/partmodel.cpp
@@ -1,667 +1,656 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "partmodel.h"
#include "enums.h"
#include "htmlutils.h"
+#include "messagepart.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <QDebug>
#include <QGpgME/Protocol>
#include <QRegularExpression>
#include <QStringLiteral>
#include <QTextDocument>
#include <verificationresult.h>
static std::optional<GpgME::Signature> signatureFromMessagePart(MimeTreeParser::MessagePart *messagePart)
{
const auto signatureState = messagePart->signatureState();
const bool messageIsSigned = signatureState == MimeTreeParser::KMMsgPartiallySigned || signatureState == MimeTreeParser::KMMsgFullySigned;
if (!messageIsSigned) {
return std::nullopt;
}
const auto signatureParts = messagePart->signatures();
Q_ASSERT(!signatureParts.isEmpty());
if (signatureParts.empty()) {
return std::nullopt;
}
const auto signaturePart = signatureParts.front(); // TODO add support for multiple signature
const auto signatures = signaturePart->partMetaData()->verificationResult.signatures();
Q_ASSERT(!signatures.empty());
if (signatures.empty()) {
return std::nullopt;
}
const auto signature = signatures.front(); // TODO add support for multiple signature
return signature;
}
// We return a pair containing the trimmed string, as well as a boolean indicating whether the string was trimmed or not
std::pair<QString, bool> PartModel::trim(const QString &text)
{
// The delimiters have <p>.? prefixed including the .? because sometimes we get a byte order mark <feff> (seen with user-agent:
// Microsoft-MacOutlook/10.1d.0.190908) We match both regulard withspace with \s and non-breaking spaces with \u00A0
const QList<QRegularExpression> delimiters{
// English
QRegularExpression{QStringLiteral("<p>.?-+Original(\\s|\u00A0)Message-+"), QRegularExpression::CaseInsensitiveOption},
// The remainder is not quoted
QRegularExpression{QStringLiteral("<p>.?On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// The remainder is quoted
QRegularExpression{QStringLiteral("&gt; On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// German
// Forwarded
QRegularExpression{QStringLiteral("<p>.?Von:.*</p>"), QRegularExpression::CaseInsensitiveOption},
// Reply
QRegularExpression{QStringLiteral("<p>.?Am.*schrieb.*:</p>"), QRegularExpression::CaseInsensitiveOption},
// Signature
QRegularExpression{QStringLiteral("<p>.?--(\\s|\u00A0)<br>"), QRegularExpression::CaseInsensitiveOption},
};
for (const auto &expression : delimiters) {
auto i = expression.globalMatchView(text);
while (i.hasNext()) {
const auto match = i.next();
const int startOffset = match.capturedStart(0);
// This is a very simplistic detection for an inline reply where we would have the patterns before the actual message content.
// We simply ignore anything we find within the first few lines.
if (startOffset >= 5) {
return {text.mid(0, startOffset), true};
} else {
// Search for the next delimiter
continue;
}
}
}
return {text, false};
}
static QString addCss(const QString &s)
{
// Get the default font from QApplication
static const QString fontFamily = QFont{}.family();
// overflow:hidden ensures no scrollbars are ever shown.
static const QString css = QStringLiteral("<style>\n")
+ QStringLiteral(
"body {\n"
" overflow:hidden;\n"
" font-family: \"%1\" ! important;\n"
" color: #31363b ! important;\n"
" background-color: #fcfcfc ! important\n"
"}\n")
.arg(fontFamily)
+ QStringLiteral("blockquote { \n"
" border-left: 2px solid #bdc3c7 ! important;\n"
"}\n")
+ QStringLiteral("</style>");
const QString header = QLatin1StringView(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html><head><title></title>")
+ css + QLatin1StringView("</head>\n<body>\n");
return header + s + QStringLiteral("</body></html>");
}
class PartModelPrivate
{
public:
PartModelPrivate(PartModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser)
: q(q_ptr)
, mParser(parser)
{
collectContents();
}
~PartModelPrivate() = default;
void checkPart(const MimeTreeParser::MessagePart::Ptr part)
{
mMimeTypeCache[part.data()] = part->mimeType();
// Extract the content of the part and
mContents.insert(part.data(), extractContent(part.data()));
}
// Recursively find encapsulated messages
void findEncapsulated(const MimeTreeParser::EncapsulatedRfc822MessagePart::Ptr &e)
{
mEncapsulatedParts[e.data()] = mParser->collectContentParts(e);
for (const auto &subPart : std::as_const(mEncapsulatedParts[e.data()])) {
checkPart(subPart);
mParents[subPart.data()] = e.data();
if (auto encapsulatedSub = subPart.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedSub);
}
}
}
QVariant extractContent(MimeTreeParser::MessagePart *messagePart)
{
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return alternativePart->icalContent();
}
}
auto preprocessPlaintext = [&](const QString &text) {
// Reduce consecutive new lines to never exceed 2
auto cleaned = text;
cleaned.replace(QRegularExpression(QStringLiteral("[\n\r]{2,}")), QStringLiteral("\n\n"));
// We always do rich text (so we get highlighted links and stuff).
const auto html = Qt::convertFromPlainText(cleaned, Qt::WhiteSpaceNormal);
if (trimMail) {
const auto result = PartModel::trim(html);
isTrimmed = result.second;
Q_EMIT q->trimMailChanged();
return MimeTreeParser::linkify(result.first);
}
return MimeTreeParser::linkify(html);
};
if (messagePart->isHtml()) {
if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
containsHtmlAndPlain = true;
Q_EMIT q->containsHtmlChanged();
if (!showHtml) {
return preprocessPlaintext(messagePart->plaintextContent());
}
}
return addCss(mParser->resolveCidLinks(messagePart->htmlContent()));
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (node && mMimeTypeCache[attachmentPart] == QByteArrayLiteral("text/calendar")) {
return messagePart->text();
}
}
return preprocessPlaintext(messagePart->text());
}
QVariant contentForPart(MimeTreeParser::MessagePart *messagePart) const
{
return mContents.value(messagePart);
}
void collectContents()
{
mEncapsulatedParts.clear();
mParents.clear();
mContents.clear();
containsHtmlAndPlain = false;
isTrimmed = false;
const auto parts = mParser->collectContentParts();
MimeTreeParser::MessagePart::List filteredParts;
for (const auto &part : parts) {
if (part->node()) {
const auto contentType = part->node()->contentType();
if (contentType && contentType->hasParameter("protected-headers")) {
const auto contentDisposition = part->node()->contentDisposition();
if (contentDisposition && contentDisposition->disposition() == KMime::Headers::CDinline) {
continue;
}
}
}
filteredParts << part;
}
for (const auto &part : std::as_const(filteredParts)) {
checkPart(part);
if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedPart);
}
}
for (const auto &part : std::as_const(filteredParts)) {
if (mMimeTypeCache[part.data()] == QByteArrayLiteral("text/calendar")) {
mParts.prepend(part);
} else {
mParts.append(part);
}
}
}
PartModel *q;
MimeTreeParser::MessagePart::List mParts;
QHash<MimeTreeParser::MessagePart *, QByteArray> mMimeTypeCache;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart::List> mEncapsulatedParts;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart *> mParents;
QMap<MimeTreeParser::MessagePart *, QVariant> mContents;
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
bool showHtml{false};
bool containsHtmlAndPlain{false};
bool trimMail{false};
bool isTrimmed{false};
};
PartModel::PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
: d(std::unique_ptr<PartModelPrivate>(new PartModelPrivate(this, parser)))
{
}
PartModel::~PartModel() = default;
void PartModel::setShowHtml(bool html)
{
if (d->showHtml != html) {
beginResetModel();
d->showHtml = html;
d->collectContents();
endResetModel();
Q_EMIT showHtmlChanged();
}
}
bool PartModel::showHtml() const
{
return d->showHtml;
}
void PartModel::setTrimMail(bool trim)
{
if (d->trimMail != trim) {
beginResetModel();
d->trimMail = trim;
d->collectContents();
endResetModel();
Q_EMIT trimMailChanged();
}
}
bool PartModel::trimMail() const
{
return d->trimMail;
}
bool PartModel::isTrimmed() const
{
return d->isTrimmed;
}
bool PartModel::containsHtml() const
{
return d->containsHtmlAndPlain;
}
QHash<int, QByteArray> PartModel::roleNames() const
{
return {
{TypeRole, QByteArrayLiteral("type")},
{ContentRole, QByteArrayLiteral("content")},
{IsEmbeddedRole, QByteArrayLiteral("isEmbedded")},
{SidebarSecurityLevelRole, QByteArrayLiteral("sidebarSecurityLevel")},
{EncryptionSecurityLevelRole, QByteArrayLiteral("encryptionSecurityLevel")},
{SignatureSecurityLevelRole, QByteArrayLiteral("signatureSecurityLevel")},
{EncryptionSecurityLevelRole, QByteArrayLiteral("encryptionSecurityLevel")},
{ErrorType, QByteArrayLiteral("errorType")},
{ErrorString, QByteArrayLiteral("errorString")},
{IsErrorRole, QByteArrayLiteral("error")},
{SenderRole, QByteArrayLiteral("sender")},
{SignatureDetailsRole, QByteArrayLiteral("signatureDetails")},
{SignatureIconNameRole, QByteArrayLiteral("signatureIconName")},
{EncryptionDetails, QByteArrayLiteral("encryptionDetails")},
{EncryptionIconNameRole, QByteArrayLiteral("encryptionIconName")},
{DateRole, QByteArrayLiteral("date")},
};
}
QModelIndex PartModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0) {
return QModelIndex();
}
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
if (row < parts.size()) {
return createIndex(row, column, parts.at(row).data());
}
}
return QModelIndex();
}
if (row < d->mParts.size()) {
return createIndex(row, column, d->mParts.at(row).data());
}
return QModelIndex();
}
SignatureInfo encryptionInfo(MimeTreeParser::MessagePart *messagePart)
{
SignatureInfo signatureInfo;
const auto encryptions = messagePart->encryptions();
if (encryptions.size() > 1) {
qWarning() << "Can't deal with more than one encryption";
}
for (const auto &encryptionPart : encryptions) {
signatureInfo.keyId = encryptionPart->partMetaData()->keyId;
signatureInfo.cryptoProto = encryptionPart->cryptoProto();
signatureInfo.decryptRecipients = encryptionPart->decryptRecipients();
}
return signatureInfo;
};
template<typename T>
const T *findHeader(KMime::Content *content)
{
+ if (!content) {
+ return {};
+ }
auto header = content->header<T>();
if (header || !content->parent()) {
return header;
}
return findHeader<T>(content->parent());
}
+PartModel::SecurityLevel PartModel::signatureSecurityLevel(MimeTreeParser::MessagePart *messagePart)
+{
+ auto signature = signatureFromMessagePart(messagePart);
+ if (!signature) {
+ return SecurityLevel::Unknow;
+ }
+
+ const auto summary = signature->summary();
+
+ if (summary & GpgME::Signature::Summary::Red) {
+ return SecurityLevel::Bad;
+ }
+ if (summary & GpgME::Signature::Summary::Valid) {
+ return SecurityLevel::Good;
+ }
+
+ return SecurityLevel::NotSoGood;
+}
+
+QString PartModel::signatureDetails(MimeTreeParser::MessagePart *messagePart)
+{
+ auto signature = signatureFromMessagePart(messagePart);
+ if (!signature) {
+ return QString{};
+ }
+
+ // guess sender from mime node or parent node
+ auto from = findHeader<KMime::Headers::From>(messagePart->node());
+ if (from) {
+ const auto mailboxes = from->mailboxes();
+ if (!mailboxes.isEmpty()) {
+ auto mailBox = mailboxes.front();
+ if (mailBox.hasAddress()) {
+ return Kleo::Formatting::prettySignature(*signature, mailboxes.front());
+ }
+ }
+ }
+ return Kleo::Formatting::prettySignature(*signature, {});
+}
+
QVariant PartModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.internalPointer()) {
const auto messagePart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer());
// qWarning() << "Found message part " << messagePart->metaObject()->className() << messagePart->partMetaData()->status << messagePart->error();
Q_ASSERT(messagePart);
switch (role) {
case Qt::DisplayRole:
return QStringLiteral("Content%1");
case SenderRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->from();
}
return {};
}
case DateRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->date();
}
return {};
}
case TypeRole: {
if (messagePart->error()) {
return QVariant::fromValue(Types::Error);
}
if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return QVariant::fromValue(Types::Encapsulated);
}
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return QVariant::fromValue(Types::Ical);
}
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (!node) {
qWarning() << "no content for attachment";
return {};
}
if (d->mMimeTypeCache[attachmentPart] == QByteArrayLiteral("text/calendar")) {
return QVariant::fromValue(Types::Ical);
}
}
if (!d->showHtml && d->containsHtmlAndPlain) {
return QVariant::fromValue(Types::Plain);
}
// For simple html we don't need a browser
auto complexHtml = [&] {
if (messagePart->isHtml()) {
const auto text = messagePart->htmlContent();
if (text.contains(QStringLiteral("<!DOCTYPE html PUBLIC"))) {
// We can probably deal with this if it adheres to the strict dtd
//(that's what our composer produces as well)
if (!text.contains(QStringLiteral("http://www.w3.org/TR/REC-html40/strict.dtd"))) {
return true;
}
}
// Blockquotes don't support any styling which would be necessary so they become readable.
if (text.contains(QStringLiteral("blockquote"))) {
return true;
}
// Media queries are too advanced
if (text.contains(QStringLiteral("@media"))) {
return true;
}
// auto css properties are not supported e.g margin-left: auto;
if (text.contains(QStringLiteral(": auto;"))) {
return true;
}
return false;
} else {
return false;
}
}();
if (complexHtml) {
return QVariant::fromValue(Types::Html);
}
return QVariant::fromValue(Types::Plain);
}
case IsEmbeddedRole:
return false;
case IsErrorRole:
return messagePart->error();
case ContentRole:
return d->contentForPart(messagePart);
case SidebarSecurityLevelRole: {
const auto signature = index.data(SignatureSecurityLevelRole).value<SecurityLevel>();
const auto encryption = index.data(EncryptionSecurityLevelRole).value<SecurityLevel>();
if (signature == SecurityLevel::Bad || encryption == SecurityLevel::Bad) {
return SecurityLevel::Bad;
}
if (signature == SecurityLevel::NotSoGood || encryption == SecurityLevel::NotSoGood) {
return SecurityLevel::NotSoGood;
}
if (signature == SecurityLevel::Good || encryption == SecurityLevel::Good) {
return SecurityLevel::Good;
}
return SecurityLevel::Unknow;
}
- case SignatureSecurityLevelRole: {
+ case SignatureSecurityLevelRole:
// Color displayed for the signature info box
- auto signature = signatureFromMessagePart(messagePart);
- if (!signature) {
- return SecurityLevel::Unknow;
- }
-
- const auto summary = signature->summary();
-
- if (summary & GpgME::Signature::Summary::Red) {
- return SecurityLevel::Bad;
- }
- if (summary & GpgME::Signature::Summary::Valid) {
- return SecurityLevel::Good;
- }
-
- return SecurityLevel::NotSoGood;
- }
+ return signatureSecurityLevel(messagePart);
case EncryptionSecurityLevelRole: {
// Color displayed for the encryption info box
const auto encryption = messagePart->encryptionState();
const bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messagePart->error()) {
return SecurityLevel::Bad;
}
return messageIsEncrypted ? SecurityLevel::Good : SecurityLevel::Unknow;
}
case EncryptionIconNameRole: {
const auto encryption = messagePart->encryptionState();
const bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messagePart->error()) {
return QStringLiteral("data-error");
}
return messageIsEncrypted ? QStringLiteral("mail-encrypted") : QString();
}
case SignatureIconNameRole: {
auto signature = signatureFromMessagePart(messagePart);
if (!signature) {
return QString{};
}
const auto summary = signature->summary();
if (summary & GpgME::Signature::Valid) {
return QStringLiteral("mail-signed");
} else if (summary & GpgME::Signature::Red) {
return QStringLiteral("data-error");
} else {
return QStringLiteral("data-warning");
}
}
- case SignatureDetailsRole: {
- auto signature = signatureFromMessagePart(messagePart);
- if (!signature) {
- return QString{};
- }
-
- GpgME::Key key = signature->key();
- if (key.isNull()) {
- key = Kleo::KeyCache::instance()->findByFingerprint(signature->fingerprint());
- }
- if (key.isNull() && signature->fingerprint()) {
- // try to find a subkey that was used for signing;
- // assumes that the key ID is the last 16 characters of the fingerprint
- const auto fpr = std::string_view{signature->fingerprint()};
- const auto keyID = std::string{fpr, fpr.size() - 16, 16};
- const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID});
- if (subkeys.size() > 0) {
- key = subkeys[0].parent();
- }
- }
- if (key.isNull()) {
- qCWarning(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature->fingerprint();
- }
-
- QStringList senderUserId;
- for (int i = 0, count = key.userIDs().size(); i < count; i++) {
- senderUserId << QString::fromUtf8(key.userID(i).email());
- }
-
- // guess sender from mime node or parent node
- auto from = findHeader<KMime::Headers::From>(messagePart->node());
- if (from) {
- const auto mailboxes = from->mailboxes();
- if (!mailboxes.isEmpty()) {
- auto mailBox = mailboxes.front();
- if (mailBox.hasAddress()) {
- return Kleo::Formatting::prettySignature(*signature, mailboxes.front());
- }
- }
- }
- return Kleo::Formatting::prettySignature(*signature, {});
- }
+ case SignatureDetailsRole:
+ return signatureDetails(messagePart);
case EncryptionDetails:
return QVariant::fromValue(encryptionInfo(messagePart));
case ErrorType:
return messagePart->error();
case ErrorString: {
switch (messagePart->error()) {
case MimeTreeParser::MessagePart::NoKeyError: {
if (auto encryptedMessagePart = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(messagePart)) {
if (encryptedMessagePart->isNoSecKey()) {
QString errorMessage;
if (encryptedMessagePart->cryptoProto() == QGpgME::smime()) {
errorMessage +=
i18ndc("mimetreeparser", "@info:status", "You cannot decrypt this message.");
} else {
errorMessage +=
i18ndc("mimetreeparser", "@info:status", "You cannot decrypt this message.");
}
if (!encryptedMessagePart->decryptRecipients().empty()) {
errorMessage += QLatin1Char(' ')
+ i18ndcp("mimetreeparser",
"@info:status",
"The message is encrypted for the following recipient:",
"The message is encrypted for the following recipients:",
encryptedMessagePart->decryptRecipients().size());
errorMessage +=
MimeTreeParser::decryptRecipientsToHtml(encryptedMessagePart->decryptRecipients(), encryptedMessagePart->cryptoProto());
}
return errorMessage;
}
}
}
return messagePart->errorString();
case MimeTreeParser::MessagePart::PassphraseError:
return i18ndc("mimetreeparser", "@info:status", "Wrong passphrase.");
case MimeTreeParser::MessagePart::UnknownError:
break;
default:
break;
}
return messagePart->errorString();
}
}
}
return QVariant();
}
QModelIndex PartModel::parent(const QModelIndex &index) const
{
if (index.isValid()) {
if (auto indexPart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer())) {
for (const auto &part : std::as_const(d->mParts)) {
if (part.data() == indexPart) {
return QModelIndex();
}
}
const auto parentPart = d->mParents[indexPart];
Q_ASSERT(parentPart);
int row = 0;
const auto parts = d->mEncapsulatedParts[parentPart];
for (const auto &part : parts) {
if (part.data() == indexPart) {
break;
}
row++;
}
return createIndex(row, 0, parentPart);
}
return {};
}
return {};
}
int PartModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
return parts.size();
}
return 0;
}
return d->mParts.count();
}
int PartModel::columnCount(const QModelIndex &) const
{
return 1;
}
#include "moc_partmodel.cpp"
diff --git a/src/core/partmodel.h b/src/core/partmodel.h
index 2f70018..9591d49 100644
--- a/src/core/partmodel.h
+++ b/src/core/partmodel.h
@@ -1,142 +1,146 @@
// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QAbstractItemModel>
#include <QModelIndex>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
+#include "messagepart.h"
#include "mimetreeparser_core_export.h"
#include <memory>
#include <verificationresult.h>
namespace QGpgME
{
class Protocol;
}
namespace MimeTreeParser
{
class ObjectTreeParser;
}
class PartModelPrivate;
class MIMETREEPARSER_CORE_EXPORT PartModel : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY(bool showHtml READ showHtml WRITE setShowHtml NOTIFY showHtmlChanged)
Q_PROPERTY(bool containsHtml READ containsHtml NOTIFY containsHtmlChanged)
Q_PROPERTY(bool trimMail READ trimMail WRITE setTrimMail NOTIFY trimMailChanged)
Q_PROPERTY(bool isTrimmed READ isTrimmed NOTIFY trimMailChanged)
public:
PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser);
~PartModel() override;
static std::pair<QString, bool> trim(const QString &text);
public:
enum class Types : quint8 {
Error,
Encapsulated,
Ical,
Plain,
None,
Html,
};
Q_ENUM(Types);
enum Roles {
TypeRole = Qt::UserRole + 1,
ContentRole,
IsEmbeddedRole,
IsErrorRole,
SidebarSecurityLevelRole,
EncryptionSecurityLevelRole,
EncryptionIconNameRole,
SignatureSecurityLevelRole,
SignatureDetailsRole,
SignatureIconNameRole,
EncryptionDetails,
ErrorType,
ErrorString,
SenderRole,
DateRole,
};
/// This enum maps directly to color displayed in the UI for the following elements:
/// - Encryption info box
/// - Signature info box
/// - Sidebar (worse of the two above)
enum SecurityLevel {
Unknow, ///< Do not display element (not encrypted or not signed)
Good, ///< Green
NotSoGood, ///< Orange
Bad, ////< Red
};
Q_ENUM(SecurityLevel);
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QModelIndex parent(const QModelIndex &index) const override;
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void setShowHtml(bool html);
[[nodiscard]] bool showHtml() const;
[[nodiscard]] bool containsHtml() const;
void setTrimMail(bool trim);
[[nodiscard]] bool trimMail() const;
[[nodiscard]] bool isTrimmed() const;
+ static SecurityLevel signatureSecurityLevel(MimeTreeParser::MessagePart *messagePart);
+ static QString signatureDetails(MimeTreeParser::MessagePart *messagePart);
+
Q_SIGNALS:
void showHtmlChanged();
void trimMailChanged();
void containsHtmlChanged();
private:
std::unique_ptr<PartModelPrivate> d;
};
class MIMETREEPARSER_CORE_EXPORT SignatureInfo
{
Q_GADGET
Q_PROPERTY(QByteArray keyId MEMBER keyId CONSTANT)
Q_PROPERTY(bool keyMissing MEMBER keyMissing CONSTANT)
Q_PROPERTY(bool keyRevoked MEMBER keyRevoked CONSTANT)
Q_PROPERTY(bool keyExpired MEMBER keyExpired CONSTANT)
Q_PROPERTY(bool sigExpired MEMBER sigExpired CONSTANT)
Q_PROPERTY(bool crlMissing MEMBER crlMissing CONSTANT)
Q_PROPERTY(bool crlTooOld MEMBER crlTooOld CONSTANT)
Q_PROPERTY(QString signer MEMBER signer CONSTANT)
Q_PROPERTY(QStringList signerMailAddresses MEMBER signerMailAddresses CONSTANT)
Q_PROPERTY(bool signatureIsGood MEMBER signatureIsGood CONSTANT)
Q_PROPERTY(bool isCompliant MEMBER isCompliant CONSTANT)
/// Validity information of the key who signed the message.
Q_PROPERTY(QString keyTrust MEMBER keyTrust CONSTANT)
Q_PROPERTY(GpgME::Signature::Summary signatureSummary MEMBER signatureSummary CONSTANT)
public:
bool keyRevoked = false;
bool keyExpired = false;
bool sigExpired = false;
bool keyMissing = false;
bool crlMissing = false;
bool crlTooOld = false;
bool isCompliant = false;
QString keyTrust;
QByteArray keyId;
const QGpgME::Protocol *cryptoProto = nullptr;
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> decryptRecipients;
QString signer;
GpgME::Signature::Summary signatureSummary;
QStringList signerMailAddresses;
bool signatureIsGood = false;
};

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 4:56 PM (15 h, 13 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
21/f1/92726b7badcdbab3d7275edae29c

Event Timeline