Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index e9ab049..429a7aa 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/mimetreeparsertest.cpp b/autotests/core/mimetreeparsertest.cpp
index 2612074..150b1d5 100644
--- a/autotests/core/mimetreeparsertest.cpp
+++ b/autotests/core/mimetreeparsertest.cpp
@@ -1,780 +1,789 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsystems.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#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 öäü")));
- auto signaturePart = part->signatures().first();
- QCOMPARE(signaturePart->partMetaData()->isGoodSignature, true);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyMissing, false);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyExpired, false);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyRevoked, false);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::SigExpired, false);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlMissing, false);
- QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlTooOld, false);
- QCOMPARE(signaturePart->partMetaData()->keyId, 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 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")}});
}
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(signaturePart->partMetaData()->isGoodSignature, true);
+ QCOMPARE(signature.summary() & GpgME::Signature::Valid, true);
}
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);
+ // 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);
}
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(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);
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/messagepart.cpp b/src/core/messagepart.cpp
index 1ae1d52..55f79b6 100644
--- a/src/core/messagepart.cpp
+++ b/src/core/messagepart.cpp
@@ -1,1117 +1,983 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messagepart.h"
#include "cryptohelper.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <KMime/Content>
#include <Libkleo/Compliance>
#include <Libkleo/KeyCache>
#include <QGpgME/DN>
#include <QGpgME/DecryptVerifyJob>
#include <QGpgME/Protocol>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>
#include <QStringDecoder>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme.h>
#include <libkleo/formatting.h>
using namespace MimeTreeParser;
//------MessagePart-----------------------
MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node)
: mText(text)
, mOtp(otp)
, mParentPart(nullptr)
, mNode(node) // only null for messagepartlist
, mError(NoError)
, mRoot(false)
{
}
MessagePart::~MessagePart()
{
for (auto n : std::as_const(mNodesToDelete)) {
delete n;
}
}
MessagePart::Disposition MessagePart::disposition() const
{
if (!mNode) {
return Invalid;
}
const auto cd = mNode->contentDisposition(false);
if (!cd) {
return Invalid;
}
switch (cd->disposition()) {
case KMime::Headers::CDinline:
return Inline;
case KMime::Headers::CDattachment:
return Attachment;
default:
return Invalid;
}
}
QString MessagePart::filename() const
{
if (!mNode) {
return {};
}
if (const auto cd = mNode->contentDisposition(false)) {
const auto name = cd->filename();
// Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways.
// Not the recommended way, but exists.
if (!name.isEmpty()) {
return name;
}
}
if (const auto ct = mNode->contentType(false)) {
return ct->name();
}
return {};
}
static KMime::Headers::ContentType *contentType(KMime::Content *node)
{
if (node) {
return node->contentType(false);
}
return nullptr;
}
QByteArray MessagePart::charset() const
{
if (!mNode) {
return QByteArrayLiteral("us-ascii");
}
if (auto ct = contentType(mNode)) {
return ct->charset();
}
// Per rfc2045 us-ascii is the default
return QByteArrayLiteral("us-ascii");
}
QByteArray MessagePart::mimeType() const
{
if (auto ct = contentType(mNode)) {
return ct->mimeType();
}
return {};
}
bool MessagePart::isText() const
{
if (auto ct = contentType(mNode)) {
return ct->isText();
}
return false;
}
MessagePart::Error MessagePart::error() const
{
return mError;
}
QString MessagePart::errorString() const
{
return mMetaData.errorText;
}
PartMetaData *MessagePart::partMetaData()
{
return &mMetaData;
}
bool MessagePart::isAttachment() const
{
if (mNode) {
return KMime::isAttachment(mNode);
}
return false;
}
KMime::Content *MessagePart::node() const
{
return mNode;
}
void MessagePart::setIsRoot(bool root)
{
mRoot = root;
}
bool MessagePart::isRoot() const
{
return mRoot;
}
QString MessagePart::text() const
{
return mText;
}
void MessagePart::setText(const QString &text)
{
mText = text;
}
bool MessagePart::isHtml() const
{
return false;
}
MessagePart *MessagePart::parentPart() const
{
return mParentPart;
}
void MessagePart::setParentPart(MessagePart *parentPart)
{
mParentPart = parentPart;
}
QString MessagePart::htmlContent() const
{
return text();
}
QString MessagePart::plaintextContent() const
{
return text();
}
void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
{
auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
mRoot = subMessagePart->isRoot();
for (const auto &part : std::as_const(subMessagePart->subParts())) {
appendSubPart(part);
}
}
void MessagePart::parseInternal(const QByteArray &data)
{
auto tempNode = new KMime::Content();
const auto lfData = KMime::CRLFtoLF(data);
// We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts,
// or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this.
if (lfData.contains("\n\n")) {
tempNode->setContent(lfData);
} else {
tempNode->setBody(lfData);
}
tempNode->parse();
tempNode->contentType()->setCharset(charset());
bindLifetime(tempNode);
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString(QByteArrayLiteral("temporary node"));
}
parseInternal(tempNode);
}
QString MessagePart::renderInternalText() const
{
QString text;
for (const auto &mp : subParts()) {
text += mp->text();
}
return text;
}
void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
{
messagePart->setParentPart(this);
mBlocks.append(messagePart);
}
const QList<MessagePart::Ptr> &MessagePart::subParts() const
{
return mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !mBlocks.isEmpty();
}
QList<SignedMessagePart *> MessagePart::signatures() const
{
QList<SignedMessagePart *> list;
if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
QList<EncryptedMessagePart *> MessagePart::encryptions() const
{
QList<EncryptedMessagePart *> list;
if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
KMMsgEncryptionState MessagePart::encryptionState() const
{
if (!encryptions().isEmpty()) {
return KMMsgFullyEncrypted;
}
return KMMsgNotEncrypted;
}
KMMsgSignatureState MessagePart::signatureState() const
{
if (!signatures().isEmpty()) {
return KMMsgFullySigned;
}
return KMMsgNotSigned;
}
void MessagePart::bindLifetime(KMime::Content *node)
{
mNodesToDelete << node;
}
KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const
{
if (node() && node()->hasHeader(header)) {
return node()->headerByType(header);
}
if (auto parent = parentPart()) {
return parent->header(header);
}
return nullptr;
}
//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
QString MessagePartList::text() const
{
return renderInternalText();
}
QString MessagePartList::plaintextContent() const
{
return QString();
}
QString MessagePartList::htmlContent() const
{
return QString();
}
//-----TextMessageBlock----------------------
TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePartList(otp, node)
, mSignatureState(KMMsgSignatureStateUnknown)
, mEncryptionState(KMMsgEncryptionStateUnknown)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
parseContent();
}
void TextMessagePart::parseContent()
{
mSignatureState = KMMsgNotSigned;
mEncryptionState = KMMsgNotEncrypted;
const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
// We also get blocks for unencrypted messages
if (!blocks.isEmpty()) {
auto aCodec = QStringDecoder(mOtp->codecNameFor(mNode).constData());
const auto cryptProto = QGpgME::openpgp();
/* The (overall) signature/encrypted status is broken
* if one unencrypted part is at the beginning or in the middle
* because mailmain adds an unencrypted part at the end this should not break the overall status
*
* That's why we first set the tmp status and if one crypted/signed block comes afterwards, than
* the status is set to unencryped
*/
bool fullySignedOrEncrypted = true;
bool fullySignedOrEncryptedTmp = true;
for (const auto &block : blocks) {
if (!fullySignedOrEncryptedTmp) {
fullySignedOrEncrypted = false;
}
if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
fullySignedOrEncryptedTmp = false;
appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(KMime::CRLFtoLF(block.text())))));
} else if (block.type() == PgpMessageBlock) {
auto content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false));
mp->bindLifetime(content);
mp->setIsEncrypted(true);
appendSubPart(mp);
} else if (block.type() == ClearsignedBlock) {
auto content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false));
mp->bindLifetime(content);
- mp->setIsSigned(true);
appendSubPart(mp);
} else {
continue;
}
const auto mp = subParts().last().staticCast<MessagePart>();
const PartMetaData *messagePart(mp->partMetaData());
- if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
+ if (!messagePart->isEncrypted && !messagePart->isSigned() && !block.text().trimmed().isEmpty()) {
mp->setText(aCodec.decode(KMime::CRLFtoLF(block.text())));
}
if (messagePart->isEncrypted) {
mEncryptionState = KMMsgPartiallyEncrypted;
}
- if (messagePart->isSigned) {
+ if (messagePart->isSigned()) {
mSignatureState = KMMsgPartiallySigned;
}
}
// Do we have an fully Signed/Encrypted Message?
if (fullySignedOrEncrypted) {
if (mSignatureState == KMMsgPartiallySigned) {
mSignatureState = KMMsgFullySigned;
}
if (mEncryptionState == KMMsgPartiallyEncrypted) {
mEncryptionState = KMMsgFullyEncrypted;
}
}
}
}
KMMsgEncryptionState TextMessagePart::encryptionState() const
{
if (mEncryptionState == KMMsgNotEncrypted) {
return MessagePart::encryptionState();
}
return mEncryptionState;
}
KMMsgSignatureState TextMessagePart::signatureState() const
{
if (mSignatureState == KMMsgNotSigned) {
return MessagePart::signatureState();
}
return mSignatureState;
}
//-----AttachmentMessageBlock----------------------
AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: TextMessagePart(otp, node)
{
}
//-----HtmlMessageBlock----------------------
HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
setText(QStringDecoder(mOtp->codecNameFor(mNode).constData()).decode(KMime::CRLFtoLF(mNode->decodedContent())));
}
//-----MimeMessageBlock----------------------
MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
parseInternal(mNode, onlyOneMimePart);
}
MimeMessagePart::~MimeMessagePart()
{
}
QString MimeMessagePart::text() const
{
return renderInternalText();
}
QString MimeMessagePart::plaintextContent() const
{
return QString();
}
QString MimeMessagePart::htmlContent() const
{
return QString();
}
//-----AlternativeMessagePart----------------------
AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) {
mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
}
if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) {
mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true));
}
if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) {
mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true));
} else {
// If we didn't find the HTML part as the first child of the multipart/alternative, it might
// be that this is a HTML message with images, and text/plain and multipart/related are the
// immediate children of this multipart/alternative node.
// In this case, the HTML node is a child of multipart/related.
// In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments
// with html content.
//
// In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild.
auto data = [&] {
if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) {
return d;
}
return findTypeInDirectChildren(mNode, "multipart/mixed");
}();
if (data) {
QString htmlContent;
const auto parts = data->contents();
for (auto p : parts) {
if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) {
htmlContent += MimeMessagePart(mOtp, p, true).text();
} else if (KMime::isAttachment(p)) {
appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true)));
}
}
mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr));
}
}
}
AlternativeMessagePart::~AlternativeMessagePart()
{
}
QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes()
{
return mChildParts.keys();
}
QString AlternativeMessagePart::text() const
{
if (mChildParts.contains(MultipartPlain)) {
return mChildParts[MultipartPlain]->text();
}
return QString();
}
bool AlternativeMessagePart::isHtml() const
{
return mChildParts.contains(MultipartHtml);
}
QString AlternativeMessagePart::plaintextContent() const
{
return text();
}
QString AlternativeMessagePart::htmlContent() const
{
if (mChildParts.contains(MultipartHtml)) {
return mChildParts[MultipartHtml]->text();
} else {
return plaintextContent();
}
}
QString AlternativeMessagePart::icalContent() const
{
if (mChildParts.contains(MultipartIcal)) {
return mChildParts[MultipartIcal]->text();
}
return {};
}
//-----CertMessageBlock----------------------
CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto)
: MessagePart(otp, QString(), node)
, mCryptoProto(cryptoProto)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
}
CertMessagePart::~CertMessagePart()
{
}
QString CertMessagePart::text() const
{
return QString();
}
//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
const QGpgME::Protocol *cryptoProto,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption)
: MessagePart(otp, {}, node)
, mParseAfterDecryption(parseAfterDecryption)
, mCryptoProto(cryptoProto)
, mSignedData(signedData)
{
- mMetaData.isSigned = true;
- mMetaData.isGoodSignature = false;
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
+ qWarning() << "created" << this;
}
SignedMessagePart::~SignedMessagePart()
{
-}
-
-void SignedMessagePart::setIsSigned(bool isSigned)
-{
- mMetaData.isSigned = isSigned;
-}
-
-bool SignedMessagePart::isSigned() const
-{
- return mMetaData.isSigned;
+ qWarning() << "desctrued" << this;
}
static QString prettifyDN(const char *uid)
{
// We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for.
return QString::fromUtf8(uid);
}
const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
void SignedMessagePart::startVerification()
{
if (!mSignedData) {
return;
}
- mMetaData.isSigned = false;
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
auto codec = QStringDecoder(mOtp->codecNameFor(mSignedData).constData());
// If we have a mNode, this is a detached signature
if (mNode) {
const auto signature = mNode->decodedContent();
// This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF)
const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent());
const auto job = mCryptoProto->verifyDetachedJob();
setVerificationResult(job->exec(signature, signedData), signedData);
job->deleteLater();
setText(codec.decode(KMime::CRLFtoLF(signedData)));
} else {
QByteArray outdata;
const auto job = mCryptoProto->verifyOpaqueJob();
setVerificationResult(job->exec(mSignedData->decodedContent(), outdata), outdata);
job->deleteLater();
setText(codec.decode(KMime::CRLFtoLF(outdata)));
}
- if (!mMetaData.isSigned) {
+ if (!mMetaData.isSigned()) {
mMetaData.creationTime = QDateTime();
}
}
-static int signatureToStatus(const GpgME::Signature &sig)
-{
- switch (sig.status().code()) {
- case GPG_ERR_NO_ERROR:
- return GPGME_SIG_STAT_GOOD;
- case GPG_ERR_BAD_SIGNATURE:
- return GPGME_SIG_STAT_BAD;
- case GPG_ERR_NO_PUBKEY:
- return GPGME_SIG_STAT_NOKEY;
- case GPG_ERR_NO_DATA:
- return GPGME_SIG_STAT_NOSIG;
- case GPG_ERR_SIG_EXPIRED:
- return GPGME_SIG_STAT_GOOD_EXP;
- case GPG_ERR_KEY_EXPIRED:
- return GPGME_SIG_STAT_GOOD_EXPKEY;
- default:
- return GPGME_SIG_STAT_ERROR;
- }
-}
-
-static QString senderUserIdValidity(const GpgME::UserID &uid)
-{
- switch (uid.validity()) {
- case GpgME::UserID::Ultimate:
- return i18n("The certificate is marked as your own.");
- case GpgME::UserID::Full:
- return i18n("The certificate belongs to this sender.");
- case GpgME::UserID::Marginal:
- return i18n("The trust model indicates marginally that the certificate belongs to this sender.");
- case GpgME::UserID::Never:
- return i18n("This certificate should not be used.");
- case GpgME::UserID::Undefined:
- case GpgME::UserID::Unknown:
- default:
- return i18n("There is no indication that this certificate belongs to this sender.");
- }
-}
-
-void SignedMessagePart::sigStatusToMetaData()
-{
- if (!partMetaData()->isSigned) {
- return;
- }
-
- GpgME::Signature signature = mSignatures.front();
- mMetaData.status_code = signatureToStatus(signature);
- mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD;
- // save extended signature status flags
- mMetaData.sigSummary = signature.summary();
-
- // Search for the key by its fingerprint so that we can check for
- // trust etc.
- GpgME::Key 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()) {
- qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint();
- }
-
- if (key.keyID()) {
- partMetaData()->keyId = key.keyID();
- }
- if (partMetaData()->keyId.isEmpty()) {
- partMetaData()->keyId = signature.fingerprint();
- }
- partMetaData()->keyTrust = senderUserIdValidity(key.userID(0));
- partMetaData()->signatureSummary = signature.summary();
-
- if (key.numUserIDs() > 0 && key.userID(0).id()) {
- partMetaData()->signer = prettifyDN(key.userID(0).id());
- }
- for (const auto &uid : key.userIDs()) {
- // The following if /should/ always result in TRUE but we
- // won't trust implicitly the plugin that gave us these data.
- if (uid.email()) {
- QString email = QString::fromUtf8(uid.email());
- if (!email.isEmpty()) {
- partMetaData()->signerMailAddresses.append(email);
- }
- }
- }
-
- if (signature.creationTime()) {
- partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
- } else {
- partMetaData()->creationTime = QDateTime();
- }
- if (partMetaData()->signer.isEmpty()) {
- if (key.numUserIDs() > 0 && key.userID(0).name()) {
- partMetaData()->signer = prettifyDN(key.userID(0).name());
- }
- if (!partMetaData()->signerMailAddresses.empty()) {
- if (partMetaData()->signer.isEmpty()) {
- partMetaData()->signer = partMetaData()->signerMailAddresses.front();
- } else {
- partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
- }
- }
- }
- if (Kleo::DeVSCompliance::isCompliant()) {
- partMetaData()->isCompliant = signature.isDeVs();
- partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs());
- } else {
- partMetaData()->isCompliant = true;
- }
-}
-
void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData)
{
- mSignatures = result.signatures();
- // FIXME
- // mMetaData.auditLogError = result.error;
- mMetaData.isSigned = !mSignatures.empty();
- if (mMetaData.isSigned) {
- sigStatusToMetaData();
+ mMetaData.verificationResult = result;
+ qWarning() << "set mMetaData.verificationResult" << this;
+
+ if (mMetaData.isSigned()) {
if (!signedData.isEmpty() && mParseAfterDecryption) {
parseInternal(signedData);
}
}
}
QString SignedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString SignedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
//-----CryptMessageBlock---------------------
EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
const QString &text,
const QGpgME::Protocol *cryptoProto,
KMime::Content *node,
KMime::Content *encryptedNode,
bool parseAfterDecryption)
: MessagePart(otp, text, node)
, mParseAfterDecryption(parseAfterDecryption)
, mPassphraseError(false)
, mNoSecKey(false)
, mDecryptMessage(false)
, mCryptoProto(cryptoProto)
, mEncryptedNode(encryptedNode)
{
- mMetaData.isSigned = false;
- mMetaData.isGoodSignature = false;
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
mMetaData.isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return mMetaData.isEncrypted;
}
const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
void EncryptedMessagePart::setDecryptMessage(bool decrypt)
{
mDecryptMessage = decrypt;
}
bool EncryptedMessagePart::decryptMessage() const
{
return mDecryptMessage;
}
bool EncryptedMessagePart::isDecryptable() const
{
return mMetaData.isDecryptable;
}
bool EncryptedMessagePart::isNoSecKey() const
{
return mNoSecKey;
}
bool EncryptedMessagePart::passphraseError() const
{
return mPassphraseError;
}
bool EncryptedMessagePart::decrypt(KMime::Content &data)
{
mError = NoError;
mMetaData.errorText.clear();
// FIXME
// mMetaData.auditLogError = GpgME::Error();
mMetaData.auditLog.clear();
const QByteArray ciphertext = data.decodedContent();
QByteArray plainText;
auto job = mCryptoProto->decryptVerifyJob();
const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText);
job->deleteLater();
auto decryptResult = p.first;
auto verifyResult = p.second;
- mMetaData.isSigned = verifyResult.signatures().size() > 0;
+ mMetaData.verificationResult = verifyResult;
// Normalize CRLF's
plainText = KMime::CRLFtoLF(plainText);
auto codec = QStringDecoder(mOtp->codecNameFor(&data).constData());
const auto decoded = codec.decode(plainText);
- partMetaData()->isSigned = verifyResult.signatures().size() > 0;
-
- if (partMetaData()->isSigned) {
+ if (partMetaData()->isSigned()) {
// We simply attach a signed message part to indicate that this content is also signed
// We're forwarding mNode to not loose the encoding information
auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, mNode, nullptr));
subPart->setText(decoded);
subPart->setVerificationResult(verifyResult, plainText);
appendSubPart(subPart);
}
mDecryptRecipients.clear();
bool cannotDecrypt = false;
bool bDecryptionOk = !decryptResult.error();
for (const auto &recipient : decryptResult.recipients()) {
if (!recipient.status()) {
bDecryptionOk = true;
}
GpgME::Key key;
key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID());
if (key.isNull()) {
auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()});
if (ret.size() == 1) {
key = ret.front().parent();
}
if (key.isNull()) {
qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID();
}
}
mDecryptRecipients.emplace_back(recipient, key);
}
- if (!bDecryptionOk && partMetaData()->isSigned) {
+ if (!bDecryptionOk && partMetaData()->isSigned()) {
// Only a signed part
partMetaData()->isEncrypted = false;
bDecryptionOk = true;
mDecryptedData = plainText;
} else {
mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_BAD_PASSPHRASE;
mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
if (decryptResult.error().isCanceled()) {
setDecryptMessage(false);
}
partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
if (Kleo::DeVSCompliance::isCompliant()) {
partMetaData()->isCompliant = decryptResult.isDeVs();
partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
} else {
partMetaData()->isCompliant = true;
}
if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
partMetaData()->keyId = decryptResult.recipient(0).keyID();
}
if (bDecryptionOk) {
mDecryptedData = plainText;
} else {
mNoSecKey = true;
const auto decryRecipients = decryptResult.recipients();
for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
}
if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
mPassphraseError = true;
}
}
}
if (!bDecryptionOk) {
QString cryptPlugLibName;
mError = UnknownError;
if (mCryptoProto) {
cryptPlugLibName = mCryptoProto->name();
}
if (mNoSecKey) {
mError = NoKeyError;
}
if (mPassphraseError) {
mError = PassphraseError;
}
if (!mCryptoProto) {
partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
} else if (cannotDecrypt) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
} else if (!passphraseError()) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
+ i18n("Error: %1", partMetaData()->errorText);
}
}
return bDecryptionOk;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
mMetaData.isEncrypted = true;
mMetaData.isDecryptable = decrypt(*data);
- if (mParseAfterDecryption && !mMetaData.isSigned) {
+ if (mParseAfterDecryption && !mMetaData.isSigned()) {
parseInternal(mDecryptedData);
} else {
setText(QString::fromUtf8(mDecryptedData.constData()));
}
}
void EncryptedMessagePart::startDecryption()
{
if (mEncryptedNode) {
startDecryption(mEncryptedNode);
} else {
startDecryption(mNode);
}
}
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const
{
return mDecryptRecipients;
}
QString EncryptedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::text() const
{
if (hasSubParts()) {
auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
if (_mp) {
return _mp->text();
}
}
return MessagePart::text();
}
EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
: MessagePart(otp, QString(), node)
, mMessage(message)
{
mMetaData.isEncrypted = false;
- mMetaData.isSigned = false;
mMetaData.isEncapsulatedRfc822Message = true;
if (!mMessage) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
return;
}
parseInternal(message.data());
}
QString EncapsulatedRfc822MessagePart::text() const
{
return renderInternalText();
}
QString EncapsulatedRfc822MessagePart::from() const
{
if (auto from = mMessage->from(false)) {
return from->asUnicodeString();
}
return {};
}
QDateTime EncapsulatedRfc822MessagePart::date() const
{
if (auto date = mMessage->date(false)) {
return date->dateTime();
}
return {};
}
HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
#include "moc_messagepart.cpp"
diff --git a/src/core/messagepart.h b/src/core/messagepart.h
index bdaa58f..518aaef 100644
--- a/src/core/messagepart.h
+++ b/src/core/messagepart.h
@@ -1,376 +1,372 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "enums.h"
#include "mimetreeparser_core_export.h"
#include "partmetadata.h"
#include <gpgme++/decryptionresult.h>
#include <gpgme++/importresult.h>
#include <gpgme++/verificationresult.h>
#include <KMime/Message>
#include <QMap>
#include <QSharedPointer>
#include <QString>
namespace KMime
{
class Content;
}
namespace QGpgME
{
class Protocol;
}
namespace MimeTreeParser
{
class ObjectTreeParser;
class MultiPartAlternativeBodyPartFormatter;
class SignedMessagePart;
class EncryptedMessagePart;
class MIMETREEPARSER_CORE_EXPORT MessagePart : public QObject
{
Q_OBJECT
Q_PROPERTY(bool attachment READ isAttachment CONSTANT)
Q_PROPERTY(bool root READ isRoot CONSTANT)
Q_PROPERTY(bool isHtml READ isHtml CONSTANT)
Q_PROPERTY(QString plaintextContent READ plaintextContent CONSTANT)
Q_PROPERTY(QString htmlContent READ htmlContent CONSTANT)
public:
enum Disposition {
Inline,
Attachment,
Invalid,
};
using Ptr = QSharedPointer<MessagePart>;
using List = QList<Ptr>;
MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node = nullptr);
~MessagePart() override;
[[nodiscard]] virtual QString text() const;
void setText(const QString &text);
[[nodiscard]] virtual bool isAttachment() const;
void setIsRoot(bool root);
[[nodiscard]] bool isRoot() const;
void setParentPart(MessagePart *parentPart);
[[nodiscard]] MessagePart *parentPart() const;
[[nodiscard]] virtual QString plaintextContent() const;
[[nodiscard]] virtual QString htmlContent() const;
[[nodiscard]] virtual bool isHtml() const;
[[nodiscard]] QByteArray mimeType() const;
[[nodiscard]] QByteArray charset() const;
[[nodiscard]] QString filename() const;
[[nodiscard]] Disposition disposition() const;
[[nodiscard]] bool isText() const;
enum Error {
NoError = 0,
PassphraseError,
NoKeyError,
UnknownError,
};
[[nodiscard]] Error error() const;
[[nodiscard]] QString errorString() const;
[[nodiscard]] PartMetaData *partMetaData();
void appendSubPart(const MessagePart::Ptr &messagePart);
[[nodiscard]] const QList<MessagePart::Ptr> &subParts() const;
[[nodiscard]] bool hasSubParts() const;
KMime::Content *node() const;
virtual KMMsgSignatureState signatureState() const;
virtual KMMsgEncryptionState encryptionState() const;
[[nodiscard]] QList<SignedMessagePart *> signatures() const;
[[nodiscard]] QList<EncryptedMessagePart *> encryptions() const;
/**
* Retrieve the header @header in this part or any parent parent.
*
* Useful for MemoryHole support.
*/
[[nodiscard]] KMime::Headers::Base *header(const char *header) const;
void bindLifetime(KMime::Content *);
protected:
void parseInternal(KMime::Content *node, bool onlyOneMimePart = false);
void parseInternal(const QByteArray &data);
[[nodiscard]] QString renderInternalText() const;
QString mText;
ObjectTreeParser *mOtp;
PartMetaData mMetaData;
MessagePart *mParentPart;
KMime::Content *mNode;
QList<KMime::Content *> mNodesToDelete;
Error mError;
private:
QList<MessagePart::Ptr> mBlocks;
bool mRoot;
};
class MIMETREEPARSER_CORE_EXPORT MimeMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<MimeMessagePart> Ptr;
MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart = false);
virtual ~MimeMessagePart();
[[nodiscard]] QString text() const override;
[[nodiscard]] QString plaintextContent() const override;
[[nodiscard]] QString htmlContent() const override;
private:
friend class AlternativeMessagePart;
};
class MIMETREEPARSER_CORE_EXPORT MessagePartList : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<MessagePartList> Ptr;
MessagePartList(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node);
~MessagePartList() override = default;
[[nodiscard]] QString text() const override;
[[nodiscard]] QString plaintextContent() const override;
[[nodiscard]] QString htmlContent() const override;
};
class MIMETREEPARSER_CORE_EXPORT TextMessagePart : public MessagePartList
{
Q_OBJECT
public:
typedef QSharedPointer<TextMessagePart> Ptr;
TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node);
~TextMessagePart() override = default;
[[nodiscard]] KMMsgSignatureState signatureState() const override;
[[nodiscard]] KMMsgEncryptionState encryptionState() const override;
private:
MIMETREEPARSER_CORE_NO_EXPORT void parseContent();
KMMsgSignatureState mSignatureState;
KMMsgEncryptionState mEncryptionState;
friend class ObjectTreeParser;
};
class MIMETREEPARSER_CORE_EXPORT AttachmentMessagePart : public TextMessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<AttachmentMessagePart> Ptr;
AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node);
~AttachmentMessagePart() override = default;
[[nodiscard]] virtual bool isAttachment() const override
{
return true;
}
};
class MIMETREEPARSER_CORE_EXPORT HtmlMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<HtmlMessagePart> Ptr;
HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node);
~HtmlMessagePart() override = default;
[[nodiscard]] bool isHtml() const override
{
return true;
}
};
class MIMETREEPARSER_CORE_EXPORT AlternativeMessagePart : public MessagePart
{
Q_OBJECT
public:
enum HtmlMode {
Normal, ///< A normal plaintext message, non-multipart
Html, ///< A HTML message, non-multipart
MultipartPlain, ///< A multipart/alternative message, the plain text part is currently displayed
MultipartHtml, ///< A multipart/altervative message, the HTML part is currently displayed
MultipartIcal ///< A multipart/altervative message, the ICal part is currently displayed
};
typedef QSharedPointer<AlternativeMessagePart> Ptr;
AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node);
~AlternativeMessagePart() override;
[[nodiscard]] QString text() const override;
[[nodiscard]] bool isHtml() const override;
[[nodiscard]] QString plaintextContent() const override;
[[nodiscard]] QString htmlContent() const override;
[[nodiscard]] QString icalContent() const;
[[nodiscard]] QList<HtmlMode> availableModes();
private:
QMap<HtmlMode, MessagePart::Ptr> mChildParts;
friend class ObjectTreeParser;
friend class MultiPartAlternativeBodyPartFormatter;
};
class MIMETREEPARSER_CORE_EXPORT CertMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<CertMessagePart> Ptr;
CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto);
~CertMessagePart() override;
[[nodiscard]] QString text() const override;
private:
const QGpgME::Protocol *mCryptoProto;
GpgME::ImportResult mInportResult;
};
class MIMETREEPARSER_CORE_EXPORT EncapsulatedRfc822MessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<EncapsulatedRfc822MessagePart> Ptr;
EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message);
~EncapsulatedRfc822MessagePart() override = default;
[[nodiscard]] QString text() const override;
[[nodiscard]] QString from() const;
[[nodiscard]] QDateTime date() const;
private:
const KMime::Message::Ptr mMessage;
};
class MIMETREEPARSER_CORE_EXPORT EncryptedMessagePart : public MessagePart
{
Q_OBJECT
Q_PROPERTY(bool decryptMessage READ decryptMessage WRITE setDecryptMessage)
Q_PROPERTY(bool isEncrypted READ isEncrypted)
Q_PROPERTY(bool isNoSecKey READ isNoSecKey)
Q_PROPERTY(bool passphraseError READ passphraseError)
public:
typedef QSharedPointer<EncryptedMessagePart> Ptr;
EncryptedMessagePart(ObjectTreeParser *otp,
const QString &text,
const QGpgME::Protocol *protocol,
KMime::Content *node,
KMime::Content *encryptedNode = nullptr,
bool parseAfterDecryption = true);
~EncryptedMessagePart() override = default;
[[nodiscard]] QString text() const override;
void setDecryptMessage(bool decrypt);
[[nodiscard]] bool decryptMessage() const;
void setIsEncrypted(bool encrypted);
[[nodiscard]] bool isEncrypted() const;
[[nodiscard]] bool isDecryptable() const;
[[nodiscard]] bool isNoSecKey() const;
[[nodiscard]] bool passphraseError() const;
void startDecryption(KMime::Content *data);
void startDecryption();
QByteArray mDecryptedData;
[[nodiscard]] QString plaintextContent() const override;
[[nodiscard]] QString htmlContent() const override;
const QGpgME::Protocol *cryptoProto() const;
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> decryptRecipients() const;
private:
[[nodiscard]] MIMETREEPARSER_CORE_NO_EXPORT bool decrypt(KMime::Content &data);
bool mParseAfterDecryption{true};
protected:
bool mPassphraseError;
bool mNoSecKey;
bool mDecryptMessage;
const QGpgME::Protocol *mCryptoProto;
QByteArray mVerifiedText;
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> mDecryptRecipients;
KMime::Content *mEncryptedNode;
};
class MIMETREEPARSER_CORE_EXPORT SignedMessagePart : public MessagePart
{
Q_OBJECT
- Q_PROPERTY(bool isSigned READ isSigned CONSTANT)
+
public:
- typedef QSharedPointer<SignedMessagePart> Ptr;
+ using Ptr = QSharedPointer<SignedMessagePart>;
SignedMessagePart(ObjectTreeParser *otp,
const QGpgME::Protocol *protocol,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption = true);
~SignedMessagePart() override;
- void setIsSigned(bool isSigned);
- [[nodiscard]] bool isSigned() const;
-
void startVerification();
[[nodiscard]] QString plaintextContent() const override;
[[nodiscard]] QString htmlContent() const override;
const QGpgME::Protocol *cryptoProto() const;
private:
- MIMETREEPARSER_CORE_NO_EXPORT void sigStatusToMetaData();
MIMETREEPARSER_CORE_NO_EXPORT void setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData);
bool mParseAfterDecryption{true};
protected:
const QGpgME::Protocol *mCryptoProto;
KMime::Content *mSignedData;
std::vector<GpgME::Signature> mSignatures;
friend EncryptedMessagePart;
};
class MIMETREEPARSER_CORE_EXPORT HeadersPart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<HeadersPart> Ptr;
HeadersPart(ObjectTreeParser *otp, KMime::Content *node);
~HeadersPart() override = default;
};
}
diff --git a/src/core/partmetadata.h b/src/core/partmetadata.h
index dedce9b..c07b867 100644
--- a/src/core/partmetadata.h
+++ b/src/core/partmetadata.h
@@ -1,57 +1,58 @@
// SPDX-FileCopyrightText: 2002-2003 Karl-Heinz Zimmer <khz@kde.org>
// SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDateTime>
#include <QStringList>
#include <gpgme++/context.h>
#include <gpgme++/verificationresult.h>
namespace MimeTreeParser
{
class PartMetaData
{
public:
PartMetaData()
- : isSigned(false)
- , isGoodSignature(false)
- , isEncrypted(false)
+ : isEncrypted(false)
, isDecryptable(false)
, inProgress(false)
, technicalProblem(false)
, isEncapsulatedRfc822Message(false)
, isCompliant(false)
, keyRevoked(false)
{
}
- GpgME::Signature::Summary sigSummary = GpgME::Signature::None;
+ inline bool isSigned() const
+ {
+ return verificationResult.signatures().size() > 0;
+ }
+
+ GpgME::VerificationResult verificationResult;
+
QString signClass;
QString signer;
QStringList signerMailAddresses;
QByteArray keyId;
QString keyTrust;
- GpgME::Signature::Summary signatureSummary;
QString status; // to be used for unknown plug-ins
int status_code = 0; // = GPGME_SIG_STAT_NONE; to be used for i18n of OpenPGP and S/MIME CryptPlugs
QString errorText;
QDateTime creationTime;
QString decryptionError;
QString auditLog;
QString compliance; // textual representation of compliance status; empty if compliance isn't enforced
GpgME::Error auditLogError;
- bool isSigned : 1;
- bool isGoodSignature : 1;
bool isEncrypted : 1;
bool isDecryptable : 1;
bool inProgress : 1;
bool technicalProblem : 1;
bool isEncapsulatedRfc822Message : 1;
bool isCompliant : 1; // corresponds to the isDeVS flag of signature or decryption result
bool keyRevoked : 1;
};
}
diff --git a/src/core/partmodel.cpp b/src/core/partmodel.cpp
index 53eec5d..7484330 100644
--- a/src/core/partmodel.cpp
+++ b/src/core/partmodel.cpp
@@ -1,662 +1,892 @@
// 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 "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>
+
+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;
+}
+
+static QString formatValidSignatureWithTrustLevel(const GpgME::UserID &id)
+{
+ if (id.isNull()) {
+ return QString();
+ }
+ switch (id.validity()) {
+ case GpgME::UserID::Marginal:
+ return i18n("The signature is valid but the trust in the certificate's validity is only marginal.");
+ case GpgME::UserID::Full:
+ return i18n("The signature is valid and the certificate's validity is fully trusted.");
+ case GpgME::UserID::Ultimate:
+ return i18n("The signature is valid and the certificate's validity is ultimately trusted.");
+ case GpgME::UserID::Never:
+ return i18n("The signature is valid but the certificate's validity is <em>not trusted</em>.");
+ case GpgME::UserID::Unknown:
+ return i18n("The signature is valid but the certificate's validity is unknown.");
+ case GpgME::UserID::Undefined:
+ default:
+ return i18n("The signature is valid but the certificate's validity is undefined.");
+ }
+}
+
+static QString renderKeyLink(const QString &fpr, const QString &text)
+{
+ return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(fpr, text);
+}
+
+static QString renderKey(const GpgME::Key &key)
+{
+ if (key.isNull()) {
+ return i18n("Unknown certificate");
+ }
+
+ if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) {
+ const QString text = QStringLiteral("%1 (%2)")
+ .arg(Kleo::Formatting::prettyNameAndEMail(key).toHtmlEscaped())
+ .arg(Kleo::Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData()));
+ return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), text);
+ }
+
+ return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), Kleo::Formatting::prettyID(key.primaryFingerprint()));
+}
+
+static QString formatDate(const QDateTime &dt)
+{
+ return QLocale().toString(dt);
+}
+
+static QString formatSigningInformation(const GpgME::Signature &sig, const GpgME::Key &key)
+{
+ if (sig.isNull()) {
+ return QString();
+ }
+ const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime();
+ QString text;
+
+ if (key.isNull()) {
+ return text += i18n("With unavailable certificate:") + QStringLiteral("<br>ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper());
+ }
+
+ text += i18n("With certificate: %1", renderKey(key));
+
+ if (Kleo::DeVSCompliance::isCompliant()) {
+ text += (QStringLiteral("<br/>")
+ + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
+ "The signature is %1",
+ Kleo::DeVSCompliance::name(true))
+ : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
+ "The signature <b>is not</b> %1.",
+ Kleo::DeVSCompliance::name(true))));
+ }
+
+ return text;
+}
+
+static QString signatureSummaryToString(GpgME::Signature::Summary summary)
+{
+ if (summary & GpgME::Signature::None) {
+ return i18n("Error: Signature not verified");
+ } else if (summary & GpgME::Signature::Valid || summary & GpgME::Signature::Green) {
+ return i18n("Good signature");
+ } else if (summary & GpgME::Signature::KeyRevoked) {
+ return i18n("Signing certificate was revoked");
+ } else if (summary & GpgME::Signature::KeyExpired) {
+ return i18n("Signing certificate is expired");
+ } else if (summary & GpgME::Signature::KeyMissing) {
+ return i18n("Certificate is not available");
+ } else if (summary & GpgME::Signature::SigExpired) {
+ return i18n("Signature expired");
+ } else if (summary & GpgME::Signature::CrlMissing) {
+ return i18n("CRL missing");
+ } else if (summary & GpgME::Signature::CrlTooOld) {
+ return i18n("CRL too old");
+ } else if (summary & GpgME::Signature::BadPolicy) {
+ return i18n("Bad policy");
+ } else if (summary & GpgME::Signature::SysError) {
+ return i18n("System error"); // ### retrieve system error details?
+ } else if (summary & GpgME::Signature::Red) {
+ return i18n("Bad signature");
+ }
+ return QString();
+}
+
+static bool addrspec_equal(const KMime::Types::AddrSpec &lhs, const KMime::Types::AddrSpec &rhs, Qt::CaseSensitivity cs)
+{
+ return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0;
+}
+
+static std::string stripAngleBrackets(const std::string &str)
+{
+ if (str.empty()) {
+ return str;
+ }
+ if (str[0] == '<' && str[str.size() - 1] == '>') {
+ return str.substr(1, str.size() - 2);
+ }
+ return str;
+}
+
+static std::string email(const GpgME::UserID &uid)
+{
+ if (uid.parent().protocol() == GpgME::OpenPGP) {
+ if (const char *const email = uid.email()) {
+ return stripAngleBrackets(email);
+ } else {
+ return std::string();
+ }
+ }
+
+ Q_ASSERT(uid.parent().protocol() == GpgME::CMS);
+
+ if (const char *const id = uid.id())
+ if (*id == '<') {
+ return stripAngleBrackets(id);
+ } else {
+ return Kleo::DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
+ }
+ else {
+ return std::string();
+ }
+}
+
+static bool mailbox_equal(const KMime::Types::Mailbox &lhs, const KMime::Types::Mailbox &rhs, Qt::CaseSensitivity cs)
+{
+ return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs);
+}
+
+static KMime::Types::Mailbox mailbox(const GpgME::UserID &uid)
+{
+ const std::string e = email(uid);
+ KMime::Types::Mailbox mbox;
+ if (!e.empty()) {
+ mbox.setAddress(e.c_str());
+ }
+ return mbox;
+}
+
+static GpgME::UserID findUserIDByMailbox(const GpgME::Key &key, const KMime::Types::Mailbox &mbox)
+{
+ const auto userIDs{key.userIDs()};
+ for (const GpgME::UserID &id : userIDs) {
+ if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) {
+ return id;
+ }
+ }
+ return {};
+}
+
+static QString formatSignature(const GpgME::Signature &sig, const GpgME::Key &key, const KMime::Types::Mailbox &sender)
+{
+ if (sig.isNull()) {
+ return QString();
+ }
+
+ const QString text = formatSigningInformation(sig, key) + QLatin1StringView("<br/>");
+
+ // Green
+ if (sig.summary() & GpgME::Signature::Valid) {
+ const GpgME::UserID id = findUserIDByMailbox(key, sender);
+ return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0));
+ }
+
+ // Red
+ if ((sig.summary() & GpgME::Signature::Red)) {
+ const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
+ if (sig.summary() & GpgME::Signature::SysError) {
+ return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
+ }
+ return ret;
+ }
+
+ // Key missing
+ if ((sig.summary() & GpgME::Signature::KeyMissing)) {
+ return text + i18n("You can search the certificate on a keyserver or import it from a file.");
+ }
+
+ // Yellow
+ if ((sig.validity() & GpgME::Signature::Validity::Undefined) //
+ || (sig.validity() & GpgME::Signature::Validity::Unknown) //
+ || (sig.summary() == GpgME::Signature::Summary::None)) {
+ return text
+ + (key.protocol() == GpgME::OpenPGP
+ ? i18n("The used key is not certified by you or any trusted person.")
+ : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
+ }
+
+ // Catch all fall through
+ const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
+ if (sig.summary() & GpgME::Signature::SysError) {
+ return ret + QStringLiteral(" (%1)").arg(Kleo::Formatting::errorAsString(sig.status()));
+ }
+ return ret;
+}
// 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;
};
-
-SignatureInfo signatureInfo(MimeTreeParser::MessagePart *messagePart)
+template<typename T>
+const T *findHeader(KMime::Content *content)
{
- SignatureInfo signatureInfo;
- const auto signatures = messagePart->signatures();
- if (signatures.size() > 1) {
- qWarning() << "Can't deal with more than one signature";
- }
- for (const auto &signaturePart : signatures) {
- signatureInfo.keyId = signaturePart->partMetaData()->keyId;
- signatureInfo.cryptoProto = signaturePart->cryptoProto();
- signatureInfo.keyMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyMissing;
- signatureInfo.keyExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyExpired;
- signatureInfo.keyRevoked = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyRevoked;
- signatureInfo.sigExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::SigExpired;
- signatureInfo.crlMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlMissing;
- signatureInfo.crlTooOld = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlTooOld;
- signatureInfo.signer = signaturePart->partMetaData()->signer;
- signatureInfo.isCompliant = signaturePart->partMetaData()->isCompliant;
- signatureInfo.signerMailAddresses = signaturePart->partMetaData()->signerMailAddresses;
- signatureInfo.signatureIsGood = signaturePart->partMetaData()->isGoodSignature;
- signatureInfo.keyTrust = signaturePart->partMetaData()->keyTrust;
- signatureInfo.signatureSummary = signaturePart->partMetaData()->signatureSummary;
+ auto header = content->header<T>();
+ if (header || !content->parent()) {
+ return header;
}
- return signatureInfo;
+ return findHeader<T>(content->parent());
}
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: {
// Color displayed for the signature info box
- const auto signature = messagePart->signatureState();
- const bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned;
- if (!messageIsSigned) {
+ auto signature = signatureFromMessagePart(messagePart);
+ if (!signature) {
return SecurityLevel::Unknow;
}
- const auto sigInfo = signatureInfo(messagePart);
- if (sigInfo.signatureSummary & GpgME::Signature::Summary::Red) {
+ const auto summary = signature->summary();
+
+ if (summary & GpgME::Signature::Summary::Red) {
return SecurityLevel::Bad;
}
- if (sigInfo.signatureSummary & GpgME::Signature::Summary::Valid) {
+ if (summary & GpgME::Signature::Summary::Valid) {
return SecurityLevel::Good;
}
return SecurityLevel::NotSoGood;
}
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: {
- const auto signatureDetails = signatureInfo(messagePart);
- if (signatureDetails.signatureSummary & GpgME::Signature::Valid) {
+ auto signature = signatureFromMessagePart(messagePart);
+ if (!signature) {
+ return QString{};
+ }
+
+ const auto summary = signature->summary();
+ if (summary & GpgME::Signature::Valid) {
return QStringLiteral("mail-signed");
- } else if (signatureDetails.signatureSummary & GpgME::Signature::Red) {
+ } else if (summary & GpgME::Signature::Red) {
return QStringLiteral("data-error");
} else {
return QStringLiteral("data-warning");
}
}
case SignatureDetailsRole: {
- const auto signatureDetails = signatureInfo(messagePart);
- QString href;
- if (signatureDetails.cryptoProto) {
- href = QStringLiteral("messageviewer:showCertificate#%1 ### %2 ### %3")
- .arg(signatureDetails.cryptoProto->displayName(), signatureDetails.cryptoProto->name(), QString::fromLatin1(signatureDetails.keyId));
- }
-
- QString details;
- if (signatureDetails.keyMissing) {
- if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) {
- details += i18ndc("mimetreeparser",
- "@info",
- "This message has been signed VS-NfD compliant using the certificate <a href=\"%1\">%2</a>.",
- href,
- Kleo::Formatting::prettyID(signatureDetails.keyId.toStdString().data()))
- + QLatin1Char('\n');
- } else {
- details += i18ndc("mimetreeparser",
- "@info",
- "This message has been signed using the certificate <a href=\"%1\">%2</a>.",
- href,
- Kleo::Formatting::prettyID(signatureDetails.keyId.toStdString().data()))
- + QLatin1Char('\n');
+ 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();
}
- details += i18ndc("mimetreeparser", "@info", "The certificate details are not available.");
- } else {
- QString signerDisplayName = signatureDetails.signer.toHtmlEscaped();
- if (signatureDetails.cryptoProto == QGpgME::smime()) {
- Kleo::DN dn(signatureDetails.signer);
- signerDisplayName = MimeTreeParser::dnToDisplayName(dn).toHtmlEscaped();
- }
- if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) {
- details +=
- i18ndc("mimetreeparser", "@info", "This message has been signed VS-NfD compliant by <a href=\"%1\">%2</a>.", href, signerDisplayName);
- } else {
- details += i18ndc("mimetreeparser", "@info", "This message has been signed by <a href=\"%1\">%2</a>.", href, signerDisplayName);
- }
- if (signatureDetails.keyRevoked) {
- details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@info", "The <a href=\"%1\">certificate</a> was revoked.", href);
- }
- if (signatureDetails.keyExpired) {
- details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@info", "The <a href=\"%1\">certificate</a> is expired.", href);
+ }
+ 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 formatSignature(*signature, key, mailboxes.front());
+ }
}
}
- if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired) {
- details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@info", "The signature is invalid.");
- } else {
- details += QLatin1Char(' ') + signatureDetails.keyTrust;
+ // use first non empty userId as fallback
+ KMime::Types::Mailbox mailBox;
+ for (int i = 0, count = key.userIDs().size(); i < count; i++) {
+ const auto address = QString::fromUtf8(key.userID(1).email());
+ if (!address.isEmpty()) {
+ mailBox.fromUnicodeString(address);
+ break;
+ }
}
- return details;
+ return formatSignature(*signature, key, mailBox);
}
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"

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 3, 5:36 AM (10 h, 14 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7d/50/7387da76fd794a4452628842fca6

Event Timeline