diff --git a/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp b/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp index ad5b6249..c2d321b5 100644 --- a/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp +++ b/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp @@ -1,186 +1,195 @@ /* Copyright 2018 Sandro Knauß This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "grantleeheaderformattertest.h" #include +#include #include "../grantleeheaderformatter.h" #include #include #include #include #include #include using namespace MessageViewer; QTEST_MAIN(GrantleeHeaderFormatterTest) void testHeaderFile(const QString &data, const QString &absolutePath, const QString &name) { QString header = QStringLiteral("\n" "\n" "\n"); header += data + QStringLiteral("\n"); header += QStringLiteral("\n\n\n"); header.replace(QStringLiteral("file://") + absolutePath, QStringLiteral("file://PATHTOSTYLE")); header.replace(QRegExp(QStringLiteral("[\t ]+")), QStringLiteral(" ")); header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*")), QStringLiteral("\n")); header.replace(QRegExp(QStringLiteral("([\n\t ])\\1+")), QStringLiteral("\\1")); header.replace(QRegExp(QStringLiteral(">\n+[\t ]*")), QStringLiteral(">")); header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*<")), QStringLiteral("<")); header.replace(QLatin1String(" "), QLatin1String("NBSP_ENTITY_PLACEHOLDER")); // xmlling chokes on   QString outName = name + QStringLiteral(".out.html"); QString fName = name + QStringLiteral(".html"); QVERIFY(QFile(QStringLiteral(HEADER_DATA_DIR "/") + fName).exists()); { QFile f(outName); f.open(QIODevice::WriteOnly); f.write(header.toUtf8()); f.close(); } // TODO add proper cmake check for xmllint and diff { const QStringList args = QStringList() << QStringLiteral("--format") << QStringLiteral("--encode") << QStringLiteral("UTF8") << QStringLiteral("--output") << fName << outName; QCOMPARE(QProcess::execute(QStringLiteral("xmllint"), args), 0); } { // compare to reference file const QStringList args = QStringList() << QStringLiteral("-u") << fName << QStringLiteral(HEADER_DATA_DIR "/") + fName; QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(QStringLiteral("diff"), args); QVERIFY(proc.waitForFinished()); QCOMPARE(proc.exitCode(), 0); } } KMime::Message::Ptr readAndParseMail(const QString &mailFile) { QFile file(QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + mailFile); bool openFile = file.open(QIODevice::ReadOnly); Q_ASSERT(openFile); const QByteArray data = KMime::CRLFtoLF(file.readAll()); Q_ASSERT(!data.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(data); msg->parse(); return msg; } void GrantleeHeaderFormatterTest::testInvalid() { auto style = GrantleeHeaderStyle(); auto formatter = GrantleeHeaderFormatter(); + MimeTreeParser::NodeHelper nodeHelper; + style.setNodeHelper(&nodeHelper); auto aMsg = readAndParseMail(QStringLiteral("allheaders.mbox")); QString filename = QStringLiteral("invalid"); QString absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + filename; QString data = formatter.toHtml(QStringList(), absolutePath, filename, &style, aMsg.data(), false); QCOMPARE(data, QStringLiteral("Template not found, invalid")); } void GrantleeHeaderFormatterTest::testPrint() { QString tmplName = QStringLiteral("printtest.tmpl"); auto style = GrantleeHeaderStyle(); auto formatter = GrantleeHeaderFormatter(); + MimeTreeParser::NodeHelper nodeHelper; + style.setNodeHelper(&nodeHelper); KMime::Message::Ptr aMsg(new KMime::Message); const QString &absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + tmplName; { const QString &data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), false); testHeaderFile(QStringLiteral("
")+data, absolutePath, QStringLiteral("printtest.off")); } { const QString &data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), true); testHeaderFile(QStringLiteral("
")+data, absolutePath, QStringLiteral("printtest.on")); } } void GrantleeHeaderFormatterTest::testFancyDate() { QString tmplName = QStringLiteral("fancydate.tmpl"); auto style = GrantleeHeaderStyle(); auto formatter = GrantleeHeaderFormatter(); + MimeTreeParser::NodeHelper nodeHelper; + style.setNodeHelper(&nodeHelper); KMime::Message::Ptr msg(new KMime::Message); { auto datetime(QDateTime::currentDateTime()); datetime.setTime(QTime(12,34,56)); datetime = datetime.addDays(-1); const QByteArray data = "From: from@example.com\nDate: " + datetime.toString(Qt::RFC2822Date).toLocal8Bit() + "\nTo: to@example.com\n\ncontent"; msg->setContent(KMime::CRLFtoLF(data)); msg->parse(); } const QString &absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + tmplName; const QString &data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, msg.data(), false); testHeaderFile(QStringLiteral("
")+data, absolutePath, QStringLiteral("fancydate")); } void GrantleeHeaderFormatterTest::testBlock_data() { QTest::addColumn("tmplName"); QDir dir(QStringLiteral(HEADER_DATA_DIR)); const auto l = dir.entryList(QStringList(QStringLiteral("*.tmpl")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html"))) { continue; } QTest::newRow(file.toLatin1().constData()) << file; } } void GrantleeHeaderFormatterTest::testBlock() { QFETCH(QString, tmplName); auto style = GrantleeHeaderStyle(); auto formatter = GrantleeHeaderFormatter(); + MimeTreeParser::NodeHelper nodeHelper; + style.setNodeHelper(&nodeHelper); auto aMsg = readAndParseMail(QStringLiteral("headertest.mbox")); QString absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + tmplName; QString data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), false); testHeaderFile(QStringLiteral("
")+data, absolutePath, tmplName); } diff --git a/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp b/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp index 5166305c..aea3f6c4 100644 --- a/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp +++ b/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp @@ -1,215 +1,218 @@ /* Copyright 2018 Sandro Knauß This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "grantleeheaderstyletest.h" #include +#include #include #include #include #include #include #include #include using namespace MessageViewer; QTEST_MAIN(GrantleeHeaderStyleTest) void testHeaderFile(const HeaderStyle &style, KMime::Message *msg, const QString &name) { QString header = QStringLiteral("\n" "\n" "\n"); header += style.format(msg) + QStringLiteral("
"); header += QStringLiteral("\n\n\n"); header.replace(QStringLiteral("file://") + style.theme().absolutePath(), QStringLiteral("file://PATHTOSTYLE")); header.replace(QRegExp(QStringLiteral("[\t ]+")), QStringLiteral(" ")); header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*")), QStringLiteral("\n")); header.replace(QRegExp(QStringLiteral("([\n\t ])\\1+")), QStringLiteral("\\1")); header.replace(QRegExp(QStringLiteral(">\n+[\t ]*")), QStringLiteral(">")); header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*<")), QStringLiteral("<")); header.replace(QLatin1String(" "), QLatin1String("NBSP_ENTITY_PLACEHOLDER")); // xmlling chokes on   QString outName = name + QStringLiteral(".out.html"); QString fName = name + QStringLiteral(".html"); QVERIFY(QFile(QStringLiteral(HEADER_DATA_DIR "/") + fName).exists()); { QFile f(outName); f.open(QIODevice::WriteOnly); f.write(header.toUtf8()); f.close(); } // TODO add proper cmake check for xmllint and diff { const QStringList args = QStringList() << QStringLiteral("--format") << QStringLiteral("--encode") << QStringLiteral("UTF8") << QStringLiteral("--output") << fName << outName; QCOMPARE(QProcess::execute(QStringLiteral("xmllint"), args), 0); } { // compare to reference file const QStringList args = QStringList() << QStringLiteral("-u") << fName << QStringLiteral(HEADER_DATA_DIR "/") + fName; QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(QStringLiteral("diff"), args); QVERIFY(proc.waitForFinished()); QCOMPARE(proc.exitCode(), 0); } } KMime::Message::Ptr readAndParseMail(const QString &mailFile) { QFile file(QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + mailFile); bool openFile = file.open(QIODevice::ReadOnly); Q_ASSERT(openFile); const QByteArray data = KMime::CRLFtoLF(file.readAll()); Q_ASSERT(!data.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(data); msg->parse(); return msg; } void GrantleeHeaderStyleTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); qputenv("LC_ALL", "C"); expectedDataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); expectedDataLocation += QDir::separator() + QStringLiteral("messageviewer/defaultthemes"); QDir targetDir(expectedDataLocation); QDir sourceDir(QStringLiteral(GRANTLEETHEME_DATA_DIR)); const auto themeDirs = sourceDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); if (targetDir.exists()) { QVERIFY(targetDir.removeRecursively()); // Start with a fresh copy } for (const auto &themeDir : themeDirs) { const QString &dirName = targetDir.filePath(themeDir.fileName()); QVERIFY(targetDir.mkpath(themeDir.fileName())); const auto files = QDir(themeDir.absoluteFilePath()).entryInfoList(QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const auto &file : files) { const QString &newPath = dirName + QDir::separator() + file.fileName(); QVERIFY(QFile(file.absoluteFilePath()).copy(newPath)); } } const auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); if (!configDir.exists()) { QVERIFY(configDir.mkpath(QStringLiteral("."))); } QFile antispamFile(QStringLiteral(HEADER_DATA_DIR "/kmail.antispamrc")); const QString &newPath = configDir.filePath(QStringLiteral("kmail.antispamrc")); antispamFile.copy(newPath); } void GrantleeHeaderStyleTest::cleanupTestCase() { QDir targetDir(expectedDataLocation); if (targetDir.exists()) { QVERIFY(targetDir.removeRecursively()); // Start with a fresh copy } } const GrantleeTheme::Theme defaultTheme(const QString &name = QStringLiteral("5.2")) { const QStringList defaultThemePath = QStandardPaths::locateAll( QStandardPaths::GenericDataLocation, QStringLiteral("messageviewer/defaultthemes/"), QStandardPaths::LocateDirectory); return GrantleeTheme::ThemeManager::loadTheme(defaultThemePath.at(0) + QStringLiteral("/")+name, name, QStringLiteral("kmail_default.desktop")); } void GrantleeHeaderStyleTest::testName() { auto style = GrantleeHeaderStyle(); QCOMPARE(style.name(), "grantlee"); } void GrantleeHeaderStyleTest::testRenderHeaderNoMessage() { auto style = GrantleeHeaderStyle(); QCOMPARE(style.format(nullptr), QString()); } void GrantleeHeaderStyleTest::testRenderHeaderInvalidTheme() { auto style = GrantleeHeaderStyle(); auto aMsg = new KMime::Message(); QCOMPARE(style.format(aMsg), QStringLiteral("Grantlee theme \"\" is not valid.")); } void GrantleeHeaderStyleTest::testRenderHeaderEmpty() { auto style = GrantleeHeaderStyle(); auto aMsg = new KMime::Message(); style.setTheme(defaultTheme()); testHeaderFile(style, aMsg, QStringLiteral("empty")); } void GrantleeHeaderStyleTest::testRenderHeaderVCard() { auto style = GrantleeHeaderStyle(); auto aMsg = new KMime::Message(); style.setTheme(defaultTheme()); style.setVCardName(QStringLiteral("nofile.vcd")); testHeaderFile(style, aMsg, QStringLiteral("vcard")); } void GrantleeHeaderStyleTest::testRenderHeader_data() { QTest::addColumn("mailFileName"); QDir dir(QStringLiteral(HEADER_DATA_DIR)); const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html"))) { continue; } QTest::newRow(file.toLatin1().constData()) << file; } } void GrantleeHeaderStyleTest::testRenderHeader() { QFETCH(QString, mailFileName); auto style = GrantleeHeaderStyle(); + MimeTreeParser::NodeHelper nodeHelper; + style.setNodeHelper(&nodeHelper); auto aMsg = readAndParseMail(mailFileName); style.setTheme(defaultTheme()); testHeaderFile(style, aMsg.data(), mailFileName); } diff --git a/mimetreeparser/autotests/basicobjecttreeparsertest.cpp b/mimetreeparser/autotests/basicobjecttreeparsertest.cpp index da8e0ce5..8cee073b 100644 --- a/mimetreeparser/autotests/basicobjecttreeparsertest.cpp +++ b/mimetreeparser/autotests/basicobjecttreeparsertest.cpp @@ -1,381 +1,401 @@ /* Copyright (c) 2010 Thomas McGuire Copyright (c) 2019 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "basicobjecttreeparsertest.h" #include "util.h" #include "setupenv.h" #include #include #include #include #include #include #include using namespace MimeTreeParser; QTEST_MAIN(ObjectTreeParserTest) void ObjectTreeParserTest::initTestCase() { Test::setupEnv(); } QString stringifyMessagePartTree(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent) { const QString line = QStringLiteral("%1 * %3\n").arg(indent, QString::fromUtf8(messagePart->metaObject()->className())); QString ret = line; indent += QLatin1Char(' '); foreach (const auto &subPart, messagePart->subParts()) { ret += stringifyMessagePartTree(subPart, indent); } return ret; } void testMessagePartTree(const MessagePart::Ptr &messagePart, const QString &mailFileName) { QString renderedTree = stringifyMessagePartTree(messagePart, QString()); const QString treeFileName = QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFileName + QStringLiteral(".tree"); const QString outTreeFileName = mailFileName + QStringLiteral(".tree"); { QFile f(outTreeFileName); f.open(QIODevice::WriteOnly); f.write(renderedTree.toUtf8()); f.close(); } // compare to reference file const QStringList args = QStringList() << QStringLiteral("-u") << treeFileName << outTreeFileName; QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(QStringLiteral("diff"), args); QVERIFY(proc.waitForFinished()); QCOMPARE(proc.exitCode(), 0); } void ObjectTreeParserTest::testMailWithoutEncryption() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); otp.parseObjectTree(originalMessage.data()); QVERIFY(!nodeHelper.unencryptedMessage(originalMessage)); } void ObjectTreeParserTest::testSignedForwardedOpenPGPSignedEncrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("signed-forward-openpgp-signed-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgPartiallyEncrypted); QCOMPARE(nodeHelper.overallSignatureState( originalMessage.data()), KMMsgFullySigned); KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QVERIFY(!unencryptedMessage); // We must not invalidate the outer signature } void ObjectTreeParserTest::testForwardedOpenPGPSignedEncrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("forward-openpgp-signed-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgPartiallyEncrypted); QCOMPARE(nodeHelper.overallSignatureState( originalMessage.data()), KMMsgPartiallySigned); // Now, test that the unencrypted message is generated correctly KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QVERIFY(unencryptedMessage.data()); QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/mixed"); QCOMPARE(unencryptedMessage->contents().size(), 2); QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(), "text/plain"); QCOMPARE(unencryptedMessage->contents().first()->decodedContent().data(), "bla bla bla"); QCOMPARE(unencryptedMessage->contents().at( 1)->contentType()->mimeType().data(), "message/rfc822"); KMime::Message::Ptr encapsulated = unencryptedMessage->contents().at(1)->bodyAsMessage(); QCOMPARE(encapsulated->contentType()->mimeType().data(), "multipart/signed"); QCOMPARE(encapsulated->contents().size(), 2); QCOMPARE(encapsulated->contents().first()->contentType()->mimeType().data(), "text/plain"); QCOMPARE(encapsulated->contents().at( 1)->contentType()->mimeType().data(), "application/pgp-signature"); QCOMPARE(encapsulated->contents().first()->decodedContent().data(), "encrypted message text"); // TODO: Check that the signature is valid } void ObjectTreeParserTest::testSMIMESignedEncrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("smime-signed-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text"); QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgFullyEncrypted); QCOMPARE(nodeHelper.overallSignatureState( originalMessage.data()), KMMsgFullySigned); // Now, test that the unencrypted message is generated correctly KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed"); QCOMPARE(unencryptedMessage->contents().size(), 2); QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(), "text/plain"); QCOMPARE(unencryptedMessage->contents().at( 1)->contentType()->mimeType().data(), "application/pkcs7-signature"); QCOMPARE( unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text"); // TODO: Check that the signature is valid } void ObjectTreeParserTest::testOpenPGPSignedEncrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("openpgp-signed-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text"); QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgFullyEncrypted); QCOMPARE(nodeHelper.overallSignatureState( originalMessage.data()), KMMsgFullySigned); // Now, test that the unencrypted message is generated correctly KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed"); QCOMPARE(unencryptedMessage->contents().size(), 2); QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(), "text/plain"); QCOMPARE(unencryptedMessage->contents().at( 1)->contentType()->mimeType().data(), "application/pgp-signature"); QCOMPARE( unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text"); // TODO: Check that the signature is valid } void ObjectTreeParserTest::testOpenPGPEncryptedAndSigned() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("openpgp-encrypted+signed.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text"); QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgFullyEncrypted); QCOMPARE(nodeHelper.overallSignatureState( originalMessage.data()), KMMsgFullySigned); // Now, test that the unencrypted message is generated correctly KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain"); QCOMPARE(unencryptedMessage->contents().size(), 0); QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text"); // TODO: Check that the signature is valid } void ObjectTreeParserTest::testOpenPGPEncrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("openpgp-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text"); QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgFullyEncrypted); // Now, test that the unencrypted message is generated correctly KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain"); QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text"); QCOMPARE(unencryptedMessage->contents().size(), 0); } void ObjectTreeParserTest::testOpenPGPEncryptedNotDecrypted() { KMime::Message::Ptr originalMessage = readAndParseMail(QStringLiteral("openpgp-encrypted.mbox")); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); otp.parseObjectTree(originalMessage.data()); QCOMPARE(nodeHelper.overallEncryptionState( originalMessage.data()), KMMsgFullyEncrypted); QCOMPARE(otp.plainTextContent().toLatin1().data(), ""); KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage); QVERIFY(!unencryptedMessage); } void ObjectTreeParserTest::testAsync_data() { QTest::addColumn("mailFileName"); QTest::addColumn("output"); QTest::newRow("openpgp-encrypt") << QStringLiteral("openpgp-encrypted.mbox") << QStringLiteral( "encrypted message text"); QTest::newRow("smime-opaque-sign") << QStringLiteral("smime-opaque-sign.mbox") << QStringLiteral("A simple signed only test."); QTest::newRow("smime-encrypt") << QStringLiteral("smime-encrypted.mbox") << QStringLiteral( "The quick brown fox jumped over the lazy dog."); QTest::newRow("openpgp-inline-encrypt") << QStringLiteral( "openpgp-inline-charset-encrypted.mbox") << QStringLiteral( "asdasd asd asd asdf sadf sdaf sadf \u00F6\u00E4\u00FC"); } void ObjectTreeParserTest::testAsync() { QFETCH(QString, mailFileName); QFETCH(QString, output); KMime::Message::Ptr originalMessage = readAndParseMail(mailFileName); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; testSource.setDecryptMessage(true); { QEventLoop loop; ObjectTreeParser otp(&testSource, &nodeHelper); connect(&nodeHelper, &NodeHelper::update, &loop, &QEventLoop::quit); otp.setAllowAsync(true); otp.parseObjectTree(originalMessage.data()); loop.exec(); } // Job ended { ObjectTreeParser otp(&testSource, &nodeHelper); otp.setAllowAsync(true); otp.parseObjectTree(originalMessage.data()); QCOMPARE(otp.plainTextContent(), output); } } void ObjectTreeParserTest::testHtmlContent_data() { QTest::addColumn("mailFileName"); QTest::addColumn("output"); QTest::newRow("html-attachments1") << QStringLiteral("html-attachment1.mbox") << QStringLiteral( "

A Body Text

"); QTest::newRow("html-attachments2") << QStringLiteral("html-attachment2.mbox") << QStringLiteral("HTML Text"); } void ObjectTreeParserTest::testHtmlContent() { QFETCH(QString, mailFileName); QFETCH(QString, output); KMime::Message::Ptr originalMessage = readAndParseMail(mailFileName); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); QVERIFY(otp.plainTextContent().isEmpty()); QCOMPARE(otp.htmlContent(), output); } +void ObjectTreeParserTest::testMemoryHole() +{ + const QString fileName = QStringLiteral("openpgp-encrypted-memoryhole.mbox"); + KMime::Message::Ptr originalMessage = readAndParseMail(fileName); + NodeHelper nodeHelper; + SimpleObjectTreeSource testSource; + ObjectTreeParser otp(&testSource, &nodeHelper); + testSource.setDecryptMessage(true); + otp.parseObjectTree(originalMessage.data()); + + QCOMPARE(nodeHelper.mailHeaderAsBase("from", originalMessage.data())->asUnicodeString(), QStringLiteral("you@example.com")); + QCOMPARE(nodeHelper.mailHeaderAsBase("to", originalMessage.data())->asUnicodeString(), QStringLiteral("me@example.com")); + QCOMPARE(nodeHelper.mailHeaderAsBase("subject", originalMessage.data())->asUnicodeString(), QStringLiteral("hidden subject")); + QCOMPARE(nodeHelper.mailHeaderAsBase("cc", originalMessage.data())->asUnicodeString(), QStringLiteral("cc@example.com")); + QCOMPARE(nodeHelper.mailHeaderAsBase("message-id", originalMessage.data())->asUnicodeString(), QStringLiteral("")); + QCOMPARE(nodeHelper.mailHeaderAsBase("references", originalMessage.data())->asUnicodeString(), QStringLiteral("")); + QCOMPARE(nodeHelper.mailHeaderAsBase("in-reply-to", originalMessage.data())->asUnicodeString(), QStringLiteral("")); + QCOMPARE(nodeHelper.dateHeader(originalMessage.data()), QDateTime(QDate(2018, 1, 2), QTime(3,4,5))); +} + void ObjectTreeParserTest::testRenderedTree_data() { QTest::addColumn("mailFileName"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : l) { if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".tree"))) { continue; } QTest::newRow(file.toLatin1().constData()) << file; } } void ObjectTreeParserTest::testRenderedTree() { QFETCH(QString, mailFileName); KMime::Message::Ptr originalMessage = readAndParseMail(mailFileName); NodeHelper nodeHelper; SimpleObjectTreeSource testSource; ObjectTreeParser otp(&testSource, &nodeHelper); testSource.setDecryptMessage(true); otp.parseObjectTree(originalMessage.data()); testMessagePartTree(otp.parsedPart(), mailFileName); } diff --git a/mimetreeparser/autotests/basicobjecttreeparsertest.h b/mimetreeparser/autotests/basicobjecttreeparsertest.h index d42b365f..6ff0c130 100644 --- a/mimetreeparser/autotests/basicobjecttreeparsertest.h +++ b/mimetreeparser/autotests/basicobjecttreeparsertest.h @@ -1,47 +1,48 @@ /* Copyright (c) 2010 Thomas McGuire Copyright (c) 2019 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H #define MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H #include class ObjectTreeParserTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testMailWithoutEncryption(); void testSMIMESignedEncrypted(); void testOpenPGPSignedEncrypted(); void testOpenPGPEncryptedAndSigned(); void testForwardedOpenPGPSignedEncrypted(); void testSignedForwardedOpenPGPSignedEncrypted(); void testOpenPGPEncrypted(); void testOpenPGPEncryptedNotDecrypted(); void testAsync_data(); void testAsync(); void testHtmlContent_data(); void testHtmlContent(); + void testMemoryHole(); void testRenderedTree(); void testRenderedTree_data(); }; #endif // MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H diff --git a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp index 5083bbb5..67d385bf 100644 --- a/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp +++ b/mimetreeparser/src/bodyformatter/applicationpgpencrypted.cpp @@ -1,86 +1,88 @@ /* Copyright (c) 2016 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "applicationpgpencrypted.h" #include "utils.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include #include "mimetreeparser_debug.h" using namespace MimeTreeParser; const ApplicationPGPEncryptedBodyPartFormatter *ApplicationPGPEncryptedBodyPartFormatter::self; const Interface::BodyPartFormatter *ApplicationPGPEncryptedBodyPartFormatter::create() { if (!self) { self = new ApplicationPGPEncryptedBodyPartFormatter(); } return self; } MessagePart::Ptr ApplicationPGPEncryptedBodyPartFormatter::process(Interface::BodyPart &part) const { KMime::Content *node(part.content()); if (node->decodedContent().trimmed() != "Version: 1") { qCWarning(MIMETREEPARSER_LOG) << "Unknown PGP Version String:" << node->decodedContent().trimmed(); } if (!part.content()->parent()) { return MessagePart::Ptr(); } KMime::Content *data = findTypeInDirectChilds(part.content()->parent(), "application/octet-stream"); if (!data) { return MessagePart::Ptr(); //new MimeMessagePart(part.objectTreeParser(), node, false)); } part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(part.objectTreeParser(), data->decodedText(), QGpgME::openpgp(), part.nodeHelper()->fromAsString(data), node)); mp->setIsEncrypted(true); mp->setDecryptMessage(part.source()->decryptMessage()); PartMetaData *messagePart(mp->partMetaData()); if (!part.source()->decryptMessage()) { part.nodeHelper()->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed } else if (KMime::Content *newNode = part.nodeHelper()->decryptedNodeForContent(data)) { + part.nodeHelper()->registerOverrideHeader(data->parent(), mp); // if we already have a decrypted node for this encrypted node, don't do the decryption again return MessagePart::Ptr(new MimeMessagePart(part.objectTreeParser(), newNode, false)); } else { mp->startDecryption(data); if (!messagePart->inProgress) { + part.nodeHelper()->registerOverrideHeader(data->parent(), mp); part.nodeHelper()->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed if (messagePart->isDecryptable && messagePart->isSigned) { part.nodeHelper()->setSignatureState(node, KMMsgFullySigned); } } } return mp; } diff --git a/mimetreeparser/src/bodyformatter/multipartencrypted.cpp b/mimetreeparser/src/bodyformatter/multipartencrypted.cpp index a8672caf..d72ffc93 100644 --- a/mimetreeparser/src/bodyformatter/multipartencrypted.cpp +++ b/mimetreeparser/src/bodyformatter/multipartencrypted.cpp @@ -1,100 +1,103 @@ /* Copyright (c) 2016 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multipartencrypted.h" #include "utils.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include #include "mimetreeparser_debug.h" using namespace MimeTreeParser; const MultiPartEncryptedBodyPartFormatter *MultiPartEncryptedBodyPartFormatter::self; const Interface::BodyPartFormatter *MultiPartEncryptedBodyPartFormatter::create() { if (!self) { self = new MultiPartEncryptedBodyPartFormatter(); } return self; } MessagePart::Ptr MultiPartEncryptedBodyPartFormatter::process(Interface::BodyPart &part) const { KMime::Content *node = part.content(); if (node->contents().isEmpty()) { Q_ASSERT(false); return MessagePart::Ptr(); } const QGpgME::Protocol *useThisCryptProto = nullptr; /* ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- */ KMime::Content *data = findTypeInDirectChilds(node, "application/octet-stream"); if (data) { useThisCryptProto = QGpgME::openpgp(); } if (!data) { data = findTypeInDirectChilds(node, "application/pkcs7-mime"); if (data) { useThisCryptProto = QGpgME::smime(); } } /* --------------------------------------------------------------------------------------------------------------- */ if (!data) { return MessagePart::Ptr(new MimeMessagePart(part.objectTreeParser(), node->contents().at(0), false)); } part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(part.objectTreeParser(), data->decodedText(), useThisCryptProto, part.nodeHelper()->fromAsString(data), node)); mp->setIsEncrypted(true); mp->setDecryptMessage(part.source()->decryptMessage()); PartMetaData *messagePart(mp->partMetaData()); + if (!part.source()->decryptMessage()) { part.nodeHelper()->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed } else if (KMime::Content *newNode = part.nodeHelper()->decryptedNodeForContent(data)) { + part.nodeHelper()->registerOverrideHeader(data->parent(), mp); + // if we already have a decrypted node for part.objectTreeParser() encrypted node, don't do the decryption again return MessagePart::Ptr(new MimeMessagePart(part.objectTreeParser(), newNode, true)); } else { mp->startDecryption(data); - qCDebug(MIMETREEPARSER_LOG) << "decrypted, signed?:" << messagePart->isSigned; if (!messagePart->inProgress) { + part.nodeHelper()->registerOverrideHeader(data->parent(), mp); part.nodeHelper()->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed } } return mp; } diff --git a/mimetreeparser/src/messagepart.cpp b/mimetreeparser/src/messagepart.cpp index dbaa1869..4c8d97d4 100644 --- a/mimetreeparser/src/messagepart.cpp +++ b/mimetreeparser/src/messagepart.cpp @@ -1,1401 +1,1449 @@ /* Copyright (c) 2015 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagepart.h" #include "mimetreeparser_debug.h" #include "cryptohelper.h" #include "objecttreeparser.h" #include "job/qgpgmejobexecutor.h" #include "memento/cryptobodypartmemento.h" #include "memento/decryptverifybodypartmemento.h" #include "memento/verifydetachedbodypartmemento.h" #include "memento/verifyopaquebodypartmemento.h" #include "bodyformatter/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; //------MessagePart----------------------- namespace MimeTreeParser { class MessagePartPrivate { public: MessagePart *mParentPart = nullptr; QVector mBlocks; KMime::Content *mNode = nullptr; KMime::Content *mAttachmentNode = nullptr; QString mText; PartMetaData mMetaData; bool mRoot = false; bool mIsImage = false; bool mNeverDisplayInline = false; }; } MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text) : mOtp(otp) , d(new MessagePartPrivate) { d->mText = text; } MessagePart::~MessagePart() = default; MessagePart *MessagePart::parentPart() const { return d->mParentPart; } void MessagePart::setParentPart(MessagePart *parentPart) { d->mParentPart = parentPart; } QString MessagePart::htmlContent() const { return text(); } QString MessagePart::plaintextContent() const { return text(); } PartMetaData *MessagePart::partMetaData() const { return &d->mMetaData; } Interface::BodyPartMemento *MessagePart::memento() const { return nodeHelper()->bodyPartMemento(content(), "__plugin__"); } void MessagePart::setMemento(Interface::BodyPartMemento *memento) { nodeHelper()->setBodyPartMemento(content(), "__plugin__", memento); } KMime::Content *MessagePart::content() const { return d->mNode; } void MessagePart::setContent(KMime::Content *node) { d->mNode = node; } KMime::Content *MessagePart::attachmentContent() const { return d->mAttachmentNode; } void MessagePart::setAttachmentContent(KMime::Content *node) { d->mAttachmentNode = node; } bool MessagePart::isAttachment() const { return d->mAttachmentNode; } QString MessagePart::attachmentIndex() const { return attachmentContent()->index().toString(); } QString MessagePart::attachmentLink() const { return mOtp->nodeHelper()->asHREF(content(), QStringLiteral("body")); } QString MessagePart::makeLink(const QString &path) const { // FIXME: use a PRNG for the first arg, instead of a serial number static int serial = 0; if (path.isEmpty()) { return {}; } return QStringLiteral("x-kmail:/bodypart/%1/%2/%3") .arg(serial++).arg(content()->index().toString()) .arg(QString::fromLatin1(QUrl::toPercentEncoding(path, "/"))); } void MessagePart::setIsRoot(bool root) { d->mRoot = root; } bool MessagePart::isRoot() const { return d->mRoot; } QString MessagePart::text() const { return d->mText; } void MessagePart::setText(const QString &text) { d->mText = text; } bool MessagePart::isHtml() const { return false; } Interface::ObjectTreeSource *MessagePart::source() const { Q_ASSERT(mOtp); return mOtp->mSource; } NodeHelper *MessagePart::nodeHelper() const { Q_ASSERT(mOtp); return mOtp->nodeHelper(); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); d->mRoot = subMessagePart->isRoot(); foreach (const auto &part, subMessagePart->subParts()) { appendSubPart(part); } } QString MessagePart::renderInternalText() const { QString text; foreach (const auto &mp, subParts()) { text += mp->text(); } return text; } void MessagePart::fix() const { foreach (const auto &mp, subParts()) { const auto m = mp.dynamicCast(); if (m) { m->fix(); } } } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); d->mBlocks.append(messagePart); } const QVector &MessagePart::subParts() const { return d->mBlocks; } bool MessagePart::hasSubParts() const { return !d->mBlocks.isEmpty(); } void MessagePart::clearSubParts() { d->mBlocks.clear(); } bool MessagePart::neverDisplayInline() const { return d->mNeverDisplayInline; } void MessagePart::setNeverDisplayInline(bool displayInline) { d->mNeverDisplayInline = displayInline; } bool MessagePart::isImage() const { return d->mIsImage; } void MessagePart::setIsImage(bool image) { d->mIsImage = image; } +bool MessagePart::hasHeader(const char *header) const +{ + Q_UNUSED(header); + return false; +} + +KMime::Headers::Base * MimeTreeParser::MessagePart::header(const char* header) const +{ + Q_UNUSED(header); + return nullptr; +} + //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp) : MessagePart(otp, QString()) { } MessagePartList::~MessagePartList() { } 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, bool decryptMessage) : MessagePartList(otp) , mDecryptMessage(decryptMessage) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); parseContent(); } TextMessagePart::~TextMessagePart() { } bool TextMessagePart::decryptMessage() const { return mDecryptMessage; } void TextMessagePart::parseContent() { const auto aCodec = mOtp->codecFor(content()); const QString &fromAddress = mOtp->nodeHelper()->fromAsString(content()); mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; const auto blocks = prepareMessageForDecryption(content()->decodedContent()); const auto cryptProto = QGpgME::openpgp(); if (!blocks.isEmpty()) { /* 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 unencrypted */ bool fullySignedOrEncrypted = true; bool fullySignedOrEncryptedTmp = true; int blockIndex = -1; for (const auto &block : blocks) { blockIndex += 1; if (!fullySignedOrEncryptedTmp) { fullySignedOrEncrypted = false; } if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { fullySignedOrEncryptedTmp = false; appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text())))); } else if (block.type() == PgpMessageBlock) { EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); mp->setDecryptMessage(decryptMessage()); mp->setIsEncrypted(true); mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit() ); appendSubPart(mp); if (!decryptMessage()) { continue; } mp->startDecryption(block.text(), aCodec); if (mp->partMetaData()->inProgress) { continue; } } else if (block.type() == ClearsignedBlock) { SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit() ); appendSubPart(mp); mp->startVerification(block.text(), aCodec); } else { continue; } const auto mp = subParts().last().staticCast(); const PartMetaData *messagePart(mp->partMetaData()); if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { mp->setText(aCodec->toUnicode(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 { return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { return mSignatureState; } bool TextMessagePart::showLink() const { return !temporaryFilePath().isEmpty(); } bool TextMessagePart::isFirstTextPart() const { return content()->topLevel()->textContent() == content(); } bool TextMessagePart::hasLabel() const { return !NodeHelper::fileName(content()).isEmpty(); } QString TextMessagePart::label() const { const QString name = content()->contentType()->name(); QString label = name.isEmpty() ? NodeHelper::fileName(content()) : name; if (label.isEmpty()) { label = i18nc("display name for an unnamed attachment", "Unnamed"); } return label; } QString TextMessagePart::comment() const { const QString comment = content()->contentDescription()->asUnicodeString(); if (comment == label()) { return {}; } return comment; } QString TextMessagePart::temporaryFilePath() const { return nodeHelper()->writeNodeToTempFile(content()); } //-----AttachmentMessageBlock---------------------- AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage) : TextMessagePart(otp, node, decryptMessage) { } AttachmentMessagePart::~AttachmentMessagePart() { } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source) : MessagePart(otp, QString()) , mSource(source) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); const QByteArray partBody(node->decodedContent()); mBodyHTML = mOtp->codecFor(node)->toUnicode(partBody); mCharset = NodeHelper::charset(node); } HtmlMessagePart::~HtmlMessagePart() { } void HtmlMessagePart::fix() const { mOtp->mHtmlContent += mBodyHTML; mOtp->mHtmlContentCharset = mCharset; } QString HtmlMessagePart::text() const { return mBodyHTML; } QString MimeTreeParser::HtmlMessagePart::plaintextContent() const { return QString(); } bool HtmlMessagePart::isHtml() const { return true; } QString HtmlMessagePart::bodyHtml() const { return mBodyHTML; } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString()) , mOnlyOneMimePart(onlyOneMimePart) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); parseInternal(node, mOnlyOneMimePart); } 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, Util::HtmlMode preferredMode) : MessagePart(otp, QString()) , mPreferredMode(preferredMode) { setContent(node); KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar"); KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html"); KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain"); if (!dataHtml) { // 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. dataHtml = findTypeInDirectChilds(node, "multipart/related"); // Still not found? Stupid apple mail actually puts the attachments inside of the // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed // here. // Do this only when preferring HTML mail, though, since otherwise the attachments are hidden // when displaying plain text. if (!dataHtml) { dataHtml = findTypeInDirectChilds(node, "multipart/mixed"); } } if (dataIcal) { mChildNodes[Util::MultipartIcal] = dataIcal; } if (dataText) { mChildNodes[Util::MultipartPlain] = dataText; } if (dataHtml) { mChildNodes[Util::MultipartHtml] = dataHtml; } if (mChildNodes.isEmpty()) { qCWarning(MIMETREEPARSER_LOG) << "no valid nodes"; return; } QMapIterator i(mChildNodes); while (i.hasNext()) { i.next(); mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true)); } } AlternativeMessagePart::~AlternativeMessagePart() { } Util::HtmlMode AlternativeMessagePart::preferredMode() const { return mPreferredMode; } void AlternativeMessagePart::setPreferredMode(Util::HtmlMode preferredMode) { mPreferredMode = preferredMode; } QList AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(Util::MultipartPlain)) { return mChildParts[Util::MultipartPlain]->text(); } return QString(); } void AlternativeMessagePart::fix() const { if (mChildParts.contains(Util::MultipartPlain)) { mChildParts[Util::MultipartPlain]->fix(); } const auto mode = preferredMode(); if (mode != Util::MultipartPlain && mChildParts.contains(mode)) { mChildParts[mode]->fix(); } } const QMap &AlternativeMessagePart::childParts() const { return mChildParts; } bool AlternativeMessagePart::isHtml() const { return mChildParts.contains(Util::MultipartHtml); } QString AlternativeMessagePart::plaintextContent() const { return text(); } QString AlternativeMessagePart::htmlContent() const { if (mChildParts.contains(Util::MultipartHtml)) { return mChildParts[Util::MultipartHtml]->text(); } else { return plaintextContent(); } } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport) : MessagePart(otp, QString()) , mAutoImport(autoImport) , mCryptoProto(cryptoProto) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); if (!mAutoImport) { return; } const QByteArray certData = node->decodedContent(); QGpgME::ImportJob *import = mCryptoProto->importJob(); QGpgMEJobExecutor executor; mImportResult = executor.exec(import, certData); } CertMessagePart::~CertMessagePart() { } QString CertMessagePart::text() const { return QString(); } const GpgME::ImportResult &CertMessagePart::importResult() const { return mImportResult; } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mMementoName("verification") { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = true; partMetaData()->isGoodSignature = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { partMetaData()->isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return partMetaData()->isSigned; } QByteArray SignedMessagePart::mementoName() const { return mMementoName; } void SignedMessagePart::setMementoName(const QByteArray& name) { mMementoName = name; } bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode) { NodeHelper *nodeHelper = mOtp->nodeHelper(); partMetaData()->isSigned = false; partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; const QByteArray _mementoName = mementoName(); CryptoBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(content(), _mementoName)); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { if (!signature.isEmpty()) { QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob(); if (job) { m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data); } } else { QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob(); if (job) { m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data); } } if (m) { if (mOtp->allowAsync()) { QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (m->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } } else { m->exec(); } nodeHelper->setBodyPartMemento(content(), _mementoName, m); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { partMetaData()->inProgress = false; mOtp->mHasPendingAsyncJobs = false; } if (m && !partMetaData()->inProgress) { if (!signature.isEmpty()) { mVerifiedText = data; } setVerificationResult(m, textNode); } if (!m && !partMetaData()->inProgress) { QString errorMsg; QString cryptPlugLibName; QString cryptPlugDisplayName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); cryptPlugDisplayName = mCryptoProto->displayName(); } if (!mCryptoProto) { if (cryptPlugDisplayName.isEmpty()) { errorMsg = i18n("No appropriate crypto plug-in was found."); } else { errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName); } } else { errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName); } partMetaData()->errorText = i18n("The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1", errorMsg); } return partMetaData()->isSigned; } 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; } } QString prettifyDN(const char *uid) { return QGpgME::DN(uid).prettyDN(); } void SignedMessagePart::sigStatusToMetaData() { GpgME::Key key; if (partMetaData()->isSigned) { GpgME::Signature signature = mSignatures.front(); partMetaData()->status_code = signatureToStatus(signature); partMetaData()->isGoodSignature = partMetaData()->status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags partMetaData()->sigSummary = signature.summary(); if (partMetaData()->isGoodSignature && !key.keyID()) { // Search for the key by its fingerprint so that we can check for // trust etc. QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } delete job; } } if (key.keyID()) { partMetaData()->keyId = key.keyID(); } if (partMetaData()->keyId.isEmpty()) { partMetaData()->keyId = signature.fingerprint(); } partMetaData()->keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { partMetaData()->signer = prettifyDN(key.userID(0).id()); } for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) { // The following if /should/ always result in TRUE but we // won't trust implicitly the plugin that gave us these data. if (key.userID(iMail).email()) { QString email = QString::fromUtf8(key.userID(iMail).email()); // ### work around gpgme 0.3.QString text() const override;x / cryptplug bug where the // ### email addresses are specified as angle-addr, not addr-spec: if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { email = email.mid(1, email.length() - 2); } 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 += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); } } } } } void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec) { startVerificationDetached(text, nullptr, QByteArray()); if (!content() && partMetaData()->isSigned) { setText(aCodec->toUnicode(mVerifiedText)); } } void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; if (textNode) { parseInternal(textNode, false); } okVerify(text, signature, textNode); if (!partMetaData()->isSigned) { partMetaData()->creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode) { { const auto vm = dynamic_cast(m); if (vm) { mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } partMetaData()->auditLogError = m->auditLogError(); partMetaData()->auditLog = m->auditLogAsHtml(); partMetaData()->isSigned = !mSignatures.empty(); if (partMetaData()->isSigned) { sigStatusToMetaData(); if (content()) { mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned); if (!textNode) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (!mVerifiedText.isEmpty()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("signed data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } } } QString SignedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString SignedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } const QGpgME::Protocol *SignedMessagePart::cryptoProto() const { return mCryptoProto; } QString SignedMessagePart::fromAddress() const { return mFromAddress; } +bool SignedMessagePart::hasHeader(const char* header) const +{ + const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content()); + if (extraContent) { + return extraContent->hasHeader(header); + } + return false; +} + +KMime::Headers::Base * SignedMessagePart::header(const char* header) const +{ + const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content()); + if (extraContent) { + return extraContent->headerByType(header); + } + return nullptr; +} + //-----CryptMessageBlock--------------------- EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mPassphraseError(false) , mNoSecKey(false) , mDecryptMessage(false) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mMementoName("decryptverify") { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = false; partMetaData()->isGoodSignature = false; partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } EncryptedMessagePart::~EncryptedMessagePart() { } void EncryptedMessagePart::setDecryptMessage(bool decrypt) { mDecryptMessage = decrypt; } bool EncryptedMessagePart::decryptMessage() const { return mDecryptMessage; } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { partMetaData()->isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return partMetaData()->isEncrypted; } bool EncryptedMessagePart::isDecryptable() const { return partMetaData()->isDecryptable; } bool EncryptedMessagePart::isNoSecKey() const { return mNoSecKey; } bool EncryptedMessagePart::passphraseError() const { return mPassphraseError; } QByteArray EncryptedMessagePart::mementoName() const { return mMementoName; } void EncryptedMessagePart::setMementoName(const QByteArray& name) { mMementoName = name; } void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); startDecryption(content); if (!partMetaData()->inProgress && partMetaData()->isDecryptable) { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { _mp->setText(aCodec->toUnicode(mDecryptedData)); } else { setText(aCodec->toUnicode(mDecryptedData)); } } else { setText(aCodec->toUnicode(mDecryptedData)); } } } bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) { mPassphraseError = false; partMetaData()->inProgress = false; partMetaData()->errorText.clear(); partMetaData()->auditLogError = GpgME::Error(); partMetaData()->auditLog.clear(); bool bDecryptionOk = false; bool cannotDecrypt = false; NodeHelper *nodeHelper = mOtp->nodeHelper(); Q_ASSERT(decryptMessage()); const QByteArray _mementoName = mementoName(); // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(&data, _mementoName)); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob(); if (!job) { cannotDecrypt = true; } else { const QByteArray ciphertext = data.decodedContent(); DecryptVerifyBodyPartMemento *newM = new DecryptVerifyBodyPartMemento(job, ciphertext); if (mOtp->allowAsync()) { QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (newM->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } nodeHelper->setBodyPartMemento(&data, _mementoName, newM); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; m = nullptr; } if (m) { const QByteArray &plainText = m->plainText(); const GpgME::DecryptionResult &decryptResult = m->decryptResult(); const GpgME::VerificationResult &verifyResult = m->verifyResult(); partMetaData()->isSigned = verifyResult.signatures().size() > 0; if (partMetaData()->isSigned) { auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content())); subPart->setVerificationResult(m, nullptr); appendSubPart(subPart); } mDecryptRecipients.clear(); bDecryptionOk = !decryptResult.error(); // std::stringstream ss; // ss << decryptResult << '\n' << verifyResult; // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); for (const auto &recipient : decryptResult.recipients()) { if (!recipient.status()) { bDecryptionOk = true; } GpgME::Key key; QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(recipient.keyID())), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << recipient.keyID(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << recipient.keyID(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << recipient.keyID(); } else { key = found_keys[0]; } } mDecryptRecipients.push_back(std::make_pair(recipient, key)); } 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_NO_SECKEY; partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; if (decryptResult.error().isCanceled()) { setDecryptMessage(false); } partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { partMetaData()->keyId = decryptResult.recipient(0).keyID(); } if (bDecryptionOk) { mDecryptedData = plainText; } else { mNoSecKey = true; foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); } if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly mPassphraseError = true; } } } } if (!bDecryptionOk) { QString cryptPlugLibName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); } 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) + QLatin1String("
") + i18n("Error: %1", partMetaData()->errorText); } } return bDecryptionOk; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { if (!content() && !data) { return; } if (!data) { data = content(); } partMetaData()->isEncrypted = true; bool bOkDecrypt = okDecryptMIME(*data); if (partMetaData()->inProgress) { return; } partMetaData()->isDecryptable = bOkDecrypt; if (!partMetaData()->isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } if (partMetaData()->isEncrypted && !decryptMessage()) { partMetaData()->isDecryptable = true; } if (content() && !partMetaData()->isSigned) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (decryptMessage()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("encrypted data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } QString EncryptedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::text() const { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { return _mp->text(); } else { return MessagePart::text(); } } else { return MessagePart::text(); } } const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const { return mCryptoProto; } QString EncryptedMessagePart::fromAddress() const { return mFromAddress; } const std::vector > &EncryptedMessagePart::decryptRecipients() const { return mDecryptRecipients; } +bool EncryptedMessagePart::hasHeader(const char* header) const +{ + const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content()); + if (extraContent) { + return extraContent->hasHeader(header); + } + return false; +} + +KMime::Headers::Base * EncryptedMessagePart::header(const char* header) const +{ + const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content()); + if (extraContent) { + return extraContent->headerByType(header); + } + return nullptr; +} + EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) : MessagePart(otp, QString()) , mMessage(message) { setContent(node); partMetaData()->isEncrypted = false; partMetaData()->isSigned = false; partMetaData()->isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true); mOtp->nodeHelper()->setPartMetaData(node, *partMetaData()); if (!mMessage) { qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists, // since the user can click the link and expect to have normal attachment operations there. mOtp->nodeHelper()->writeNodeToTempFile(message.data()); parseInternal(message.data(), false); } EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() { } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } void EncapsulatedRfc822MessagePart::fix() const { } const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const { return mMessage; } diff --git a/mimetreeparser/src/messagepart.h b/mimetreeparser/src/messagepart.h index 27ee6636..072640bc 100644 --- a/mimetreeparser/src/messagepart.h +++ b/mimetreeparser/src/messagepart.h @@ -1,450 +1,457 @@ /* Copyright (c) 2015 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIMETREEPARSER_MESSAGEPART_H #define MIMETREEPARSER_MESSAGEPART_H #include "mimetreeparser_export.h" #include "mimetreeparser/bodypartformatter.h" #include "mimetreeparser/util.h" #include #include #include #include #include #include #include #include class QTextCodec; namespace GpgME { class ImportResult; } namespace QGpgME { class Protocol; } namespace KMime { class Content; } namespace MimeTreeParser { class CryptoBodyPartMemento; class MessagePartPrivate; namespace Interface { class ObjectTreeSource; } /** * @brief The MessagePart class */ class MIMETREEPARSER_EXPORT MessagePart : public QObject { Q_OBJECT Q_PROPERTY(QString plaintextContent READ plaintextContent) Q_PROPERTY(QString htmlContent READ htmlContent) Q_PROPERTY(bool isAttachment READ isAttachment) Q_PROPERTY(bool root READ isRoot) Q_PROPERTY(bool isHtml READ isHtml) Q_PROPERTY(bool isImage READ isImage CONSTANT) Q_PROPERTY(bool neverDisplayInline READ neverDisplayInline CONSTANT) Q_PROPERTY(QString attachmentIndex READ attachmentIndex CONSTANT) Q_PROPERTY(QString link READ attachmentLink CONSTANT) public: typedef QSharedPointer Ptr; MessagePart(ObjectTreeParser *otp, const QString &text); ~MessagePart(); void setParentPart(MessagePart *parentPart); MessagePart *parentPart() const; virtual QString text() const; void setText(const QString &text); virtual QString plaintextContent() const; virtual QString htmlContent() const; /** The KMime::Content* node that's represented by this part. * Can be @c nullptr, e.g. for sub-parts of an inline signed body part. */ KMime::Content *content() const; void setContent(KMime::Content *node); /** The KMime::Content* node that's the source of this part. * This is not necessarily the same as content(), for example for * broken-up multipart nodes. */ KMime::Content *attachmentContent() const; void setAttachmentContent(KMime::Content *node); bool isAttachment() const; /** @see KMime::Content::index() */ QString attachmentIndex() const; /** @see NodeHelper::asHREF */ QString attachmentLink() const; /** Returns a string representation of an URL that can be used * to invoke a BodyPartURLHandler for this body part. */ QString makeLink(const QString &path) const; void setIsRoot(bool root); bool isRoot() const; virtual bool isHtml() const; bool neverDisplayInline() const; void setNeverDisplayInline(bool displayInline); bool isImage() const; void setIsImage(bool image); PartMetaData *partMetaData() const; Interface::BodyPartMemento *memento() const; void setMemento(Interface::BodyPartMemento *memento); /* only a function that should be removed if the refactoring is over */ virtual void fix() const; void appendSubPart(const MessagePart::Ptr &messagePart); const QVector &subParts() const; bool hasSubParts() const; void clearSubParts(); Interface::ObjectTreeSource *source() const; NodeHelper *nodeHelper() const; + virtual bool hasHeader(const char *header) const; + virtual KMime::Headers::Base *header(const char *header) const; + protected: void parseInternal(KMime::Content *node, bool onlyOneMimePart); QString renderInternalText() const; ObjectTreeParser *mOtp = nullptr; private: std::unique_ptr d; }; /** * @brief The MimeMessagePart class */ class MIMETREEPARSER_EXPORT MimeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart); ~MimeMessagePart() override; QString text() const override; QString plaintextContent() const override; QString htmlContent() const override; private: bool mOnlyOneMimePart; }; /** * @brief The MessagePartList class */ class MIMETREEPARSER_EXPORT MessagePartList : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; explicit MessagePartList(MimeTreeParser::ObjectTreeParser *otp); ~MessagePartList() override; QString text() const override; QString plaintextContent() const override; QString htmlContent() const override; }; enum IconType { NoIcon = 0, IconExternal, IconInline }; /** * @brief The TextMessagePart class */ class MIMETREEPARSER_EXPORT TextMessagePart : public MessagePartList { Q_OBJECT Q_PROPERTY(bool showLink READ showLink CONSTANT) Q_PROPERTY(bool isFirstTextPart READ isFirstTextPart CONSTANT) Q_PROPERTY(bool hasLabel READ hasLabel CONSTANT) Q_PROPERTY(QString label READ label CONSTANT) Q_PROPERTY(QString comment READ comment CONSTANT) public: typedef QSharedPointer Ptr; TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage); ~TextMessagePart() override; KMMsgSignatureState signatureState() const; KMMsgEncryptionState encryptionState() const; bool decryptMessage() const; bool showLink() const; bool isFirstTextPart() const; bool hasLabel() const; /** The attachment filename, or the closest approximation thereof we have. */ QString label() const; /** A description of this attachment, if provided. */ QString comment() const; /** Temporary file containing the part content. */ QString temporaryFilePath() const; private: void parseContent(); KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; bool mDecryptMessage; }; /** * @brief The AttachmentMessagePart class */ class MIMETREEPARSER_EXPORT AttachmentMessagePart : public TextMessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage); ~AttachmentMessagePart() override; }; /** * @brief The HtmlMessagePart class */ class MIMETREEPARSER_EXPORT HtmlMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, MimeTreeParser::Interface::ObjectTreeSource *source); ~HtmlMessagePart() override; QString text() const override; QString plaintextContent() const override; void fix() const override; bool isHtml() const override; QString bodyHtml() const; private: Interface::ObjectTreeSource *mSource; QString mBodyHTML; QByteArray mCharset; }; /** * @brief The AlternativeMessagePart class */ class MIMETREEPARSER_EXPORT AlternativeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode); ~AlternativeMessagePart() override; QString text() const override; Util::HtmlMode preferredMode() const; void setPreferredMode(Util::HtmlMode preferredMode); bool isHtml() const override; QString plaintextContent() const override; QString htmlContent() const override; QList availableModes(); void fix() const override; const QMap &childParts() const; private: Util::HtmlMode mPreferredMode; QMap mChildNodes; QMap mChildParts; }; /** * @brief The CertMessagePart class */ class MIMETREEPARSER_EXPORT CertMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport); ~CertMessagePart() override; QString text() const override; const GpgME::ImportResult &importResult() const; private: bool mAutoImport; GpgME::ImportResult mImportResult; const QGpgME::Protocol *mCryptoProto; }; /** * @brief The EncapsulatedRfc822MessagePart class */ class MIMETREEPARSER_EXPORT EncapsulatedRfc822MessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); ~EncapsulatedRfc822MessagePart() override; QString text() const override; void fix() const override; const KMime::Message::Ptr message() const; private: const KMime::Message::Ptr mMessage; }; /** * @brief The EncryptedMessagePart class */ class MIMETREEPARSER_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 Ptr; EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); ~EncryptedMessagePart() override; QString text() const override; void setDecryptMessage(bool decrypt); bool decryptMessage() const; void setIsEncrypted(bool encrypted); bool isEncrypted() const; bool isDecryptable() const; bool isNoSecKey() const; bool passphraseError() const; void startDecryption(const QByteArray &text, const QTextCodec *aCodec); void startDecryption(KMime::Content *data = nullptr); void setMementoName(const QByteArray &name); QByteArray mementoName() const; QByteArray mDecryptedData; QString plaintextContent() const override; QString htmlContent() const override; const QGpgME::Protocol *cryptoProto() const; QString fromAddress() const; const std::vector > &decryptRecipients() const; + bool hasHeader(const char *header) const override; + KMime::Headers::Base* header(const char* header) const override; private: /** Handles the decryption of a given content * returns true if the decryption was successful * if used in async mode, check if mMetaData.inPogress is true, it initiates a running decryption process. */ bool okDecryptMIME(KMime::Content &data); protected: bool mPassphraseError; bool mNoSecKey; bool mDecryptMessage; const QGpgME::Protocol *mCryptoProto; QString mFromAddress; QByteArray mVerifiedText; QByteArray mMementoName; std::vector > mDecryptRecipients; friend class EncryptedBodyPartFormatter; }; /** * @brief The SignedMessagePart class */ class MIMETREEPARSER_EXPORT SignedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isSigned READ isSigned) public: typedef QSharedPointer Ptr; SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); ~SignedMessagePart() override; void setIsSigned(bool isSigned); bool isSigned() const; void startVerification(const QByteArray &text, const QTextCodec *aCodec); void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature); void setMementoName(const QByteArray &name); QByteArray mementoName() const; QByteArray mDecryptedData; std::vector mSignatures; QString plaintextContent() const override; QString htmlContent() const override; const QGpgME::Protocol *cryptoProto() const; QString fromAddress() const; + bool hasHeader(const char *header) const override; + KMime::Headers::Base *header(const char *header) const override; private: /** Handles the verification of data * If signature is empty it is handled as inline signature otherwise as detached signature mode. * Returns true if the verification was successful and the block is signed. * If used in async mode, check if mMetaData.inProgress is true, it initiates a running verification process. */ bool okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode); void sigStatusToMetaData(); void setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode); protected: const QGpgME::Protocol *mCryptoProto; QString mFromAddress; QByteArray mVerifiedText; QByteArray mMementoName; friend EncryptedMessagePart; }; } #endif //__MIMETREEPARSER_MESSAGEPART_H diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp index 958f343e..a5559ce0 100644 --- a/mimetreeparser/src/nodehelper.cpp +++ b/mimetreeparser/src/nodehelper.cpp @@ -1,1068 +1,1093 @@ /* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nodehelper.h" #include "mimetreeparser_debug.h" #include "partmetadata.h" +#include "messagepart.h" #include "interfaces/bodypart.h" #include "temporaryfile/attachmenttemporaryfilesdirs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MimeTreeParser { NodeHelper::NodeHelper() : mAttachmentFilesDir(new AttachmentTemporaryFilesDirs()) { //TODO(Andras) add methods to modify these prefixes mLocalCodec = QTextCodec::codecForLocale(); // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. // (Introduction to i18n, 6.6 Limit of Locale technology): // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP // is the standard for Internet, and Shift-JIS is the encoding // for Windows and Macintosh. if (mLocalCodec) { const QByteArray codecNameLower = mLocalCodec->name().toLower(); if (codecNameLower == "eucjp" #if defined Q_OS_WIN || defined Q_OS_MACX || codecNameLower == "shift-jis" // OK? #endif ) { mLocalCodec = QTextCodec::codecForName("jis7"); // QTextCodec *cdc = QTextCodec::codecForName("jis7"); // QTextCodec::setCodecForLocale(cdc); // KLocale::global()->setEncoding(cdc->mibEnum()); } } } NodeHelper::~NodeHelper() { if (mAttachmentFilesDir) { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } clear(); } void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.append(node); qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString(); //<< " decodedContent" << node->decodedContent(); if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeProcessed(c, true); } } } void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.removeAll(node); //avoid double addition of extra nodes, eg. encrypted attachments const QMap >::iterator it = mExtraContents.find(node); if (it != mExtraContents.end()) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); mExtraContents.erase(it); } qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node; if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeUnprocessed(c, true); } } } bool NodeHelper::nodeProcessed(KMime::Content *node) const { if (!node) { return true; } return mProcessedNodes.contains(node); } static void clearBodyPartMemento(QMap &bodyPartMementoMap) { for (QMap::iterator it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end(); it != end; ++it) { Interface::BodyPartMemento *memento = it.value(); memento->detach(); delete memento; } bodyPartMementoMap.clear(); } void NodeHelper::clear() { mProcessedNodes.clear(); mEncryptionState.clear(); mSignatureState.clear(); mOverrideCodecs.clear(); std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(), &clearBodyPartMemento); mBodyPartMementoMap.clear(); QMap >::ConstIterator end(mExtraContents.constEnd()); for (QMap >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); } mExtraContents.clear(); mDisplayEmbeddedNodes.clear(); mDisplayHiddenNodes.clear(); } void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state) { mEncryptionState[node] = state; } KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const { return mEncryptionState.value(node, KMMsgNotEncrypted); } void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state) { mSignatureState[node] = state; } KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const { return mSignatureState.value(node, KMMsgNotSigned); } PartMetaData NodeHelper::partMetaData(KMime::Content *node) { return mPartMetaDatas.value(node, PartMetaData()); } void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData) { mPartMetaDatas.insert(node, metaData); } QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename) { QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } fname += QLatin1Char('/') + filename; QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(QByteArray()); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QString NodeHelper::writeNodeToTempFile(KMime::Content *node) { // If the message part is already written to a file, no point in doing it again. // This function is called twice actually, once from the rendering of the attachment // in the body and once for the header. QUrl existingFileName = tempFileUrlFromNode(node); if (!existingFileName.isEmpty()) { return existingFileName.toLocalFile(); } QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } QString fileName = NodeHelper::fileName(node); // strip off a leading path int slashPos = fileName.lastIndexOf(QLatin1Char('/')); if (-1 != slashPos) { fileName = fileName.mid(slashPos + 1); } if (fileName.isEmpty()) { fileName = QStringLiteral("unnamed"); } fname += QLatin1Char('/') + fileName; qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname; QByteArray data = node->decodedContent(); if (node->contentType()->isText() && !data.isEmpty()) { // convert CRLF to LF before writing text attachments to disk data = KMime::CRLFtoLF(data); } QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(data); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node) { if (!node) { return QUrl(); } const QString index = persistentIndex(node); foreach (const QString &path, mAttachmentFilesDir->temporaryFiles()) { const int right = path.lastIndexOf(QLatin1Char('/')); int left = path.lastIndexOf(QLatin1String(".index."), right); if (left != -1) { left += 7; } QStringRef storedIndex(&path, left, right - left); if (left != -1 && storedIndex == index) { return QUrl::fromLocalFile(path); } } return QUrl(); } QString NodeHelper::createTempDir(const QString ¶m) { QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param); tempFile->open(); const QString fname = tempFile->fileName(); delete tempFile; QFile fFile(fname); if (!(fFile.permissions() & QFileDevice::WriteUser)) { // Not there or not writable if (!QDir().mkpath(fname) || !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) { mAttachmentFilesDir->addTempDir(fname); return QString(); //failed create } } Q_ASSERT(!fname.isNull()); mAttachmentFilesDir->addTempDir(fname); return fname; } void NodeHelper::forceCleanTempFiles() { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } void NodeHelper::removeTempFiles() { //Don't delete as it will be deleted in class mAttachmentFilesDir->removeTempFiles(); mAttachmentFilesDir = new AttachmentTemporaryFilesDirs(); } void NodeHelper::addTempFile(const QString &file) { mAttachmentFilesDir->addTempFile(file); } bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node) { const KMime::Content *const topLevel = node->topLevel(); const KMime::Content *cur = node; while (cur && cur != topLevel) { const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType()->mimeType().toLower() == "message/rfc822"; if (parentIsMessage && cur->parent() != topLevel) { return true; } cur = cur->parent(); } return false; } QByteArray NodeHelper::charset(KMime::Content *node) { if (node->contentType(false)) { return node->contentType(false)->charset(); } else { return node->defaultCharset(); } } KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const { KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgEncryptionState otherState = encryptionState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) { otherState = overallEncryptionState(next->contents().at(0)); } if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) { otherState = overallEncryptionState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgEncryptionStateUnknown: break; case KMMsgNotEncrypted: if (myState == KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } else if (myState != KMMsgPartiallyEncrypted) { myState = KMMsgNotEncrypted; } break; case KMMsgPartiallyEncrypted: myState = KMMsgPartiallyEncrypted; break; case KMMsgFullyEncrypted: if (myState != KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } break; case KMMsgEncryptionProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState; return myState; } KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const { KMMsgSignatureState myState = KMMsgSignatureStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { //Be safe return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgSignatureState otherState = signatureState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) { otherState = overallSignatureState(next->contents().at(0)); } if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) { otherState = overallSignatureState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgSignatureStateUnknown: break; case KMMsgNotSigned: if (myState == KMMsgFullySigned) { myState = KMMsgPartiallySigned; } else if (myState != KMMsgPartiallySigned) { myState = KMMsgNotSigned; } break; case KMMsgPartiallySigned: myState = KMMsgPartiallySigned; break; case KMMsgFullySigned: if (myState != KMMsgFullySigned) { myState = KMMsgPartiallySigned; } break; case KMMsgSignatureProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState; return myState; } void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode) { const QByteArray body = aAutoDecode ? node->decodedContent() : node->body(); QMimeDatabase db; QMimeType mime = db.mimeTypeForData(body); QString mimetype = mime.name(); node->contentType()->setMimeType(mimetype.toLatin1()); } -bool NodeHelper::hasMailHeader(const char *header, const KMime::Message *message) const +bool NodeHelper::hasMailHeader(const char *header, const KMime::Content *message) const { return message->hasHeader(header); } -KMime::Headers::Base * NodeHelper::mailHeaderAsBase(const char *header, const KMime::Message *message) const +KMime::Headers::Base const * NodeHelper::mailHeaderAsBase(const char *header, const KMime::Content *message) const { + if (mHeaderOverwrite.contains(message)) { + foreach (const auto messagePart, mHeaderOverwrite.value(message)) { + if (messagePart->hasHeader(header)) { + return messagePart->header(header); // Found. + } + } + } return message->headerByType(header); } -KMime::Headers::Generics::AddressList * NodeHelper::mailHeaderAsAddressList(const char *header, KMime::Message *message) const +KMime::Headers::Generics::AddressList const * NodeHelper::mailHeaderAsAddressList(const char *header, const KMime::Content *message) const { /* works without this is maybe faster ? if(strcmp(header, "to") == 0) { return message->to(); } else if(strcmp(header, "replyTo") == 0) { return message->replyTo(); } else if(strcmp(header, "bcc") == 0) { return message->bcc(); } else if(strcmp(header, "cc") == 0) { return message->cc(); } */ auto addressList = new KMime::Headers::Generics::AddressList(); - const auto hrd = message->headerByType(header); + const auto hrd = mailHeaderAsBase(header, message); const QByteArray &data = hrd->as7BitString(false); addressList->from7BitString(data); return addressList; } -QDateTime NodeHelper::dateHeader(KMime::Message *message) const +void NodeHelper::clearOverrideHeaders() +{ + mHeaderOverwrite.clear(); +} + +void NodeHelper::registerOverrideHeader(KMime::Content *message, MessagePart::Ptr part) { - return message->date()->dateTime(); + if (!mHeaderOverwrite.contains(message)) { + mHeaderOverwrite[message] = QVector(); + } + mHeaderOverwrite[message].append(part); +} + +QDateTime NodeHelper::dateHeader(KMime::Content *message) const +{ + const auto dateHeader = mailHeaderAsBase("date", message); + if (dateHeader != nullptr) { + return static_cast(dateHeader)->dateTime(); + } + return QDateTime(); } void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec) { if (!node) { return; } mOverrideCodecs[node] = codec; } const QTextCodec *NodeHelper::codec(KMime::Content *node) { if (!node) { return mLocalCodec; } const QTextCodec *c = mOverrideCodecs.value(node, nullptr); if (!c) { // no override-codec set for this message, try the CT charset parameter: QByteArray charset = node->contentType()->charset(); // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients. if (charset.toLower() == "us-ascii") { charset = "utf-8"; } c = codecForName(charset); } if (!c) { // no charset means us-ascii (RFC 2045), so using local encoding should // be okay c = mLocalCodec; } return c; } const QTextCodec *NodeHelper::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } QString NodeHelper::fileName(const KMime::Content *node) { QString name = const_cast(node)->contentDisposition()->filename(); if (name.isEmpty()) { name = const_cast(node)->contentType()->name(); } name = name.trimmed(); return name; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const { const QMap< QString, QMap >::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node)); if (nit == mBodyPartMementoMap.end()) { return nullptr; } const QMap::const_iterator it = nit->find(which.toLower()); return it != nit->end() ? it.value() : nullptr; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento) { QMap &mementos = mBodyPartMementoMap[persistentIndex(node)]; const QByteArray whichLower = which.toLower(); const QMap::iterator it = mementos.lowerBound(whichLower); if (it != mementos.end() && it.key() == whichLower) { delete it.value(); if (memento) { it.value() = memento; } else { mementos.erase(it); } } else { mementos.insert(whichLower, memento); } } bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const { qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node); return mDisplayEmbeddedNodes.contains(node); } void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded) { qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded; if (displayedEmbedded) { mDisplayEmbeddedNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const { return mDisplayHiddenNodes.contains(node); } void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden) { if (displayedHidden) { mDisplayHiddenNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } /*! Creates a persistent index string that bridges the gap between the permanent nodes and the temporary ones. Used internally for robust indexing. */ QString NodeHelper::persistentIndex(const KMime::Content *node) const { if (!node) { return QString(); } QString indexStr = node->index().toString(); if (indexStr.isEmpty()) { QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const auto &extraNodes = it.value(); for (int i = 0; i < extraNodes.size(); i++) { if (extraNodes[i] == node) { indexStr = QStringLiteral("e%1").arg(i); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } else { const KMime::Content *const topLevel = node->topLevel(); //if the node is an extra node, prepend the index of the extra node to the url QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const QList &extraNodes = extraContents(it.key()); for (int i = 0; i < extraNodes.size(); ++i) { KMime::Content *const extraNode = extraNodes[i]; if (topLevel == extraNode) { indexStr.prepend(QStringLiteral("e%1:").arg(i)); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } return indexStr; } KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const { KMime::Content *c = node->topLevel(); if (c) { const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts); const int pathPartsSize(pathParts.size()); for (int i = 0; i < pathPartsSize; ++i) { const QString &path = pathParts[i]; if (path.startsWith(QLatin1Char('e'))) { const QList &extraParts = mExtraContents.value(c); const int idx = path.midRef(1, -1).toInt(); c = (idx < extraParts.size()) ? extraParts[idx] : nullptr; } else { c = c->content(KMime::ContentIndex(path)); } if (!c) { break; } } } return c; } QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const { return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place); } KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const { if (url.isEmpty()) { return mMessage.data(); } if (!url.isLocalFile()) { return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path()); } else { const QString path = url.toLocalFile(); // extract from //qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2" // start of the index is something that is not a number followed by a dot: \D. // index is only made of numbers,"." and ":": ([0-9.:]+) // index is the last part of the folder name: / const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/")); //search the occurrence at most at the end if (rIndex.lastIndexIn(path) != -1) { return contentFromIndex(mMessage.data(), rIndex.cap(1)); } return mMessage.data(); } } QString NodeHelper::fixEncoding(const QString &encoding) { QString returnEncoding = encoding; // According to http://www.iana.org/assignments/character-sets, uppercase is // preferred in MIME headers const QString returnEncodingToUpper = returnEncoding.toUpper(); if (returnEncodingToUpper.contains(QLatin1String("ISO "))) { returnEncoding = returnEncodingToUpper; returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-")); } return returnEncoding; } //----------------------------------------------------------------------------- QString NodeHelper::encodingForName(const QString &descriptiveName) { QString encoding = KCharsets::charsets()->encodingForName(descriptiveName); return NodeHelper::fixEncoding(encoding); } QStringList NodeHelper::supportedEncodings(bool usAscii) { QStringList encodingNames = KCharsets::charsets()->availableEncodingNames(); QStringList encodings; QMap mimeNames; QStringList::ConstIterator constEnd(encodingNames.constEnd()); for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) { QTextCodec *codec = KCharsets::charsets()->codecForName(*it); QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it); if (!mimeNames.contains(mimeName)) { encodings.append(KCharsets::charsets()->descriptionForEncoding(*it)); mimeNames.insert(mimeName, true); } } encodings.sort(); if (usAscii) { encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii"))); } return encodings; } QString NodeHelper::fromAsString(KMime::Content *node) const { if (auto topLevel = dynamic_cast(node->topLevel())) { return topLevel->from()->asUnicodeString(); } else { auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QList &nodes) { return nodes.contains(node); }); if (realNode != mExtraContents.cend()) { return fromAsString(realNode.key()); } } return QString(); } void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content) { qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content; mExtraContents[topLevelNode].append(content); } void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode) { qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode; mExtraContents[topLevelNode].clear(); } QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const { return mExtraContents.value(topLevelnode); } void NodeHelper::mergeExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { if (node->bodyIsMessage()) { qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node <encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent(); continue; } KMime::Content *c = new KMime::Content(node); c->setContent(extra->encodedContent()); c->parse(); node->addContent(c); } Q_FOREACH (KMime::Content *child, node->contents()) { mergeExtraNodes(child); } } void NodeHelper::cleanFromExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { QByteArray s = extra->encodedContent(); const auto children = node->contents(); for (KMime::Content *c : children) { if (c->encodedContent() == s) { node->removeContent(c); } } } Q_FOREACH (KMime::Content *child, node->contents()) { cleanFromExtraNodes(child); } } KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode) { /*The merge is done in several steps: 1) merge the extra nodes into topLevelNode 2) copy the modified (merged) node tree into a new node tree 3) restore the original node tree in topLevelNode by removing the extra nodes from it The reason is that extra nodes are assigned by pointer value to the nodes in the original tree. */ if (!topLevelNode) { return nullptr; } mergeExtraNodes(topLevelNode); KMime::Message *m = new KMime::Message; m->setContent(topLevelNode->encodedContent()); m->parse(); cleanFromExtraNodes(topLevelNode); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent(); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent(); return m; } KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const { const QList xc = extraContents(content); if (!xc.empty()) { if (xc.size() == 1) { return xc.front(); } else { qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?"; } } return nullptr; } bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel) { bool returnValue = false; if (node) { KMime::Content *curNode = node; KMime::Content *decryptedNode = nullptr; const QByteArray type = node->contentType(false) ? QByteArray(node->contentType()->mediaType()).toLower() : "text"; const QByteArray subType = node->contentType(false) ? node->contentType()->subType().toLower() : "plain"; const bool isMultipart = node->contentType(false) && node->contentType()->isMultipart(); bool isSignature = false; qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType; if (isMultipart) { if (subType == "signed") { isSignature = true; } else if (subType == "encrypted") { decryptedNode = decryptedNodeForContent(curNode); } } else if (type == "application") { if (subType == "octet-stream") { decryptedNode = decryptedNodeForContent(curNode); } else if (subType == "pkcs7-signature") { isSignature = true; } else if (subType == "pkcs7-mime") { // note: subtype pkcs7-mime can also be signed // and we do NOT want to remove the signature! if (encryptionState(curNode) != KMMsgNotEncrypted) { decryptedNode = decryptedNodeForContent(curNode); } } } if (decryptedNode) { qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header " "and then processing the children."; Q_ASSERT(addHeaders); KMime::Content headers; headers.setHead(curNode->head()); headers.parse(); if (decryptedNode->contentType(false)) { headers.contentType()->from7BitString(decryptedNode->contentType()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentTransferEncoding(false)) { headers.contentTransferEncoding()->from7BitString(decryptedNode->contentTransferEncoding()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDisposition(false)) { headers.contentDisposition()->from7BitString(decryptedNode->contentDisposition()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDescription(false)) { headers.contentDescription()->from7BitString(decryptedNode->contentDescription()->as7BitString(false)); } else { headers.removeHeader(); } headers.assemble(); resultingData += headers.head() + '\n'; unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1); returnValue = true; } else if (isSignature) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is."; // We can't change the nodes under the signature, as that would invalidate it. Add the signature // and its child as-is if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->encodedBody(); returnValue = false; } else if (isMultipart) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children."; // Normal multipart node, add the header and all of its children bool somethingChanged = false; if (addHeaders) { resultingData += curNode->head() + '\n'; } const QByteArray boundary = curNode->contentType()->boundary(); foreach (KMime::Content *child, curNode->contents()) { resultingData += "\n--" + boundary + '\n'; const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1); if (changed) { somethingChanged = true; } } resultingData += "\n--" + boundary + "--\n\n"; returnValue = somethingChanged; } else if (curNode->bodyIsMessage()) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child."; if (addHeaders) { resultingData += curNode->head() + '\n'; } returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1); } else { qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is."; if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->body(); returnValue = false; } } qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done."; return returnValue; } KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage) { QByteArray resultingData; const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true); if (messageChanged) { #if 0 qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData; QFile bla("stripped.mbox"); bla.open(QIODevice::WriteOnly); bla.write(resultingData); bla.close(); #endif KMime::Message::Ptr newMessage(new KMime::Message); newMessage->setContent(resultingData); newMessage->parse(); return newMessage; } else { return KMime::Message::Ptr(); } } QVector NodeHelper::attachmentsOfExtraContents() const { QVector result; for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) { foreach (auto content, it.value()) { if (KMime::isAttachment(content)) { result.push_back(content); } else { result += content->attachments(); } } } return result; } } diff --git a/mimetreeparser/src/nodehelper.h b/mimetreeparser/src/nodehelper.h index 3327e3ef..273f91c9 100644 --- a/mimetreeparser/src/nodehelper.h +++ b/mimetreeparser/src/nodehelper.h @@ -1,267 +1,273 @@ /* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIMETREEPARSER_NODEHELPER_H #define MIMETREEPARSER_NODEHELPER_H #include "mimetreeparser_export.h" #include "mimetreeparser/partmetadata.h" #include "mimetreeparser/enums.h" #include +#include #include #include #include class QUrl; class QTextCodec; namespace MimeTreeParser { class AttachmentTemporaryFilesDirs; +class MessagePart; +typedef QSharedPointer MessagePartPtr; namespace Interface { class BodyPartMemento; } } namespace MimeTreeParser { /** * @author Andras Mantia */ class MIMETREEPARSER_EXPORT NodeHelper : public QObject { Q_OBJECT public: NodeHelper(); ~NodeHelper(); void setNodeProcessed(KMime::Content *node, bool recurse); void setNodeUnprocessed(KMime::Content *node, bool recurse); bool nodeProcessed(KMime::Content *node) const; void clear(); void forceCleanTempFiles(); void setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state); KMMsgEncryptionState encryptionState(const KMime::Content *node) const; void setSignatureState(const KMime::Content *node, const KMMsgSignatureState state); KMMsgSignatureState signatureState(const KMime::Content *node) const; KMMsgSignatureState overallSignatureState(KMime::Content *node) const; KMMsgEncryptionState overallEncryptionState(KMime::Content *node) const; void setPartMetaData(KMime::Content *node, const PartMetaData &metaData); PartMetaData partMetaData(KMime::Content *node); /** * Set the 'Content-Type' by mime-magic from the contents of the body. * If autoDecode is true the decoded body will be used for mime type * determination (this does not change the body itself). */ static void magicSetType(KMime::Content *node, bool autoDecode = true); - bool hasMailHeader(const char *header, const KMime::Message *message) const; - KMime::Headers::Base *mailHeaderAsBase(const char *header, const KMime::Message *message) const; - KMime::Headers::Generics::AddressList *mailHeaderAsAddressList(const char *header, KMime::Message *message) const; - QDateTime dateHeader(KMime::Message *message) const; + void clearOverrideHeaders(); + void registerOverrideHeader(KMime::Content *message, MessagePartPtr); + bool hasMailHeader(const char *header, const KMime::Content *message) const; + KMime::Headers::Base const *mailHeaderAsBase(const char *header, const KMime::Content *message) const; + KMime::Headers::Generics::AddressList const *mailHeaderAsAddressList(const char *header, const KMime::Content *message) const; + QDateTime dateHeader(KMime::Content *message) const; /** Attach an extra node to an existing node */ void attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content); void cleanExtraContent(KMime::Content *topLevelNode); /** Get the extra nodes attached to the @param topLevelNode and all sub-nodes of @param topLevelNode */ QList extraContents(KMime::Content *topLevelNode) const; /** Return a modified message (node tree) starting from @param topLevelNode that has the original nodes and the extra nodes. The caller has the responsibility to delete the new message. */ KMime::Message *messageWithExtraContent(KMime::Content *topLevelNode); /** Get a QTextCodec suitable for this message part */ const QTextCodec *codec(KMime::Content *node); /** Set the charset the user selected for the message to display */ void setOverrideCodec(KMime::Content *node, const QTextCodec *codec); Interface::BodyPartMemento *bodyPartMemento(KMime::Content *node, const QByteArray &which) const; void setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento); // A flag to remember if the node was embedded. This is useful for attachment nodes, the reader // needs to know if they were displayed inline or not. bool isNodeDisplayedEmbedded(KMime::Content *node) const; void setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded); // Same as above, but this time determines if the node was hidden or not bool isNodeDisplayedHidden(KMime::Content *node) const; void setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden); /** * Writes the given message part to a temporary file and returns the * name of this file or QString() if writing failed. */ QString writeNodeToTempFile(KMime::Content *node); QString writeFileToTempFile(KMime::Content *node, const QString &filename); /** * Returns the temporary file path and name where this node was saved, or an empty url * if it wasn't saved yet with writeNodeToTempFile() */ QUrl tempFileUrlFromNode(const KMime::Content *node); /** * Creates a temporary dir for saving attachments, etc. * Will be automatically deleted when another message is viewed. * @param param Optional part of the directory name. */ QString createTempDir(const QString ¶m = QString()); /** * Cleanup the attachment temp files */ void removeTempFiles(); /** * Add a file to the list of managed temporary files */ void addTempFile(const QString &file); // Get a href in the form attachment:?place=, used by ObjectTreeParser and // UrlHandlerManager. QString asHREF(const KMime::Content *node, const QString &place) const; KMime::Content *fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &href) const; /** * @return true if this node is a child or an encapsulated message */ static bool isInEncapsulatedMessage(KMime::Content *node); /** * Returns the charset for the given node. If no charset is specified * for the node, the defaultCharset() is returned. */ static QByteArray charset(KMime::Content *node); /** * Return a QTextCodec for the specified charset. * This function is a bit more tolerant, than QTextCodec::codecForName */ static const QTextCodec *codecForName(const QByteArray &_str); /** * Returns a usable filename for a node, that can be the filename from the * content disposition header, or if that one is empty, the name from the * content type header. */ static QString fileName(const KMime::Content *node); /** * Fixes an encoding received by a KDE function and returns the proper, * MIME-compliant encoding name instead. * @see encodingForName */ static QString fixEncoding(const QString &encoding); //TODO(Andras) move to a utility class? /** * Drop-in replacement for KCharsets::encodingForName(). The problem with * the KCharsets function is that it returns "human-readable" encoding names * like "ISO 8859-15" instead of valid encoding names like "ISO-8859-15". * This function fixes this by replacing whitespace with a hyphen. */ static QString encodingForName(const QString &descriptiveName); //TODO(Andras) move to a utility class? /** * Return a list of the supported encodings * @param usAscii if true, US-Ascii encoding will be prepended to the list. */ static QStringList supportedEncodings(bool usAscii); //TODO(Andras) move to a utility class? QString fromAsString(KMime::Content *node) const; KMime::Content *decryptedNodeForContent(KMime::Content *content) const; /** * This function returns the unencrypted message that is based on @p originalMessage. * All encrypted MIME parts are removed and replaced by their decrypted plain-text versions. * Encrypted parts that are within signed parts are not replaced, since that would invalidate * the signature. * * This only works if the message was run through ObjectTreeParser::parseObjectTree() with the * current NodeHelper before, because parseObjectTree() actually decrypts the message and stores * the decrypted nodes by calling attachExtraContent(). * * @return the unencrypted message or an invalid pointer if the original message didn't contain * a part that needed to be modified. */ KMime::Message::Ptr unencryptedMessage(const KMime::Message::Ptr &originalMessage); /** * Returns a list of attachments of attached extra content nodes. * This is mainly useful is order to get attachments of encrypted messages. * Note that this does not include attachments from the primary node tree. * @see KMime::Content::attachments(). */ QVector attachmentsOfExtraContents() const; Q_SIGNALS: void update(MimeTreeParser::UpdateMode); private: Q_DISABLE_COPY(NodeHelper) bool unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel = 1); void mergeExtraNodes(KMime::Content *node); void cleanFromExtraNodes(KMime::Content *node); /** Creates a persistent index string that bridges the gap between the permanent nodes and the temporary ones. Used internally for robust indexing. **/ QString persistentIndex(const KMime::Content *node) const; /** Translates the persistentIndex into a node back node: any node of the actually message to what the persistentIndex is interpreded **/ KMime::Content *contentFromIndex(KMime::Content *node, const QString &persistentIndex) const; private: QList mProcessedNodes; QList mNodesUnderProcess; QMap mEncryptionState; QMap mSignatureState; QSet mDisplayEmbeddedNodes; QSet mDisplayHiddenNodes; QTextCodec *mLocalCodec = nullptr; QMap mOverrideCodecs; QMap > mBodyPartMementoMap; QMap mPartMetaDatas; QMap > mExtraContents; AttachmentTemporaryFilesDirs *mAttachmentFilesDir = nullptr; + QMap> mHeaderOverwrite; friend class NodeHelperTest; }; } #endif diff --git a/mimetreeparser/src/objecttreeparser.cpp b/mimetreeparser/src/objecttreeparser.cpp index 05ce9d9e..fef19d5c 100644 --- a/mimetreeparser/src/objecttreeparser.cpp +++ b/mimetreeparser/src/objecttreeparser.cpp @@ -1,330 +1,332 @@ /* objecttreeparser.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2015 Sandro Knauß KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ // MessageViewer includes #include "objecttreeparser.h" #include "bodypartformatterfactory.h" #include "nodehelper.h" #include "messagepart.h" #include "partnodebodypart.h" #include "mimetreeparser_debug.h" #include "bodyformatter/utils.h" #include "interfaces/bodypartformatter.h" #include "utils/util.h" #include #include // KDE includes // Qt includes #include #include using namespace MimeTreeParser; ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser) : mSource(topLevelParser->mSource) , mNodeHelper(topLevelParser->mNodeHelper) , mTopLevelContent(topLevelParser->mTopLevelContent) , mHasPendingAsyncJobs(false) , mAllowAsync(topLevelParser->mAllowAsync) { init(); } ObjectTreeParser::ObjectTreeParser(Interface::ObjectTreeSource *source, MimeTreeParser::NodeHelper *nodeHelper) : mSource(source) , mNodeHelper(nodeHelper) , mTopLevelContent(nullptr) , mHasPendingAsyncJobs(false) , mAllowAsync(false) { init(); } void ObjectTreeParser::init() { Q_ASSERT(mSource); if (!mNodeHelper) { mNodeHelper = new NodeHelper(); mDeleteNodeHelper = true; } else { mDeleteNodeHelper = false; } } ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other) : mSource(other.mSource) , mNodeHelper(other.nodeHelper()) , mTopLevelContent(other.mTopLevelContent) , mHasPendingAsyncJobs(other.hasPendingAsyncJobs()) , mAllowAsync(other.allowAsync()) , mDeleteNodeHelper(false) { } ObjectTreeParser::~ObjectTreeParser() { if (mDeleteNodeHelper) { delete mNodeHelper; mNodeHelper = nullptr; } } void ObjectTreeParser::setAllowAsync(bool allow) { Q_ASSERT(!mHasPendingAsyncJobs); mAllowAsync = allow; } bool ObjectTreeParser::allowAsync() const { return mAllowAsync; } bool ObjectTreeParser::hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } QString ObjectTreeParser::plainTextContent() const { return mPlainTextContent; } QString ObjectTreeParser::htmlContent() const { return mHtmlContent; } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree(KMime::Content *node, bool parseOnlySingleNode) { mTopLevelContent = node; mParsedPart = parseObjectTreeInternal(node, parseOnlySingleNode); if (mParsedPart) { mParsedPart->fix(); if (auto mp = toplevelTextNode(mParsedPart)) { if (auto _mp = mp.dynamicCast()) { extractNodeInfos(_mp->content(), true); } else if (auto _mp = mp.dynamicCast()) { if (_mp->childParts().contains(Util::MultipartPlain)) { extractNodeInfos(_mp->childParts()[Util::MultipartPlain]->content(), true); } } setPlainTextContent(mp->text()); } mSource->render(mParsedPart, parseOnlySingleNode); } } MessagePartPtr ObjectTreeParser::parsedPart() const { return mParsedPart; } MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mimeType) { const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(QString::fromUtf8(mimeType)); Q_ASSERT(!formatters.empty()); for (auto formatter : formatters) { PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper); mNodeHelper->setNodeDisplayedEmbedded(node, true); const MessagePart::Ptr result = formatter->process(part); if (!result) { continue; } result->setAttachmentContent(node); return result; } Q_UNREACHABLE(); return {}; } MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart) { if (!node) { return MessagePart::Ptr(); } // reset pending async jobs state (we'll rediscover pending jobs as we go) mHasPendingAsyncJobs = false; + mNodeHelper->clearOverrideHeaders(); + // reset "processed" flags for... if (onlyOneMimePart) { // ... this node and all descendants mNodeHelper->setNodeUnprocessed(node, false); if (!node->contents().isEmpty()) { mNodeHelper->setNodeUnprocessed(node, true); } } else if (!node->parent()) { // ...this node and all it's siblings and descendants mNodeHelper->setNodeUnprocessed(node, true); } const bool isRoot = node->isTopLevel(); auto parsedPart = MessagePart::Ptr(new MessagePartList(this)); parsedPart->setIsRoot(isRoot); KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(node); if (i < 0) { return parsedPart; } else { for (; i < contents.size(); ++i) { node = contents.at(i); if (mNodeHelper->nodeProcessed(node)) { continue; } ProcessResult processResult(mNodeHelper); QByteArray mimeType("text/plain"); if (node->contentType(false) && !node->contentType()->mimeType().isEmpty()) { mimeType = node->contentType()->mimeType(); } // unfortunately there's many emails where we can't trust the attachment mimetype // so try to see if we can find something better if (mimeType == "application/octet-stream") { NodeHelper::magicSetType(node); mimeType = node->contentType()->mimeType(); } const auto mp = processType(node, processResult, mimeType); Q_ASSERT(mp); parsedPart->appendSubPart(mp); mNodeHelper->setNodeProcessed(node, false); // adjust signed/encrypted flags if inline PGP was found processResult.adjustCryptoStatesOfNode(node); if (onlyOneMimePart) { break; } } } return parsedPart; } KMMsgSignatureState ProcessResult::inlineSignatureState() const { return mInlineSignatureState; } void ProcessResult::setInlineSignatureState(KMMsgSignatureState state) { mInlineSignatureState = state; } KMMsgEncryptionState ProcessResult::inlineEncryptionState() const { return mInlineEncryptionState; } void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state) { mInlineEncryptionState = state; } bool ProcessResult::neverDisplayInline() const { return mNeverDisplayInline; } void ProcessResult::setNeverDisplayInline(bool display) { mNeverDisplayInline = display; } void ProcessResult::adjustCryptoStatesOfNode(const KMime::Content *node) const { if ((inlineSignatureState() != KMMsgNotSigned) || (inlineEncryptionState() != KMMsgNotEncrypted)) { mNodeHelper->setSignatureState(node, inlineSignatureState()); mNodeHelper->setEncryptionState(node, inlineEncryptionState()); } } void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart) { if (isFirstTextPart) { mPlainTextContent += curNode->decodedText(); mPlainTextContentCharset += NodeHelper::charset(curNode); } } void ObjectTreeParser::setPlainTextContent(const QString &plainTextContent) { mPlainTextContent = plainTextContent; } const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const { Q_ASSERT(node); if (mSource->overrideCodec()) { return mSource->overrideCodec(); } return mNodeHelper->codec(node); } QByteArray ObjectTreeParser::plainTextContentCharset() const { return mPlainTextContentCharset; } QByteArray ObjectTreeParser::htmlContentCharset() const { return mHtmlContentCharset; } MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const { return mNodeHelper; }