Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F18825898
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
63 KB
Subscribers
None
View Options
diff --git a/src/core/autotests/mimetreeparsertest.cpp b/src/core/autotests/mimetreeparsertest.cpp
index bc5f008..bdcf5e1 100644
--- a/src/core/autotests/mimetreeparsertest.cpp
+++ b/src/core/autotests/mimetreeparsertest.cpp
@@ -1,776 +1,777 @@
// 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(QLatin1String(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(QLatin1String("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(QLatin1String("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(QLatin1String("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(QLatin1String("smime-encrypted.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptParts();
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(), QVector<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(), QVector<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(QLatin1String("openpgp-inline-charset-encrypted.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("openpgp-inline-encrypted+nonenc.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("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(QLatin1String("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(QLatin1String("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(QLatin1String("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(QLatin1String("cid:")));
}
void testCidLinkInForwardedInline()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("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(QLatin1String("cid:")));
}
void testOpenPGPInlineError()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("inlinepgpgencrypted-error.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("encapsulated-with-attachment.mbox")));
otp.decryptParts();
auto partList = otp.collectContentParts();
QCOMPARE(partList.size(), 2);
auto part = partList[1].dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>();
QVERIFY(bool(part));
QCOMPARE(part->from(), QLatin1String("Thomas McGuire <dontspamme@gmx.net>"));
QCOMPARE(part->date().toString(), QLatin1String("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(QLatin1String("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(QLatin1String("openpgp-inline-signed.mbox")));
otp.decryptParts();
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()->keyIsTrusted, true);
QCOMPARE(signaturePart->partMetaData()->keyMissing, false);
QCOMPARE(signaturePart->partMetaData()->keyExpired, false);
QCOMPARE(signaturePart->partMetaData()->keyRevoked, false);
QCOMPARE(signaturePart->partMetaData()->sigExpired, false);
QCOMPARE(signaturePart->partMetaData()->crlMissing, false);
QCOMPARE(signaturePart->partMetaData()->crlTooOld, false);
QCOMPARE(signaturePart->partMetaData()->keyId, QByteArray{"8D9860C58F246DE6"});
QCOMPARE(signaturePart->partMetaData()->signer, QLatin1String{"unittest key (no password) <test@kolab.org>"});
QCOMPARE(signaturePart->partMetaData()->signerMailAddresses, QStringList{{QStringLiteral("test@kolab.org")}});
}
void testEncryptedAndSigned()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted+signed.mbox")));
otp.decryptParts();
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();
QCOMPARE(signaturePart->partMetaData()->keyId, QByteArray{"8D9860C58F246DE6"});
QCOMPARE(signaturePart->partMetaData()->isGoodSignature, true);
}
void testOpenpgpMultipartEmbedded()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-multipart-embedded.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("openpgp-multipart-embedded-signed.mbox")));
otp.decryptParts();
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(signaturePart->partMetaData()->isGoodSignature, false);
QCOMPARE(signaturePart->partMetaData()->keyMissing, true);
}
void testAppleHtmlWithAttachments()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("applehtmlwithattachments.mbox")));
otp.decryptParts();
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(QLatin1String("applehtmlwithattachmentsmixed.mbox")));
otp.decryptParts();
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(QLatin1String("invitation.mbox")));
otp.decryptParts();
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(QLatin1String("gmail-invitation.mbox")));
otp.decryptParts();
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(QLatin1String("openpgp-encrypted-memoryhole.mbox")));
otp.decryptParts();
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(QLatin1String("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(QLatin1String("openpgp-encrypted-memoryhole2.mbox")));
otp.decryptParts();
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(QLatin1String("text+html-maillinglist.mbox")));
otp.decryptParts();
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(QLatin1String("crlf-encrypted-with-signature.mbox")));
otp.decryptParts();
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(QLatin1String("crlf-encrypted-with-signature-multipart.mbox")));
otp.decryptParts();
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(QLatin1String("outlook.mbox")));
otp.decryptParts();
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(QLatin1String("\r\n")));
}
void testOpenPGPEncryptedSignedThunderbird()
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-signed-thunderbird.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("signed-forward-openpgp-signed-encrypted.mbox")));
otp.print();
otp.decryptParts();
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("ISO-8859-1").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(QLatin1String("smime-opaque-sign.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("smime-encrypted.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("smime-signed-apple.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("smime-encrypted-octet-stream.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("smime-opaque-signed-encrypted-attachment.mbox")));
otp.print();
otp.decryptParts();
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(QLatin1String("smime-opaque-enc+sign.mbox")));
otp.print();
otp.decryptParts();
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 62f5b5b..e9d11f5 100644
--- a/src/core/messagepart.cpp
+++ b/src/core/messagepart.cpp
@@ -1,964 +1,962 @@
// 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 <QTextCodec>
using namespace MimeTreeParser;
using namespace Crypto;
//------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 "us-ascii";
}
if (auto ct = contentType(mNode)) {
return ct->charset();
}
// Per rfc2045 us-ascii is the default
return "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("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 QVector<MessagePart::Ptr> &MessagePart::subParts() const
{
return mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !mBlocks.isEmpty();
}
QVector<SignedMessagePart *> MessagePart::signatures() const
{
QVector<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;
}
QVector<EncryptedMessagePart *> MessagePart::encryptions() const
{
QVector<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()) {
const auto aCodec = mOtp->codecFor(mNode);
const auto cryptProto = 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->toUnicode(KMime::CRLFtoLF(block.text())))));
} else if (block.type() == PgpMessageBlock) {
KMime::Content *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) {
KMime::Content *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()) {
mp->setText(aCodec->toUnicode(KMime::CRLFtoLF(block.text())));
}
if (messagePart->isEncrypted) {
mEncryptionState = KMMsgPartiallyEncrypted;
}
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(mOtp->codecFor(mNode)->toUnicode(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)
{
- qWarning() << "AlternativeMessagePart";
if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) {
mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
- return;
}
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, const CryptoProtocol cryptoProto)
: MessagePart(otp, QString(), node)
, mProtocol(cryptoProto)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
return;
}
}
CertMessagePart::~CertMessagePart()
{
}
void CertMessagePart::import()
{
importKey(mProtocol, mNode->decodedContent());
}
QString CertMessagePart::text() const
{
return QString();
}
//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
const CryptoProtocol cryptoProto,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption)
: MessagePart(otp, {}, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mSignedData(signedData)
{
mMetaData.isSigned = true;
mMetaData.isGoodSignature = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
}
SignedMessagePart::~SignedMessagePart()
{
}
void SignedMessagePart::setIsSigned(bool isSigned)
{
mMetaData.isSigned = isSigned;
}
bool SignedMessagePart::isSigned() const
{
return mMetaData.isSigned;
}
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);
}
static void sigStatusToMetaData(PartMetaData &mMetaData, const Signature &signature)
{
mMetaData.isGoodSignature = !signature.status;
if (!mMetaData.isGoodSignature) {
qWarning() << "The signature is bad." << signature.status;
}
// save extended signature status flags
mMetaData.keyMissing = signature.result == Crypto::Signature::KeyNotFound;
mMetaData.keyExpired = signature.result == Crypto::Signature::Expired;
Key key;
if (mMetaData.isGoodSignature) {
// Search for the key by its fingerprint so that we can check for trust etc.
const auto keys = findKeys({QString::fromUtf8(signature.fingerprint)});
if (keys.size() > 1) {
// Should not happen
qCDebug(MIMETREEPARSER_CORE_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint;
}
if (keys.empty()) {
// Should not happen at this point
qCWarning(MIMETREEPARSER_CORE_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint;
} else {
key = keys[0];
}
}
mMetaData.keyId = key.keyId;
if (mMetaData.keyId.isEmpty()) {
mMetaData.keyId = signature.fingerprint;
}
mMetaData.keyIsTrusted = signature.isTrusted;
if (!key.userIds.empty()) {
mMetaData.signer = prettifyDN(key.userIds[0].id.data());
}
for (const auto &userId : key.userIds) {
QString email = QString::fromUtf8(userId.email);
// Work around cryptplug bug where email addresses are specified as angle-addr ( <person@example.org> ),
// not addr-spec ( person@example.org ):
if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) {
email = email.mid(1, email.length() - 2);
}
if (!email.isEmpty()) {
mMetaData.signerMailAddresses.append(email);
}
}
mMetaData.creationTime = signature.creationTime;
if (mMetaData.signer.isEmpty()) {
if (!key.userIds.empty()) {
mMetaData.signer = prettifyDN(key.userIds[0].name.data());
}
if (!mMetaData.signerMailAddresses.empty()) {
if (mMetaData.signer.isEmpty()) {
mMetaData.signer = mMetaData.signerMailAddresses.front();
} else {
mMetaData.signer += QLatin1String(" <") + mMetaData.signerMailAddresses.front() + QLatin1Char('>');
}
}
}
}
void SignedMessagePart::startVerification()
{
if (!mSignedData) {
return;
}
mMetaData.isSigned = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
const auto codec = mOtp->codecFor(mSignedData);
// 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());
setVerificationResult(Crypto::verifyDetachedSignature(mProtocol, signature, signedData), signedData);
setText(codec->toUnicode(KMime::CRLFtoLF(signedData)));
} else {
QByteArray outdata;
setVerificationResult(Crypto::verifyOpaqueSignature(mProtocol, mSignedData->decodedContent(), outdata), outdata);
setText(codec->toUnicode(KMime::CRLFtoLF(outdata)));
}
if (!mMetaData.isSigned) {
mMetaData.creationTime = QDateTime();
}
}
void SignedMessagePart::setVerificationResult(const VerificationResult &result, const QByteArray &signedData)
{
const auto signatures = result.signatures;
// FIXME
// mMetaData.auditLogError = result.error;
if (!signatures.empty()) {
mMetaData.isSigned = true;
sigStatusToMetaData(mMetaData, signatures.front());
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 CryptoProtocol cryptoProto,
KMime::Content *node,
KMime::Content *encryptedNode,
bool parseAfterDecryption)
: MessagePart(otp, text, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mEncryptedNode(encryptedNode)
{
mMetaData.isSigned = false;
mMetaData.isGoodSignature = false;
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
mMetaData.isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return mMetaData.isEncrypted;
}
bool EncryptedMessagePart::isDecryptable() const
{
return mMetaData.isDecryptable;
}
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;
DecryptionResult decryptResult;
VerificationResult verifyResult;
std::tie(decryptResult, verifyResult) = Crypto::decryptAndVerify(mProtocol, ciphertext, plainText);
mMetaData.isSigned = verifyResult.signatures.size() > 0;
// Normalize CRLF's
plainText = KMime::CRLFtoLF(plainText);
const auto codec = mOtp->codecFor(&data);
const auto decoded = codec->toUnicode(plainText);
if (verifyResult.signatures.size() > 0) {
// 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, mProtocol, mNode, nullptr));
subPart->setText(decoded);
subPart->setVerificationResult(verifyResult, plainText);
appendSubPart(subPart);
}
if (decryptResult.error && mMetaData.isSigned) {
// Only a signed part
mMetaData.isEncrypted = false;
mDecryptedData = plainText;
return true;
}
if (mMetaData.isEncrypted) {
mMetaData.keyId = [&] {
for (const auto &recipient : std::as_const(decryptResult.recipients)) {
if (recipient.secretKeyAvailable) {
return recipient.keyId;
}
}
return QByteArray{};
}();
}
if (!decryptResult.error) {
mDecryptedData = plainText;
setText(decoded);
} else {
mMetaData.isEncrypted = decryptResult.result != Crypto::DecryptionResult::NotEncrypted;
qWarning() << "Failed to decrypt : " << decryptResult.error;
if (decryptResult.result == Crypto::DecryptionResult::NoSecretKeyError) {
mError = NoKeyError;
mMetaData.errorText = i18n("Could not decrypt the data: no key found for recipients.");
} else if (decryptResult.result == Crypto::DecryptionResult::PassphraseError) {
mError = PassphraseError;
} else {
mError = UnknownError;
mMetaData.errorText = i18n("Could not decrypt the data.");
}
setText(QString::fromUtf8(mDecryptedData.constData()));
return false;
}
return true;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
mMetaData.isEncrypted = true;
mMetaData.isDecryptable = decrypt(*data);
if (mParseAfterDecryption && !mMetaData.isSigned) {
parseInternal(mDecryptedData);
}
}
void EncryptedMessagePart::startDecryption()
{
if (mEncryptedNode) {
startDecryption(mEncryptedNode);
} else {
startDecryption(mNode);
}
}
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();
} else {
return MessagePart::text();
}
} else {
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)
{
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 3:33 PM (1 d, 29 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
34/02/fdd12fad35e4496d8458cc85d23c
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment