diff --git a/messagecomposer/autotests/cryptocomposertest.cpp b/messagecomposer/autotests/cryptocomposertest.cpp index 1a290067..c6e71ef4 100644 --- a/messagecomposer/autotests/cryptocomposertest.cpp +++ b/messagecomposer/autotests/cryptocomposertest.cpp @@ -1,608 +1,608 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (c) 2009 Leo Franchi 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 "cryptocomposertest.h" #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include "setupenv.h" #include #include using namespace KMime; #include #include #include #include #include #include #include #include #include using namespace MessageComposer; #include #include using namespace MessageCore; #include #include #include #include #include Q_DECLARE_METATYPE(MessageCore::AttachmentPart) QTEST_MAIN(CryptoComposerTest) void CryptoComposerTest::initTestCase() { MessageComposer::Test::setupEnv(); } Q_DECLARE_METATYPE(Headers::contentEncoding) // openpgp void CryptoComposerTest::testOpenPGPMime_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); QTest::newRow("SignOpenPGPMime") << data << true << false << Headers::CE7Bit; QTest::newRow("EncryptOpenPGPMime") << data << false << true << Headers::CE7Bit; QTest::newRow("SignEncryptOpenPGPMime") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testOpenPGPMime() { QFETCH(QString, data); QFETCH(bool, sign); QFETCH(bool, encrypt); QFETCH(Headers::contentEncoding, cte); Composer *composer = new Composer; fillComposerData(composer, data); fillComposerCryptoData(composer); composer->setSignAndEncrypt(sign, encrypt); composer->setMessageCryptoFormat(Kleo::OpenPGPMIMEFormat); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; //qDebug()<< "message:" << message.data()->encodedContent(); ComposerTestUtil::verify(sign, encrypt, message.data(), data.toUtf8(), Kleo::OpenPGPMIMEFormat, cte); QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); } // the following will do for s-mime as well, as the same sign/enc jobs are used void CryptoComposerTest::testEncryptSameAttachments_data() { QTest::addColumn("format"); QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat; //TODO: fix Inline PGP with encrypted attachments //QTest::newRow( "InlineOpenPGP" ) << (int) Kleo::InlineOpenPGPFormat; } void CryptoComposerTest::testEncryptSameAttachments() { QFETCH(int, format); Composer *composer = new Composer; - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); fillComposerData(composer, data); fillComposerCryptoData(composer); AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart); attachment->setData("abc"); attachment->setMimeType("x-some/x-type"); attachment->setFileName(QString::fromLocal8Bit("anattachment.txt")); attachment->setEncrypted(true); attachment->setSigned(false); composer->addAttachmentPart(attachment); composer->setSignAndEncrypt(false, true); composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; //qDebug()<< "message:" << message.data()->encodedContent(); ComposerTestUtil::verifyEncryption(message.data(), data.toUtf8(), (Kleo::CryptoMessageFormat)format, true); QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); MimeTreeParser::SimpleObjectTreeSource testSource; testSource.setDecryptMessage(true); MimeTreeParser::NodeHelper *nh = new MimeTreeParser::NodeHelper; MimeTreeParser::ObjectTreeParser otp(&testSource, nh); otp.parseObjectTree(message.data()); KMime::Message::Ptr unencrypted = nh->unencryptedMessage(message); KMime::Content *testAttachment = Util::findTypeInMessage(unencrypted.data(), "x-some", "x-type"); QCOMPARE(testAttachment->body(), QString::fromLatin1("abc").toUtf8()); QCOMPARE(testAttachment->contentDisposition()->filename(), QString::fromLatin1("anattachment.txt")); } void CryptoComposerTest::testEditEncryptAttachments_data() { QTest::addColumn("format"); QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat; //TODO: SMIME should also be tested } void CryptoComposerTest::testEditEncryptAttachments() { QFETCH(int, format); Composer *composer = new Composer; QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); fillComposerData(composer, data); fillComposerCryptoData(composer); AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart); const QString fileName = QStringLiteral("anattachment.txt"); const QByteArray fileData = "abc"; attachment->setData(fileData); attachment->setMimeType("x-some/x-type"); attachment->setFileName(fileName); attachment->setEncrypted(true); attachment->setSigned(false); composer->addAttachmentPart(attachment); composer->setSignAndEncrypt(false, true); composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; // setup a viewer ComposerViewBase view(this, nullptr); AttachmentModel model(this); AttachmentControllerBase controller(&model, nullptr, nullptr); MessageComposer::RichTextComposerNg editor; view.setAttachmentModel(&model); view.setAttachmentController(&controller); view.setEditor(&editor); // Let's load the email to the viewer view.setMessage(message, true); QModelIndex index = model.index(0, 0); QCOMPARE(editor.toPlainText(), data); QCOMPARE(model.rowCount(), 1); QCOMPARE(model.data(index, AttachmentModel::NameRole).toString(), fileName); AttachmentPart::Ptr part = model.attachments()[0]; QCOMPARE(part->data(), fileData); QCOMPARE(part->fileName(), fileName); } void CryptoComposerTest::testEditEncryptAndLateAttachments_data() { QTest::addColumn("format"); QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat; //TODO: SMIME should also be tested } void CryptoComposerTest::testEditEncryptAndLateAttachments() { QFETCH(int, format); Composer *composer = new Composer; QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); fillComposerData(composer, data); fillComposerCryptoData(composer); AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart); const QString fileName = QStringLiteral("anattachment.txt"); const QByteArray fileData = "abc"; const QString fileName2 = QStringLiteral("nonencrypt.txt"); const QByteArray fileData2 = "readable"; attachment->setData(fileData); attachment->setMimeType("x-some/x-type"); attachment->setFileName(fileName); attachment->setEncrypted(true); attachment->setSigned(false); composer->addAttachmentPart(attachment); attachment = AttachmentPart::Ptr(new AttachmentPart); attachment->setData(fileData2); attachment->setMimeType("x-some/x-type2"); attachment->setFileName(fileName2); attachment->setEncrypted(false); attachment->setSigned(false); composer->addAttachmentPart(attachment); composer->setSignAndEncrypt(false, true); composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; // setup a viewer ComposerViewBase view(this, nullptr); AttachmentModel model(this); AttachmentControllerBase controller(&model, nullptr, nullptr); MessageComposer::RichTextComposerNg editor; view.setAttachmentModel(&model); view.setAttachmentController(&controller); view.setEditor(&editor); // Let's load the email to the viewer view.setMessage(message, true); //QModelIndex index = model.index(0, 0); QCOMPARE(editor.toPlainText(), data); QCOMPARE(model.rowCount(), 2); AttachmentPart::Ptr part = model.attachments()[0]; QCOMPARE(part->fileName(), fileName); QCOMPARE(part->data(), fileData); part = model.attachments()[1]; QCOMPARE(part->fileName(), fileName2); QCOMPARE(part->data(), fileData2); } void CryptoComposerTest::testSignEncryptLateAttachments_data() { QTest::addColumn("format"); QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat; QTest::newRow("InlineOpenPGP") << (int)Kleo::InlineOpenPGPFormat; } void CryptoComposerTest::testSignEncryptLateAttachments() { QFETCH(int, format); Composer *composer = new Composer; - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); fillComposerData(composer, data); fillComposerCryptoData(composer); AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart); attachment->setData("abc"); attachment->setMimeType("x-some/x-type"); attachment->setFileName(QString::fromLocal8Bit("anattachment.txt")); attachment->setEncrypted(false); attachment->setSigned(false); composer->addAttachmentPart(attachment); composer->setSignAndEncrypt(true, true); composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; // as we have an additional attachment, just ignore it when checking for sign/encrypt KMime::Content *b = MessageCore::NodeHelper::firstChild(message.data()); ComposerTestUtil::verifySignatureAndEncryption(b, data.toUtf8(), (Kleo::CryptoMessageFormat)format, true); QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); // now check the attachment separately QCOMPARE(QString::fromLatin1(MessageCore::NodeHelper::nextSibling(b)->body()), QString::fromLatin1("abc")); } void CryptoComposerTest::testBCCEncrypt_data() { QTest::addColumn("format"); QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat; QTest::newRow("InlineOpenPGP") << (int)Kleo::InlineOpenPGPFormat; } // secondary recipients void CryptoComposerTest::testBCCEncrypt() { QFETCH(int, format); Composer *composer = new Composer; - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); fillComposerData(composer, data); - composer->infoPart()->setBcc(QStringList(QString::fromLatin1("bcc@bcc.org"))); + composer->infoPart()->setBcc(QStringList(QLatin1String("bcc@bcc.org"))); std::vector keys = MessageComposer::Test::getKeys(); QStringList primRecipients; primRecipients << QString::fromLocal8Bit("you@you.you"); std::vector< GpgME::Key > pkeys; pkeys.push_back(keys[1]); QStringList secondRecipients; secondRecipients << QString::fromLocal8Bit("bcc@bcc.org"); std::vector< GpgME::Key > skeys; skeys.push_back(keys[2]); QList > > encKeys; encKeys.append(QPair >(primRecipients, pkeys)); encKeys.append(QPair >(secondRecipients, skeys)); composer->setSignAndEncrypt(true, true); composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format); composer->setEncryptionKeys(encKeys); composer->setSigningKeys(keys); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 2); KMime::Message::Ptr primMessage = composer->resultMessages().first(); KMime::Message::Ptr secMessage = composer->resultMessages()[1]; delete composer; composer = nullptr; ComposerTestUtil::verifySignatureAndEncryption(primMessage.data(), data.toUtf8(), (Kleo::CryptoMessageFormat)format); QCOMPARE(primMessage->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(primMessage->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); ComposerTestUtil::verifySignatureAndEncryption(secMessage.data(), data.toUtf8(), (Kleo::CryptoMessageFormat)format); QCOMPARE(secMessage->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(secMessage->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); } // inline pgp void CryptoComposerTest::testOpenPGPInline_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); QTest::newRow("SignOpenPGPInline") << data << true << false << Headers::CE7Bit; QTest::newRow("EncryptOpenPGPInline") << data << false << true << Headers::CE7Bit; QTest::newRow("SignEncryptOpenPGPInline") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testOpenPGPInline() { QFETCH(QString, data); QFETCH(bool, sign); QFETCH(bool, encrypt); QFETCH(Headers::contentEncoding, cte); Composer *composer = new Composer; fillComposerData(composer, data); fillComposerCryptoData(composer); composer->setSignAndEncrypt(sign, encrypt); composer->setMessageCryptoFormat(Kleo::InlineOpenPGPFormat); VERIFYEXEC(composer); QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; if (sign && !encrypt) { - data += QString::fromLatin1("\n"); + data += QLatin1String("\n"); } //qDebug() << "message:" << message->encodedContent(); ComposerTestUtil::verify(sign, encrypt, message.data(), data.toUtf8(), Kleo::InlineOpenPGPFormat, cte); QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me")); QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); } // s-mime void CryptoComposerTest::testSMIME_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); QTest::newRow("SignSMIME") << data << true << false << Headers::CE7Bit; QTest::newRow("EncryptSMIME") << data << false << true << Headers::CE7Bit; QTest::newRow("SignEncryptSMIME") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testSMIME() { QFETCH(bool, sign); QFETCH(bool, encrypt); runSMIMETest(sign, encrypt, false); } void CryptoComposerTest::testSMIMEOpaque_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); - QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); QTest::newRow("SignSMIMEOpaque") << data << true << false << Headers::CE7Bit; QTest::newRow("EncryptSMIMEOpaque") << data << false << true << Headers::CE7Bit; QTest::newRow("SignEncryptSMIMEOpaque") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testSMIMEOpaque() { QFETCH(bool, sign); QFETCH(bool, encrypt); runSMIMETest(sign, encrypt, true); } // contentTransferEncoding void CryptoComposerTest::testCTEquPr_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way. [ä]")); QTest::newRow("CTEquPr:Sign") << data << true << false << Headers::CEquPr; QTest::newRow("CTEquPr:Encrypt") << data << false << true << Headers::CE7Bit; QTest::newRow("CTEquPr:SignEncrypt") << data << true << true << Headers::CE7Bit; data = QStringLiteral( "All happy families are alike;\n\n\n\neach unhappy family is unhappy in its own way.\n--\n hallloasdfasdfsadfsdf asdf sadfasdf sdf sdf sdf sadfasdf sdaf daf sdf asdf sadf asdf asdf [ä]"); QTest::newRow("CTEquPr:Sign:Newline") << data << true << false << Headers::CEquPr; QTest::newRow("CTEquPr:Encrypt:Newline") << data << false << true << Headers::CE7Bit; QTest::newRow("CTEquPr:SignEncrypt:Newline") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testCTEquPr() { testSMIME(); testSMIMEOpaque(); testOpenPGPMime(); testOpenPGPInline(); } void CryptoComposerTest::testCTEbase64_data() { QTest::addColumn("data"); QTest::addColumn("sign"); QTest::addColumn("encrypt"); QTest::addColumn("cte"); QString data(QStringLiteral( "[ääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääää]")); QTest::newRow("CTEbase64:Sign") << data << true << false << Headers::CEbase64; QTest::newRow("CTEbase64:Encrypt") << data << false << true << Headers::CE7Bit; QTest::newRow("CTEbase64:SignEncrypt") << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testCTEbase64() { testSMIME(); testSMIMEOpaque(); testOpenPGPMime(); testOpenPGPInline(); } // Helper methods -void CryptoComposerTest::fillComposerData(Composer *composer, QString data) +void CryptoComposerTest::fillComposerData(Composer *composer, const QString &data) { composer->globalPart()->setFallbackCharsetEnabled(true); - composer->infoPart()->setFrom(QString::fromLatin1("me@me.me")); - composer->infoPart()->setTo(QStringList(QString::fromLatin1("you@you.you"))); + composer->infoPart()->setFrom(QStringLiteral("me@me.me")); + composer->infoPart()->setTo(QStringList(QLatin1String("you@you.you"))); composer->textPart()->setWrappedPlainText(data); } void CryptoComposerTest::fillComposerCryptoData(Composer *composer) { std::vector keys = MessageComposer::Test::getKeys(); //qDebug() << "got num of keys:" << keys.size(); QStringList recipients; recipients << QString::fromLocal8Bit("you@you.you"); QList > > encKeys; encKeys.append(QPair >(recipients, keys)); composer->setEncryptionKeys(encKeys); composer->setSigningKeys(keys); } void CryptoComposerTest::runSMIMETest(bool sign, bool enc, bool opaque) { QFETCH(QString, data); QFETCH(Headers::contentEncoding, cte); Composer *composer = new Composer; fillComposerData(composer, data); - composer->infoPart()->setFrom(QString::fromLatin1("test@example.com")); + composer->infoPart()->setFrom(QStringLiteral("test@example.com")); std::vector keys = MessageComposer::Test::getKeys(true); QStringList recipients; recipients << QString::fromLocal8Bit("you@you.you"); QList > > encKeys; encKeys.append(QPair >(recipients, keys)); composer->setEncryptionKeys(encKeys); composer->setSigningKeys(keys); composer->setSignAndEncrypt(sign, enc); Kleo::CryptoMessageFormat f; if (opaque) { f = Kleo::SMIMEOpaqueFormat; } else { f = Kleo::SMIMEFormat; } composer->setMessageCryptoFormat(f); const bool result = composer->exec(); //QEXPECT_FAIL("", "GPG setup problems", Continue); QVERIFY(result); if (result) { QCOMPARE(composer->resultMessages().size(), 1); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = nullptr; //qDebug() << "message:" << message->encodedContent(); ComposerTestUtil::verify(sign, enc, message.data(), data.toUtf8(), f, cte); QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("test@example.com")); QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you")); } } diff --git a/messagecomposer/autotests/cryptocomposertest.h b/messagecomposer/autotests/cryptocomposertest.h index b5527dd8..a678b93b 100644 --- a/messagecomposer/autotests/cryptocomposertest.h +++ b/messagecomposer/autotests/cryptocomposertest.h @@ -1,83 +1,83 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (c) 2009 Leo Franchi 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 CRYPTOCOMPOSERTEST_H #define CRYPTOCOMPOSERTEST_H #include namespace MessageComposer { class Composer; } class CryptoComposerTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); private Q_SLOTS: // openpgp void testOpenPGPMime(); void testOpenPGPMime_data(); // the following will do for s-mime as well, as the same sign/enc jobs are used void testEncryptSameAttachments(); void testEncryptSameAttachments_data(); void testSignEncryptLateAttachments(); void testSignEncryptLateAttachments_data(); void testEditEncryptAttachments(); void testEditEncryptAttachments_data(); void testEditEncryptAndLateAttachments(); void testEditEncryptAndLateAttachments_data(); // secondary recipients void testBCCEncrypt(); void testBCCEncrypt_data(); // inline pgp void testOpenPGPInline_data(); void testOpenPGPInline(); // s-mime void testSMIME_data(); void testSMIME(); void testSMIMEOpaque_data(); void testSMIMEOpaque(); // contentTransferEncoding void testCTEquPr_data(); void testCTEquPr(); void testCTEbase64_data(); void testCTEbase64(); // TODO test the code for autodetecting the charset of a text attachment. private: - void fillComposerData(MessageComposer::Composer *composer, QString data); + void fillComposerData(MessageComposer::Composer *composer, const QString &data); void fillComposerCryptoData(MessageComposer::Composer *composer); // convenience, shared code void runSMIMETest(bool sign, bool enc, bool opaque); }; #endif diff --git a/messagecomposer/autotests/messagefactoryngtest.cpp b/messagecomposer/autotests/messagefactoryngtest.cpp index f5d8a131..e07cc3cb 100644 --- a/messagecomposer/autotests/messagefactoryngtest.cpp +++ b/messagecomposer/autotests/messagefactoryngtest.cpp @@ -1,966 +1,966 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2019 Laurent Montel 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 "messagefactoryngtest.h" #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include "setupenv.h" #include #include #include #include #include #include #include #include #include #include #include #include "globalsettings_templateparser.h" #include #include #include #include #include #include using namespace MessageComposer; #ifndef Q_OS_WIN void initLocale() { setenv("LC_ALL", "en_US.utf-8", 1); setenv("TZ", "UTC", 1); } Q_CONSTRUCTOR_FUNCTION(initLocale) #endif namespace { template String very_simplistic_diff(const String &a, const String &b) { const QList al = a.split('\n'); const QList bl = b.split('\n'); String result; int ai = 0, bi = 0; while (ai < al.size() && bi < bl.size()) { if (al[ai] == bl[bi]) { //qDebug( "found equal line a@%d x b@%d", ai, bi ); result += " " + al[ai] + '\n'; ++ai; ++bi; } else { //qDebug( "found unequal line a@%d x b@%d", ai, bi ); const int b_in_a = al.indexOf(bl[bi], ai); const int a_in_b = bl.indexOf(al[ai], bi); //qDebug( " b_in_a == %d", b_in_a ); //qDebug( " a_in_b == %d", a_in_b ); if (b_in_a == -1) { if (a_in_b == -1) { // (at least) one line changed: result += "- " + al[ai++] + '\n' + "+ " + bl[bi++] + '\n'; } else { // some lines added: while (bi < a_in_b) { result += "+ " + bl[bi++] + '\n'; } } } else { // some lines removed: while (ai < b_in_a) { result += "- " + al[ai++] + '\n'; } // some lines added: while (bi < a_in_b) { result += "+ " + bl[bi++] + '\n'; } } //qDebug( "result ( a@%d b@%d ):\n%s\n--end", ai, bi, result.constData() ); } } const int sizeal(al.size()); for (int i = ai; i < sizeal; ++i) { result += "- " + al[i] + '\n'; } const int sizebl(bl.size()); for (int i = bi; i < sizebl; ++i) { result += "+ " + bl[i] + '\n'; } return result; } } #define QCOMPARE_OR_DIFF(a, b) \ if (a != b) { \ qDebug("diff:\n--begin--\n%s\n--end--", very_simplistic_diff(a, b).constData());} \ QVERIFY(a == b) QTEST_MAIN(MessageFactoryTest) void MessageFactoryTest::cleanupTestCase() { delete mIdentMan; mIdentMan = nullptr; QDir dir(QDir::homePath() + QStringLiteral("/.qttest/")); dir.removeRecursively(); } void MessageFactoryTest::initTestCase() { // Force fake XDG dirs so that KIdentityManagement does not // read actual user data when running tests locally QStandardPaths::setTestModeEnabled(true); qRegisterMetaType(); mIdentMan = new KIdentityManagement::IdentityManager; KIdentityManagement::Identity &ident = mIdentMan->modifyIdentityForUoid(mIdentMan->identityForUoidOrDefault(0).uoid()); ident.setFullName(QStringLiteral("another")); ident.setPrimaryEmailAddress(QStringLiteral("another@another.com")); mIdentMan->newFromScratch(QStringLiteral("test1")); mIdentMan->newFromScratch(QStringLiteral("test2")); mIdentMan->newFromScratch(QStringLiteral("test3")); mIdentMan->setAsDefault(ident.uoid()); mIdentMan->commit(); } KMime::Message::Ptr MessageFactoryTest::loadMessage(const QString &filename) { QFile mailFile(filename); if (!mailFile.open(QIODevice::ReadOnly)) { return {}; } const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll()); if (mailData.isEmpty()) { return {}; } KMime::Message::Ptr origMsg(new KMime::Message); origMsg->setContent(mailData); origMsg->parse(); return origMsg; } void MessageFactoryTest::testCreateReplyToAllWithUseSenderAndIdentityInCCAsync() { const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_with_identity_message_and_identity_in_cc.mbox")); KMime::Message::Ptr msg = loadMessage(filename); KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1")); i1.setFullName(QStringLiteral("foo1")); i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com")); KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2")); i2.setFullName(QStringLiteral("foo2")); i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com")); mIdentMan->commit(); MessageFactoryNG factory(msg, 0); factory.setReplyStrategy(ReplyAll); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: foo1 \n" "X-KMail-Identity: %1\n" "Date: %2\n" "Cc: blo , bli , blu , bly , Bla \n" "To: Bla \n" "Subject: Re: Plain Message Test\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%3") .arg(i1.uoid()).arg(dateStr).arg(replyStr); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyToAllWithUseSenderAsync() { const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_with_identity_message.mbox")); KMime::Message::Ptr msg = loadMessage(filename); KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1")); i1.setFullName(QStringLiteral("foo1")); i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com")); KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2")); i2.setFullName(QStringLiteral("foo2")); i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com")); mIdentMan->commit(); MessageFactoryNG factory(msg, 0); factory.setReplyStrategy(ReplyAll); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "Cc: blo , bli , blu , bly \n" "To: Bla \n" "Subject: Re: Plain Message Test\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%2") .arg(dateStr).arg(replyStr); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyToAllWithUseSenderByNoSameIdentitiesAsync() { const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_without_identity_message.mbox")); KMime::Message::Ptr msg = loadMessage(filename); KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1")); i1.setFullName(QStringLiteral("foo1")); i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com")); KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2")); i2.setFullName(QStringLiteral("foo2")); i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com")); mIdentMan->commit(); MessageFactoryNG factory(msg, 0); factory.setReplyStrategy(ReplyAll); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "Cc: blo , bli , blu , bly \n" "To: Bla \n" "Subject: Re: Plain Message Test\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%2") .arg(dateStr).arg(replyStr); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyToListAsync() { const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/list_message.mbox")); KMime::Message::Ptr msg = loadMessage(filename); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); factory.setReplyStrategy(ReplyList); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray("> This is a mail from ML")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "To: list@list.org\n" "Subject: Re: Plain Message Test\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%2") .arg(dateStr).arg(replyStr); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyToAuthorAsync() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); factory.setReplyStrategy(ReplyAuthor); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString replyTo = reply.msg->inReplyTo()->asUnicodeString(); QString reference = reply.msg->references()->asUnicodeString(); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "X-KMail-Transport: 0\n" "To: me@me.me\n" "References: %3\n" "In-Reply-To: %2\n" "Subject: Re: Test Email Subject\n" "X-KMail-CursorPos: %5\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%4") .arg(dateStr).arg(replyTo).arg(reference).arg(replyStr).arg(replyStr.length() - 1); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyAllWithMultiEmailsAsync() { KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); factory.setReplyStrategy(ReplyAll); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QString replyTo = reply.msg->inReplyTo()->asUnicodeString(); QString reference = reply.msg->references()->asUnicodeString(); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "X-KMail-Transport: 0\n" "Cc: you@you.you, you2@you.you, cc@cc.cc, cc2@cc.cc\n" "To: me@me.me\n" "References: %3\n" "In-Reply-To: %2\n" "Subject: Re: Test Email Subject\nContent-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n> All happy families are alike; each unhappy family is unhappy in its own way.") .arg(dateStr).arg(replyTo).arg(reference); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyAllAsync() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.setIdentityManager(mIdentMan); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); } void MessageFactoryTest::testCreateReplyHtmlAsync() { KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("html_utf8_encoded.mbox")); //qDebug() << "html message:" << msg->encodedContent(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.setIdentityManager(mIdentMan); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; //qDebug() << "html reply" << reply.msg->encodedContent(); QDateTime date = msg->date()->dateTime().toLocalTime(); QString datetime = QLocale().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> encoded?\n\n"))); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please")); QCOMPARE(reply.msg->contents().count(), 2); QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1()); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(false); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 2); reply = spy.at(1).at(0).value(); reply.replyAll = true; datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("text/plain")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please")); QCOMPARE(reply.msg->contents().count(), 0); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); } void MessageFactoryTest::testCreateReplyUTF16Base64Async() { KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("plain_utf16.mbox")); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); // qDebug() << "plain base64 msg message:" << msg->encodedContent(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.setIdentityManager(mIdentMan); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; // qDebug() << "html reply" << reply.msg->encodedContent(); QDateTime date = msg->date()->dateTime().toLocalTime(); QString datetime = QLocale().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> quote me please.\n\n"))); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: asking for reply")); QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1()); } void MessageFactoryTest::testCreateForwardMultiEmailsAsync() { KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone); factory.createForwardAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); KMime::Message::Ptr fw = spy.at(0).at(0).value(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat); QString fwdMsg = QString::fromLatin1( "From: another \n" "Date: %2\n" "X-KMail-Transport: 0\n" "MIME-Version: 1.0\n" "Subject: Fwd: Test Email Subject\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: forward\n" "\n" "---------- Forwarded Message ----------\n" "\n" "Subject: Test Email Subject\n" "Date: %1\n" "From: me@me.me\n" "To: you@you.you, you2@you.you\n" "CC: cc@cc.cc, cc2@cc.cc\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way.\n" "-----------------------------------------"); fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString()); // qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1(); QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject")); QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1()); } void MessageFactoryTest::testCreateForwardAsync() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone); factory.createForwardAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); KMime::Message::Ptr fw = spy.at(0).at(0).value(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat); QString fwdMsg = QString::fromLatin1( "From: another \n" "Date: %2\n" "X-KMail-Transport: 0\n" "MIME-Version: 1.0\n" "Subject: Fwd: Test Email Subject\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: forward\n" "\n" "---------- Forwarded Message ----------\n" "\n" "Subject: Test Email Subject\n" "Date: %1\n" "From: me@me.me\n" "To: you@you.you\n" "CC: cc@cc.cc\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way.\n" "-----------------------------------------"); fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString()); // qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1(); QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject")); QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1()); } void MessageFactoryTest::testCreateRedirectToAndCCAndBCC() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com"); QString redirectBcc = QStringLiteral("redirbcc@redirbcc.com, redirbcc2@redirbcc.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc, redirectBcc); QString datetime = rdir->date()->asUnicodeString(); // qDebug() << rdir->encodedContent(); - QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)")); + QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); - QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)")); + QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n" "Resent-Bcc: redirbcc@redirbcc.com, redirbcc2@redirbcc.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); // qDebug() << baseline.toLatin1(); // qDebug() << "instead:" << rdir->encodedContent(); // QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateRedirectToAndCC() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc); QString datetime = rdir->date()->asUnicodeString(); // qDebug() << rdir->encodedContent(); QString msgId = MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), QString()); - QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)")); + QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); - QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)")); + QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); // qDebug() << baseline.toLatin1(); // qDebug() << "instead:" << rdir->encodedContent(); // QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateRedirect() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo); QString datetime = rdir->date()->asUnicodeString(); // qDebug() << rdir->encodedContent(); - QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)")); + QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); - QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)")); + QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); // qDebug() << baseline.toLatin1(); // qDebug() << "instead:" << rdir->encodedContent(); // QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateResend() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); KMime::Message::Ptr rdir = factory.createResend(); QString datetime = rdir->date()->asUnicodeString(); // qDebug() << msg->encodedContent(); - QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)")); + QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); - QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)")); + QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); QString baseline = QString::fromLatin1("From: me@me.me\n" "To: %1\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %2\n" "X-KMail-Transport: 0\n" "Message-ID: %3\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(msg->to()->asUnicodeString()).arg(datetime).arg(rxmessageid.cap(1)); //qDebug() << baseline.toLatin1(); //qDebug() << "instead:" << rdir->encodedContent(); // QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateMDN() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); KMime::Message::Ptr mdn = factory.createMDN(KMime::MDN::AutomaticAction, KMime::MDN::Displayed, KMime::MDN::SentAutomatically); QVERIFY(mdn.data()); //qDebug() << "mdn" << mdn->encodedContent(); QString mdnContent = QString::fromLatin1("The message sent on %1 to %2 with subject \"%3\" has been displayed. " "This is no guarantee that the message has been read or understood."); mdnContent = mdnContent.arg(KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch())) .arg(msg->to()->asUnicodeString()).arg(msg->subject()->asUnicodeString()); //qDebug() << "comparing with:" << mdnContent; QCOMPARE_OR_DIFF(Util::findTypeInMessage(mdn.data(), "multipart", "report")->contents().at(0)->body(), mdnContent.toLatin1()); } KMime::Message::Ptr MessageFactoryTest::createPlainTestMessage() { Composer *composer = new Composer; composer->globalPart()->setFallbackCharsetEnabled(true); - composer->infoPart()->setFrom(QString::fromLatin1("me@me.me")); - composer->infoPart()->setTo(QStringList(QString::fromLatin1("you@you.you"))); - composer->infoPart()->setCc(QStringList(QString::fromLatin1("cc@cc.cc"))); - composer->infoPart()->setBcc(QStringList(QString::fromLatin1("bcc@bcc.bcc"))); - composer->textPart()->setWrappedPlainText(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + composer->infoPart()->setFrom(QStringLiteral("me@me.me")); + composer->infoPart()->setTo(QStringList(QLatin1String("you@you.you"))); + composer->infoPart()->setCc(QStringList(QLatin1String("cc@cc.cc"))); + composer->infoPart()->setBcc(QStringList(QLatin1String("bcc@bcc.bcc"))); + composer->textPart()->setWrappedPlainText(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); composer->infoPart()->setSubject(QStringLiteral("Test Email Subject")); composer->globalPart()->setMDNRequested(true); composer->exec(); KMime::Message::Ptr message = KMime::Message::Ptr(composer->resultMessages().first()); delete composer; MessageComposerSettings::self()->setPreferredCharsets(QStringList() << QStringLiteral("us-ascii") << QStringLiteral("iso-8859-1") << QStringLiteral("utf-8")); return message; } KMime::Message::Ptr MessageFactoryTest::createPlainTestMessageWithMultiEmails() { Composer *composer = new Composer; composer->globalPart()->setFallbackCharsetEnabled(true); - composer->infoPart()->setFrom(QString::fromLatin1("me@me.me")); + composer->infoPart()->setFrom(QStringLiteral("me@me.me")); composer->infoPart()->setTo(QStringList() << QStringLiteral("you@you.you") << QStringLiteral("you2@you.you")); composer->infoPart()->setCc(QStringList() << QStringLiteral("cc@cc.cc") << QStringLiteral("cc2@cc.cc")); composer->infoPart()->setBcc(QStringList() << QStringLiteral("bcc@bcc.bcc") << QStringLiteral("bcc2@bcc.bcc")); - composer->textPart()->setWrappedPlainText(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way.")); + composer->textPart()->setWrappedPlainText(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way.")); composer->infoPart()->setSubject(QStringLiteral("Test Email Subject")); composer->globalPart()->setMDNRequested(true); composer->exec(); KMime::Message::Ptr message = KMime::Message::Ptr(composer->resultMessages().first()); delete composer; MessageComposerSettings::self()->setPreferredCharsets(QStringList() << QStringLiteral("us-ascii") << QStringLiteral("iso-8859-1") << QStringLiteral("utf-8")); return message; } KMime::Message::Ptr MessageFactoryTest::loadMessageFromFile(const QString &filename) { QFile file(QLatin1String(QByteArray(MAIL_DATA_DIR "/" + filename.toLatin1()))); const bool opened = file.open(QIODevice::ReadOnly); Q_ASSERT(opened); Q_UNUSED(opened); 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 MessageFactoryTest::test_multipartAlternative_data() { QTest::addColumn("mailFileName"); QTest::addColumn("contentAt"); QTest::addColumn("selection"); QTest::addColumn("expected"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); - const QStringList lst = dir.entryList(QStringList(QLatin1String("plain_message.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); + const QStringList lst = dir.entryList(QStringList(QStringLiteral("plain_message.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const QString &file : lst) { QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 0 << "" <<"> This *is* the *message* text *from* Sudhendu Kumar\n" "> \n" "> --\n" "> Thanks & Regards\n" "> Sudhendu Kumar"; QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 1 << "" << "" "
This is the message text from Sudhendu Kumar<dontspamme@yoohoo.com>
" "
--
Thanks & Regards
Sudhendu Kumar
\n

"; QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 0 << "This *is* the *message* text *from*" <<"> This *is* the *message* text *from*"; QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 1 << "This *is* the *message* text *from*" <<"
This *is* the *message* text *from*

"; } } void MessageFactoryTest::test_multipartAlternative() { QFETCH(QString, mailFileName); QFETCH(int, contentAt); QFETCH(QString, selection); QFETCH(QString, expected); KMime::Message::Ptr origMsg = loadMessage(mailFileName); MessageFactoryNG factory(origMsg, 0); factory.setIdentityManager(mIdentMan); factory.setSelection(selection); factory.setQuote(true); factory.setReplyStrategy(ReplyAll); TemplateParser::TemplateParserSettings::self()->setTemplateReplyAll(QStringLiteral("%QUOTE")); QString str; str = TemplateParser::TemplateParserSettings::self()->templateReplyAll(); factory.setTemplate(str); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE(reply.msg->contents().at(contentAt)->encodedBody().data(), expected.toLatin1().data()); } diff --git a/messagecomposer/autotests/setupenv.cpp b/messagecomposer/autotests/setupenv.cpp index 13a52bd2..caa9cc9a 100644 --- a/messagecomposer/autotests/setupenv.cpp +++ b/messagecomposer/autotests/setupenv.cpp @@ -1,73 +1,73 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 "setupenv.h" #include #include #include #include #include void MessageComposer::Test::setupEnv() { qputenv("LC_ALL", "C"); - qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1("/.qttest")).constData()); + qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.qttest")).constData()); } std::vector< GpgME::Key, std::allocator< GpgME::Key > > MessageComposer::Test::getKeys(bool smime) { QGpgME::KeyListJob *job = nullptr; if (smime) { const QGpgME::Protocol *const backend = QGpgME::smime(); Q_ASSERT(backend); job = backend->keyListJob(false); } else { const QGpgME::Protocol *const backend = QGpgME::openpgp(); Q_ASSERT(backend); job = backend->keyListJob(false); } Q_ASSERT(job); std::vector< GpgME::Key > keys; GpgME::KeyListResult res = job->exec(QStringList(), true, keys); if (!smime) { Q_ASSERT(keys.size() == 3); } Q_ASSERT(!res.error()); /* qDebug() << "got private keys:" << keys.size(); for (std::vector< GpgME::Key >::iterator i = keys.begin(); i != keys.end(); ++i) { qDebug() << "key isnull:" << i->isNull() << "isexpired:" << i->isExpired(); qDebug() << "key numuserIds:" << i->numUserIDs(); for (uint k = 0; k < i->numUserIDs(); ++k) { qDebug() << "userIDs:" << i->userID(k).email(); } } */ return keys; } diff --git a/messagecomposer/autotests/signjobtest.cpp b/messagecomposer/autotests/signjobtest.cpp index d4775ca3..4ef75b6f 100644 --- a/messagecomposer/autotests/signjobtest.cpp +++ b/messagecomposer/autotests/signjobtest.cpp @@ -1,219 +1,219 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi 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 "signjobtest.h" #include #include #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(SignJobTest) void SignJobTest::initTestCase() { MessageComposer::Test::setupEnv(); } void SignJobTest::testContentDirect() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer); QVERIFY(composer); QVERIFY(sJob); QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); KMime::Content *content = new KMime::Content; content->setBody(data); sJob->setContent(content); sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); sJob->setSigningKeys(keys); checkSignJob(sJob); } void SignJobTest::testContentChained() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); KMime::Content *content = new KMime::Content; content->setBody(data); MessageComposer::TransparentJob *tJob = new MessageComposer::TransparentJob; tJob->setContent(content); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer); sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); sJob->setSigningKeys(keys); sJob->appendSubjob(tJob); checkSignJob(sJob); } void SignJobTest::testHeaders() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer); QVERIFY(composer); QVERIFY(sJob); QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); KMime::Content *content = new KMime::Content; content->setBody(data); sJob->setContent(content); sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); sJob->setSigningKeys(keys); VERIFYEXEC(sJob); QByteArray mimeType("multipart/signed"); QByteArray charset("ISO-8859-1"); KMime::Content *result = sJob->content(); result->assemble(); qDebug() << result->encodedContent(); QVERIFY(result->contentType(false)); QCOMPARE(result->contentType()->mimeType(), mimeType); QCOMPARE(result->contentType()->charset(), charset); QVERIFY(result->contentType()->parameter(QString::fromLocal8Bit("micalg")).startsWith(QLatin1String("pgp-sha"))); // sha1 or sha256, depending on GnuPG version QCOMPARE(result->contentType()->parameter(QString::fromLocal8Bit("protocol")), QString::fromLocal8Bit("application/pgp-signature")); QCOMPARE(result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit); } void SignJobTest::testRecommentationRFC3156() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); QString data = QStringLiteral("=2D Magic foo\nFrom test\n\n-- quaak\nOhno"); KMime::Headers::contentEncoding cte = KMime::Headers::CEquPr; MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer); QVERIFY(composer); QVERIFY(sJob); KMime::Content *content = new KMime::Content; content->setBody(data.toUtf8()); sJob->setContent(content); sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); sJob->setSigningKeys(keys); VERIFYEXEC(sJob); KMime::Content *result = sJob->content(); result->assemble(); qDebug() << result->encodedContent(); QByteArray body = MessageCore::NodeHelper::firstChild(result)->body(); QCOMPARE(QString::fromUtf8(body), QStringLiteral("=3D2D Magic foo\n=46rom test\n\n=2D- quaak\nOhno")); ComposerTestUtil::verify(true, false, result, data.toUtf8(), Kleo::OpenPGPMIMEFormat, cte); } void SignJobTest::testMixedContent() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); - QString data = QString::fromUtf8("=2D Magic foo\nFrom test\n\n-- quaak\nOhno"); + QString data = QStringLiteral("=2D Magic foo\nFrom test\n\n-- quaak\nOhno"); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer); QVERIFY(composer); QVERIFY(sJob); KMime::Content *content = new KMime::Content; content->contentType()->setMimeType(QByteArrayLiteral("multipart/mixed")); content->contentType()->setBoundary(KMime::multiPartBoundary()); KMime::Content *subcontent = new KMime::Content; subcontent->contentType()->setMimeType(QByteArrayLiteral("text/plain")); subcontent->setBody(data.toUtf8()); KMime::Content *attachment = new KMime::Content; attachment->contentType()->setMimeType(QByteArrayLiteral("text/plain")); QByteArray attachmentData("an attachment"); attachment->setBody(attachmentData); content->addContent(subcontent); content->addContent(attachment); content->assemble(); sJob->setContent(content); sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); sJob->setSigningKeys(keys); VERIFYEXEC(sJob); KMime::Content *result = sJob->content(); result->assemble(); qDebug() << result->encodedContent(); KMime::Content *firstChild = MessageCore::NodeHelper::firstChild(result); QCOMPARE(result->contents().count(), 2); QCOMPARE(firstChild->contents().count(), 2); QCOMPARE(firstChild->body(), QByteArray()); QCOMPARE(firstChild->contentType()->mimeType(), QByteArrayLiteral("multipart/mixed")); QCOMPARE(firstChild->contents()[0]->body(), data.toUtf8()); QCOMPARE(firstChild->contents()[1]->body(), attachmentData); ComposerTestUtil::verify(true, false, result, data.toUtf8(), Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit); } void SignJobTest::checkSignJob(MessageComposer::SignJob *sJob) { VERIFYEXEC(sJob); KMime::Content *result = sJob->content(); Q_ASSERT(result); result->assemble(); ComposerTestUtil::verifySignature(result, QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8(), Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit); } diff --git a/messagecomposer/src/utils/util.cpp b/messagecomposer/src/utils/util.cpp index 36b4c5d3..3ebb1ef3 100644 --- a/messagecomposer/src/utils/util.cpp +++ b/messagecomposer/src/utils/util.cpp @@ -1,459 +1,459 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi Parts based on KMail code by: 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 "utils/util.h" #include "util_p.h" #include #include "composer/composer.h" #include "job/singlepartjob.h" #include #include #include #include #include "messagecomposer_debug.h" #include #include #include #include #include #include #include #include #include KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret) { MessageComposer::Composer composer; MessageComposer::SinglepartJob cteJob(&composer); cteJob.contentType()->setMimeType(contentType->mimeType()); cteJob.contentType()->setCharset(contentType->charset()); cteJob.setData(encodedBody); cteJob.exec(); cteJob.content()->assemble(); ret->contentTransferEncoding()->setEncoding(cteJob.contentTransferEncoding()->encoding()); ret->setBody(cteJob.content()->encodedBody()); return ret; } KMime::Content *MessageComposer::Util::composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, Kleo::CryptoMessageFormat format, bool sign, const QByteArray &hashAlgo) { KMime::Content *result = new KMime::Content; // called should have tested that the signing/encryption failed Q_ASSERT(!encodedBody.isEmpty()); if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message qCDebug(MESSAGECOMPOSER_LOG) << "making MIME message, format:" << format; makeToplevelContentType(result, format, sign, hashAlgo); if (makeMultiMime(format, sign)) { // sign/enc PGPMime, sign SMIME const QByteArray boundary = KMime::multiPartBoundary(); result->contentType()->setBoundary(boundary); result->assemble(); //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head(); // Build the encapsulated MIME parts. // Build a MIME part holding the code information // taking the body contents returned in ciphertext. KMime::Content *code = new KMime::Content; setNestedContentType(code, format, sign); setNestedContentDisposition(code, format, sign); if (sign) { // sign PGPMime, sign SMIME if (format & Kleo::AnySMIME) { // sign SMIME code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64); code->contentTransferEncoding()->needToEncode(); code->setBody(encodedBody); } else { // sign PGPMmime setBodyAndCTE(encodedBody, orig->contentType(), code); } result->addContent(orig); result->addContent(code); } else { // enc PGPMime setBodyAndCTE(encodedBody, orig->contentType(), code); // Build a MIME part holding the version information // taking the body contents returned in // structuring.data.bodyTextVersion. KMime::Content *vers = new KMime::Content; vers->contentType()->setMimeType("application/pgp-encrypted"); vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment); vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); vers->setBody("Version: 1"); result->addContent(vers); result->addContent(code); } } else { //enc SMIME, sign/enc SMIMEOpaque result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64); result->contentDisposition()->setDisposition(KMime::Headers::CDattachment); result->contentDisposition()->setFilename(QStringLiteral("smime.p7m")); result->assemble(); //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head(); result->setBody(encodedBody); } } else { // sign/enc PGPInline result->setHead(orig->head()); result->parse(); // fixing ContentTransferEncoding setBodyAndCTE(encodedBody, orig->contentType(), result); } return result; } // set the correct top-level ContentType on the message void MessageComposer::Util::makeToplevelContentType(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign, const QByteArray &hashAlgo) { switch (format) { default: case Kleo::InlineOpenPGPFormat: case Kleo::OpenPGPMIMEFormat: if (sign) { content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed")); - content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pgp-signature")); + content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature")); content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower()); } else { content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted")); - content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pgp-encrypted")); + content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted")); } return; case Kleo::SMIMEFormat: if (sign) { qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME"; content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed")); - content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pkcs7-signature")); + content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pkcs7-signature")); content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(hashAlgo).toLower()); return; } // fall through (for encryption, there's no difference between // SMIME and SMIMEOpaque, since there is no mp/encrypted for // S/MIME) Q_FALLTHROUGH(); case Kleo::SMIMEOpaqueFormat: qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque"; content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime")); if (sign) { - content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromLatin1("signed-data")); + content->contentType()->setParameter(QStringLiteral("smime-type"), QStringLiteral("signed-data")); } else { - content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromLatin1("enveloped-data")); + content->contentType()->setParameter(QStringLiteral("smime-type"), QStringLiteral("enveloped-data")); } - content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("smime.p7m")); + content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("smime.p7m")); } } void MessageComposer::Util::setNestedContentType(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign) { switch (format) { case Kleo::OpenPGPMIMEFormat: if (sign) { content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature")); - content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc")); + content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("signature.asc")); content->contentDescription()->from7BitString("This is a digitally signed message part."); } else { content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream")); } return; case Kleo::SMIMEFormat: if (sign) { content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature")); - content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("smime.p7s")); + content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("smime.p7s")); return; } Q_FALLTHROUGH(); // fall through: default: case Kleo::InlineOpenPGPFormat: case Kleo::SMIMEOpaqueFormat: ; } } void MessageComposer::Util::setNestedContentDisposition(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign) { if (!sign && format & Kleo::OpenPGPMIMEFormat) { content->contentDisposition()->setDisposition(KMime::Headers::CDinline); content->contentDisposition()->setFilename(QStringLiteral("msg.asc")); } else if (sign && format & Kleo::SMIMEFormat) { content->contentDisposition()->setDisposition(KMime::Headers::CDattachment); content->contentDisposition()->setFilename(QStringLiteral("smime.p7s")); } } bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign) { switch (format) { default: case Kleo::InlineOpenPGPFormat: case Kleo::SMIMEOpaqueFormat: return false; case Kleo::OpenPGPMIMEFormat: return true; case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME } } QByteArray MessageComposer::Util::selectCharset(const QList &charsets, const QString &text) { for (const QByteArray &name : charsets) { // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because // the former knows us-ascii is latin1. QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(name)); if (!codec) { qCWarning(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << name; continue; } if (codec->canEncode(text)) { // Special check for us-ascii (needed because us-ascii is not exactly latin1). if (name == "us-ascii" && !KMime::isUsAscii(text)) { continue; } qCDebug(MESSAGECOMPOSER_LOG) << "Chosen charset" << name; return name; } } qCDebug(MESSAGECOMPOSER_LOG) << "No appropriate charset found."; return QByteArray(); } QStringList MessageComposer::Util::AttachmentKeywords() { return i18nc( "comma-separated list of keywords that are used to detect whether " "the user forgot to attach his attachment. Do not add space between words.", "attachment,attached").split(QLatin1Char(',')); } QString MessageComposer::Util::cleanedUpHeaderString(const QString &s) { // remove invalid characters from the header strings QString res(s); res.remove(QChar::fromLatin1('\r')); res.replace(QChar::fromLatin1('\n'), QLatin1Char(' ')); return res.trimmed(); } void MessageComposer::Util::addSendReplyForwardAction(const KMime::Message::Ptr &message, MailTransport::MessageQueueJob *qjob) { QList originalMessageId; QList linkStatus; if (MessageComposer::Util::getLinkInformation(message, originalMessageId, linkStatus)) { for (Akonadi::Item::Id id : qAsConst(originalMessageId)) { if (linkStatus.first() == Akonadi::MessageStatus::statusReplied()) { qjob->sentActionAttribute().addAction(MailTransport::SentActionAttribute::Action::MarkAsReplied, QVariant(id)); } else if (linkStatus.first() == Akonadi::MessageStatus::statusForwarded()) { qjob->sentActionAttribute().addAction(MailTransport::SentActionAttribute::Action::MarkAsForwarded, QVariant(id)); } } } } bool MessageComposer::Util::sendMailDispatcherIsOnline(QWidget *parent) { Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(QStringLiteral("akonadi_maildispatcher_agent")); if (!instance.isValid()) { const int rc = KMessageBox::warningYesNo(parent, i18n("The mail dispatcher is not set up, so mails cannot be sent. Do you want to create a mail dispatcher?"), i18n("No mail dispatcher."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("no_maildispatcher")); if (rc == KMessageBox::Yes) { const Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_maildispatcher_agent")); Q_ASSERT(type.isValid()); Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob(type); // async. We'll have to try again later. job->start(); } return false; } if (instance.isOnline()) { return true; } else { const int rc = KMessageBox::warningYesNo(parent, i18n("The mail dispatcher is offline, so mails cannot be sent. Do you want to make it online?"), i18n("Mail dispatcher offline."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("maildispatcher_put_online")); if (rc == KMessageBox::Yes) { instance.setIsOnline(true); return true; } } return false; } void MessageComposer::Util::removeNotNecessaryHeaders(const KMime::Message::Ptr &msg) { msg->removeHeader("X-KMail-SignatureActionEnabled"); msg->removeHeader("X-KMail-EncryptActionEnabled"); msg->removeHeader("X-KMail-CryptoMessageFormat"); } KMime::Content *MessageComposer::Util::findTypeInMessage(KMime::Content *data, const QByteArray &mimeType, const QByteArray &subType) { if (!data->contentType()->isEmpty()) { if (mimeType.isEmpty() || subType.isEmpty()) { return data; } if ((mimeType == data->contentType()->mediaType()) && (subType == data->contentType()->subType())) { return data; } } foreach (auto child, data->contents()) { if ((!child->contentType()->isEmpty()) && (mimeType == child->contentType()->mimeType()) && (subType == child->contentType()->subType())) { return child; } auto ret = findTypeInMessage(child, mimeType, subType); if (ret) { return ret; } } return nullptr; } void MessageComposer::Util::addLinkInformation(const KMime::Message::Ptr &msg, Akonadi::Item::Id id, Akonadi::MessageStatus status) { Q_ASSERT(status.isReplied() || status.isForwarded() || status.isDeleted()); QString message; if (auto hrd = msg->headerByType("X-KMail-Link-Message")) { message = hrd->asUnicodeString(); } if (!message.isEmpty()) { message += QChar::fromLatin1(','); } QString type; if (auto hrd = msg->headerByType("X-KMail-Link-Type")) { type = hrd->asUnicodeString(); } if (!type.isEmpty()) { type += QChar::fromLatin1(','); } message += QString::number(id); if (status.isReplied()) { type += QLatin1String("reply"); } else if (status.isForwarded()) { type += QLatin1String("forward"); } KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Link-Message"); header->fromUnicodeString(message, "utf-8"); msg->setHeader(header); header = new KMime::Headers::Generic("X-KMail-Link-Type"); header->fromUnicodeString(type, "utf-8"); msg->setHeader(header); } bool MessageComposer::Util::getLinkInformation(const KMime::Message::Ptr &msg, QList &id, QList &status) { auto hrdLinkMsg = msg->headerByType("X-KMail-Link-Message"); auto hrdLinkType = msg->headerByType("X-KMail-Link-Type"); if (!hrdLinkMsg || !hrdLinkType) { return false; } const QStringList messages = hrdLinkMsg->asUnicodeString().split(QLatin1Char(','), QString::SkipEmptyParts); const QStringList types = hrdLinkType->asUnicodeString().split(QLatin1Char(','), QString::SkipEmptyParts); if (messages.isEmpty() || types.isEmpty()) { return false; } for (const QString &idStr : messages) { id << idStr.toLongLong(); } for (const QString &typeStr : types) { if (typeStr == QLatin1String("reply")) { status << Akonadi::MessageStatus::statusReplied(); } else if (typeStr == QLatin1String("forward")) { status << Akonadi::MessageStatus::statusForwarded(); } } return true; } bool MessageComposer::Util::isStandaloneMessage(const Akonadi::Item &item) { // standalone message have a valid payload, but are not, themselves valid items return item.hasPayload() && !item.isValid(); } KMime::Message::Ptr MessageComposer::Util::message(const Akonadi::Item &item) { if (!item.hasPayload()) { qCWarning(MESSAGECOMPOSER_LOG) << "Payload is not a MessagePtr!"; return KMime::Message::Ptr(); } return item.payload(); } bool MessageComposer::Util::hasMissingAttachments(const QStringList &attachmentKeywords, QTextDocument *doc, const QString &subj) { if (!doc) { return false; } QStringList attachWordsList = attachmentKeywords; QRegularExpression rx(QLatin1String("\\b") +attachWordsList.join(QStringLiteral("\\b|\\b")) +QLatin1String("\\b"), QRegularExpression::CaseInsensitiveOption); // check whether the subject contains one of the attachment key words // unless the message is a reply or a forwarded message bool gotMatch = (MessageCore::StringUtil::stripOffPrefixes(subj) == subj) && (rx.match(subj).hasMatch()); if (!gotMatch) { // check whether the non-quoted text contains one of the attachment key // words QRegularExpression quotationRx(QStringLiteral("^([ \\t]*([|>:}#]|[A-Za-z]+>))+")); QTextBlock end(doc->end()); for (QTextBlock it = doc->begin(); it != end; it = it.next()) { const QString line = it.text(); gotMatch = (!quotationRx.match(line).hasMatch()) && (rx.match(line).hasMatch()); if (gotMatch) { break; } } } if (!gotMatch) { return false; } return true; } diff --git a/messagecore/autotests/attachmentcompressjobtest.cpp b/messagecore/autotests/attachmentcompressjobtest.cpp index 2cd58691..57e0a27e 100644 --- a/messagecore/autotests/attachmentcompressjobtest.cpp +++ b/messagecore/autotests/attachmentcompressjobtest.cpp @@ -1,104 +1,104 @@ /* Copyright (c) 2009 Constantin Berzan 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 "attachmentcompressjobtest.h" #include "qtest_messagecore.h" #include #include "messagecore_debug.h" #include #include #include using namespace MessageCore; QTEST_MAIN(AttachmentCompressJobTest) void AttachmentCompressJobTest::testCompress() { // Some data. QByteArray data; for (int i = 0; i < 100; ++i) { data += "This is some highly compressible text...\n"; } const QString name = QStringLiteral("name"); const QString fileName = QStringLiteral("name.txt"); const QString description = QStringLiteral("description"); // Create the original part. AttachmentPart::Ptr origPart = AttachmentPart::Ptr(new AttachmentPart); origPart->setName(name); origPart->setFileName(fileName); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); QVERIFY(!origPart->isAutoEncoding()); origPart->setData(data); QVERIFY(!origPart->isCompressed()); // Compress the part and verify it. AttachmentCompressJob *cjob = new AttachmentCompressJob(origPart, this); VERIFYEXEC(cjob); QCOMPARE(cjob->originalPart(), origPart); AttachmentPart::Ptr zipPart = cjob->compressedPart(); //qCDebug(MESSAGECORE_LOG) << data; //qCDebug(MESSAGECORE_LOG) << zipPart->data(); QVERIFY(zipPart->isAutoEncoding()); QVERIFY(zipPart->isCompressed()); QCOMPARE(zipPart->name(), QString(name + QString::fromLatin1(".zip"))); QCOMPARE(zipPart->fileName(), QString(fileName + QString::fromLatin1(".zip"))); QCOMPARE(zipPart->description(), description); QCOMPARE(zipPart->mimeType(), QByteArrayLiteral("application/zip")); // Uncompress the data and verify it. // (Stuff below is stolen from KMail code.) QByteArray zipData = zipPart->data(); QBuffer buffer(&zipData); KZip zip(&buffer); QVERIFY(zip.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = zip.directory(); QCOMPARE(dir->entries().count(), 1); const KZipFileEntry *entry = (KZipFileEntry *)dir->entry(dir->entries().at(0)); QCOMPARE(entry->data(), data); QCOMPARE(entry->name(), name); zip.close(); } void AttachmentCompressJobTest::testCompressedSizeLarger() { // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. AttachmentPart::Ptr origPart = AttachmentPart::Ptr(new AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); QVERIFY(!origPart->isAutoEncoding()); origPart->setData(data); QVERIFY(!origPart->isCompressed()); // Compress the part and verify that it is aware of its folly. AttachmentCompressJob *cjob = new AttachmentCompressJob(origPart, this); VERIFYEXEC(cjob); QVERIFY(cjob->isCompressedPartLarger()); } diff --git a/messagecore/autotests/attachmentfromurljobtest.cpp b/messagecore/autotests/attachmentfromurljobtest.cpp index 40546ca6..ac32eeee 100644 --- a/messagecore/autotests/attachmentfromurljobtest.cpp +++ b/messagecore/autotests/attachmentfromurljobtest.cpp @@ -1,105 +1,105 @@ /* Copyright (c) 2009 Constantin Berzan 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 "attachmentfromurljobtest.h" #include "qtest_messagecore.h" #include #include "attachment/attachmentfromurljob.h" #include using namespace MessageCore; QTEST_MAIN(AttachmentFromUrlJobTest) #define PATH_ATTACHMENTS QLatin1String(KDESRCDIR "/attachments/") void AttachmentFromUrlJobTest::initTestCase() { qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher } void AttachmentFromUrlJobTest::testAttachments_data() { QTest::addColumn("url"); QTest::addColumn("filename"); QTest::addColumn("mimetype"); // PATH_ATTACHMENTS is defined by CMake. - QTest::newRow("png image") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("image.png")) - << QString::fromLatin1("image.png") + QTest::newRow("png image") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("image.png")) + << QStringLiteral("image.png") << QByteArray("image/png"); - QTest::newRow("pdf doc") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("doc.pdf")) - << QString::fromLatin1("doc.pdf") + QTest::newRow("pdf doc") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("doc.pdf")) + << QStringLiteral("doc.pdf") << QByteArray("application/pdf"); - QTest::newRow("text file") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")) - << QString::fromLatin1("file.txt") + QTest::newRow("text file") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")) + << QStringLiteral("file.txt") << QByteArray("text/plain"); } void AttachmentFromUrlJobTest::testAttachments() { QFETCH(QUrl, url); QFETCH(QString, filename); QFETCH(QByteArray, mimetype); QFile file(url.path()); QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray data = file.readAll(); file.close(); AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this); VERIFYEXEC(ljob); AttachmentPart::Ptr part = ljob->attachmentPart(); delete ljob; ljob = nullptr; QCOMPARE(part->name(), filename); QCOMPARE(part->fileName(), filename); QVERIFY(!part->isInline()); QCOMPARE(part->mimeType(), mimetype); QCOMPARE(part->data(), data); } void AttachmentFromUrlJobTest::testAttachmentTooBig() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("doc.pdf")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("doc.pdf")); AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this); ljob->setMaximumAllowedSize(1024); // 1KiB, whereas the file is >9KiB. QVERIFY(!ljob->exec()); } void AttachmentFromUrlJobTest::testAttachmentCharset() { const QByteArray charset("iso-8859-2"); - const QString filename = QString::fromLatin1("file.txt"); + const QString filename = QStringLiteral("file.txt"); QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + filename); MessageCore::StringUtil::setEncodingFile(url, QString::fromLatin1(charset)); AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this); VERIFYEXEC(ljob); AttachmentPart::Ptr part = ljob->attachmentPart(); delete ljob; ljob = nullptr; QCOMPARE(part->name(), filename); QCOMPARE(part->fileName(), filename); QCOMPARE(part->charset(), charset); } diff --git a/messagecore/autotests/attachmentupdatejobtest.cpp b/messagecore/autotests/attachmentupdatejobtest.cpp index cbfe3b3d..e1b67970 100644 --- a/messagecore/autotests/attachmentupdatejobtest.cpp +++ b/messagecore/autotests/attachmentupdatejobtest.cpp @@ -1,223 +1,223 @@ /* Copyright (c) 2014-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 "attachmentupdatejobtest.h" #include #include #include #include "qtest_messagecore.h" #define PATH_ATTACHMENTS QLatin1String(KDESRCDIR "/attachments/") AttachmentUpdateJobTest::AttachmentUpdateJobTest(QObject *parent) : QObject(parent) { qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher } AttachmentUpdateJobTest::~AttachmentUpdateJobTest() { } void AttachmentUpdateJobTest::shouldHaveDefaultValue() { MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); QCOMPARE(origPart, job->originalPart()); QVERIFY(!job->updatedPart()); delete job; } void AttachmentUpdateJobTest::shouldUpdateAttachment() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); origPart->setData(data); origPart->setUrl(url); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QVERIFY(origPart->size() != job->updatedPart()->size()); QVERIFY(origPart->data() != job->updatedPart()->data()); } void AttachmentUpdateJobTest::shouldHaveSameNameDescriptionAfterUpdate() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); - const QString filename = QString::fromLatin1("filename"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); + const QString filename = QStringLiteral("filename"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setFileName(filename); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); origPart->setData(data); origPart->setUrl(url); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QCOMPARE(origPart->name(), job->updatedPart()->name()); QCOMPARE(origPart->description(), job->updatedPart()->description()); QCOMPARE(origPart->fileName(), job->updatedPart()->fileName()); } void AttachmentUpdateJobTest::shouldHaveSameCryptoSignStatusAfterUpdate() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); origPart->setData(data); origPart->setUrl(url); origPart->setSigned(true); origPart->setEncrypted(true); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QCOMPARE(origPart->isSigned(), job->updatedPart()->isSigned()); QCOMPARE(origPart->isEncrypted(), job->updatedPart()->isEncrypted()); } void AttachmentUpdateJobTest::shouldHaveSameEncodingAfterUpdate() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/pdf"); origPart->setEncoding(KMime::Headers::CE8Bit); origPart->setData(data); origPart->setUrl(url); origPart->setSigned(true); origPart->setEncrypted(true); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QCOMPARE(origPart->encoding(), job->updatedPart()->encoding()); } void AttachmentUpdateJobTest::shouldHaveSameMimetypeAfterUpdate() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/pdf"); origPart->setEncoding(KMime::Headers::CE8Bit); origPart->setData(data); origPart->setUrl(url); origPart->setSigned(true); origPart->setEncrypted(true); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QCOMPARE(origPart->mimeType(), job->updatedPart()->mimeType()); } void AttachmentUpdateJobTest::shouldNotUpdateWhenUrlIsEmpty() { QByteArray data("This is short enough that compressing it is not efficient."); - const QString name = QString::fromLatin1("name.txt"); - const QString description = QString::fromLatin1("description"); + const QString name = QStringLiteral("name.txt"); + const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/plain"); origPart->setEncoding(KMime::Headers::CE7Bit); origPart->setData(data); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); job->exec(); QVERIFY(!job->updatedPart()); } void AttachmentUpdateJobTest::shouldHaveSameInlineStatus() { - const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt")); + const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt")); // Some data. QByteArray data("This is short enough that compressing it is not efficient."); const QString name = QStringLiteral("name.txt"); const QString description = QStringLiteral("description"); // Create the original part. MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart); origPart->setName(name); origPart->setDescription(description); origPart->setMimeType("text/pdf"); origPart->setEncoding(KMime::Headers::CE8Bit); origPart->setData(data); origPart->setUrl(url); origPart->setSigned(true); origPart->setEncrypted(true); origPart->setInline(true); MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this); VERIFYEXEC(job); QCOMPARE(origPart->isInline(), job->updatedPart()->isInline()); } QTEST_MAIN(AttachmentUpdateJobTest) diff --git a/messagelist/autotests/searchlinestatustest.cpp b/messagelist/autotests/searchlinestatustest.cpp index c767b10a..957d5aea 100644 --- a/messagelist/autotests/searchlinestatustest.cpp +++ b/messagelist/autotests/searchlinestatustest.cpp @@ -1,76 +1,76 @@ /* Copyright (c) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 "searchlinestatustest.h" #include "../src/core/widgets/searchlinestatus.h" #include #include #include SearchLineStatusTest::SearchLineStatusTest(QObject *parent) : QObject(parent) { } SearchLineStatusTest::~SearchLineStatusTest() { } void SearchLineStatusTest::shouldHaveDefaultValue() { MessageList::Core::SearchLineStatus w; QVERIFY(!w.containsOutboundMessages()); QVERIFY(!w.locked()); QMenu *filterMenu = w.findChild(QStringLiteral("filtermenu")); QVERIFY(filterMenu); QVERIFY(!filterMenu->actions().isEmpty()); QVERIFY(w.completer()); QVERIFY(w.completer()->model()); QCOMPARE(w.completer()->model()->rowCount(), 0); //Verify if qt qlineedit name changed - QAction *act = w.findChild(QLatin1String("_q_qlineeditclearaction")); + QAction *act = w.findChild(QStringLiteral("_q_qlineeditclearaction")); QVERIFY(act); } void SearchLineStatusTest::shouldAddCompletionItem() { MessageList::Core::SearchLineStatus w; w.addCompletionItem(QStringLiteral("ff")); QCOMPARE(w.completer()->model()->rowCount(), 1); //Don't add same element w.addCompletionItem(QStringLiteral("ff")); QCOMPARE(w.completer()->model()->rowCount(), 1); w.addCompletionItem(QStringLiteral("ffss")); QCOMPARE(w.completer()->model()->rowCount(), 2); } void SearchLineStatusTest::shouldClearCompleter() { MessageList::Core::SearchLineStatus w; for (int i = 0; i < 10; ++i) { w.addCompletionItem(QStringLiteral("ff%1").arg(i)); } QCOMPARE(w.completer()->model()->rowCount(), 10); w.slotClearHistory(); QCOMPARE(w.completer()->model()->rowCount(), 0); } QTEST_MAIN(SearchLineStatusTest) diff --git a/messagelist/src/core/optionset.cpp b/messagelist/src/core/optionset.cpp index 559d63f6..513f7650 100644 --- a/messagelist/src/core/optionset.cpp +++ b/messagelist/src/core/optionset.cpp @@ -1,142 +1,142 @@ /****************************************************************************** * * Copyright 2008 Szymon Tomasz Stefanek * * 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 "core/optionset.h" #include // for ::time( time_t * ) #include static const int gOptionSetInitialMarker = 0xcafe; // don't change static const int gOptionSetFinalMarker = 0xbabe; // don't change static const int gOptionSetWithReadOnLyModeVersion = 0x1002; using namespace MessageList::Core; OptionSet::OptionSet() : mReadOnly(false) { generateUniqueId(); } OptionSet::OptionSet(const OptionSet &set) : mId(set.mId) , mName(set.mName) , mDescription(set.mDescription) , mReadOnly(set.mReadOnly) { } OptionSet::OptionSet(const QString &name, const QString &description, bool readOnly) : mName(name) , mDescription(description) , mReadOnly(readOnly) { generateUniqueId(); } OptionSet::~OptionSet() { } void OptionSet::generateUniqueId() { static int nextUniqueId = 0; nextUniqueId++; - mId = QStringLiteral("%1-%2").arg((unsigned int)::time(0)).arg(nextUniqueId); + mId = QStringLiteral("%1-%2").arg((unsigned int)::time(nullptr)).arg(nextUniqueId); } QString OptionSet::saveToString() const { QByteArray raw; { QDataStream s(&raw, QIODevice::WriteOnly); s << gOptionSetInitialMarker; s << gOptionSetWithReadOnLyModeVersion; s << mId; s << mName; s << mDescription; s << mReadOnly; save(s); s << gOptionSetFinalMarker; } return QString::fromLatin1(raw.toHex()); } bool OptionSet::loadFromString(const QString &data) { QByteArray raw = QByteArray::fromHex(data.toLatin1()); QDataStream s(&raw, QIODevice::ReadOnly); int marker; s >> marker; if (marker != gOptionSetInitialMarker) { return false; // invalid configuration } int currentVersion; s >> currentVersion; if (currentVersion > gOptionSetWithReadOnLyModeVersion) { return false; // invalid configuration } s >> mId; if (mId.isEmpty()) { return false; // invalid configuration } s >> mName; if (mName.isEmpty()) { return false; // invalid configuration } s >> mDescription; bool readOnly = false; if (currentVersion == gOptionSetWithReadOnLyModeVersion) { s >> readOnly; } mReadOnly = readOnly; if (!load(s)) { return false; // invalid configuration } s >> marker; if (marker != gOptionSetFinalMarker) { return false; // invalid configuration } return true; } diff --git a/messageviewer/autotests/setupenv.cpp b/messageviewer/autotests/setupenv.cpp index 1e1fb96b..6d64e37d 100644 --- a/messageviewer/autotests/setupenv.cpp +++ b/messageviewer/autotests/setupenv.cpp @@ -1,34 +1,34 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 "setupenv.h" #include #include #include void MessageViewer::Test::setupEnv() { qputenv("LC_ALL", "C"); - qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1( + qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String( "/.qttest")).constData()); QStandardPaths::setTestModeEnabled(true); } diff --git a/messageviewer/autotests/urlhandlermanagertest.cpp b/messageviewer/autotests/urlhandlermanagertest.cpp index 3d779141..28c5f653 100644 --- a/messageviewer/autotests/urlhandlermanagertest.cpp +++ b/messageviewer/autotests/urlhandlermanagertest.cpp @@ -1,127 +1,127 @@ /* Copyright (c) 2017 Sandro Knauß This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. 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 "urlhandlermanagertest.h" #include "util.h" #include "viewer/viewer.h" #include "viewer/viewer_p.h" #include "viewer/urlhandlermanager_p.h" #include #include #include #include using namespace MessageViewer; class TestBodyPartURLHandler : public Interface::BodyPartURLHandler { public: - void testPath(QString path) const + void testPath(const QString &path) const { QCOMPARE(path, mPath); } void testViewer(MessageViewer::Viewer *viewerInstance) const { QCOMPARE(viewerInstance, mViewerInstance); } void testContent(KMime::Content *content) const { QCOMPARE(content, mContent); } bool handleClick(MessageViewer::Viewer *viewerInstance, MimeTreeParser::Interface::BodyPart *part, const QString &path) const override { testContent(part->content()); testViewer(viewerInstance); testPath(path); return mHandleClick; } bool handleContextMenuRequest(MimeTreeParser::Interface::BodyPart *part, const QString &path, const QPoint &p) const override { Q_UNUSED(p); testContent(part->content()); testPath(path); return mHandleContextMenuRequest; } QString statusBarMessage(MimeTreeParser::Interface::BodyPart *part, const QString &path) const override { testContent(part->content()); testPath(path); return mStatusBarMessageRet; } QString mPath; QString mStatusBarMessageRet; bool mHandleClick; bool mHandleContextMenuRequest; MessageViewer::Viewer *mViewerInstance; KMime::Content *mContent; }; BodyPartUrlHandlerManagerTest::BodyPartUrlHandlerManagerTest(QObject *parent) : QObject(parent) { } void BodyPartUrlHandlerManagerTest::testHandleClick_data() { QTest::addColumn("url"); QTest::addColumn("path"); QTest::addColumn("index"); QTest::addColumn("ret"); QTest::newRow("completely_empty") << QString() << QString() << KMime::ContentIndex() << false; QTest::newRow("empty") << QStringLiteral("x-kmail:") << QString() << KMime::ContentIndex() << false; QTest::newRow("pgpkey") << QStringLiteral("x-kmail:/bodypart/1234/2/pgpkey") << QStringLiteral("pgpkey") << KMime::ContentIndex(QStringLiteral("2")) << true; QTest::newRow("test") << QStringLiteral("x-kmail:/bodypart/1234/1/test") << QStringLiteral("test") << KMime::ContentIndex(QStringLiteral("1")) << true; QTest::newRow("test_with_arguments") << QStringLiteral("x-kmail:/bodypart/1234/1/test?foo=qua") << QStringLiteral("test?foo=qua") << KMime::ContentIndex(QStringLiteral("1")) << true; } void BodyPartUrlHandlerManagerTest::testHandleClick() { QFETCH(QString, url); QFETCH(QString, path); QFETCH(KMime::ContentIndex, index); QFETCH(bool, ret); BodyPartURLHandlerManager manager; TestBodyPartURLHandler handler; manager.registerHandler(&handler, QLatin1String("")); Viewer v(nullptr); ViewerPrivate vp(&v, nullptr, nullptr); const KMime::Message::Ptr msg(Test::readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"))); vp.setMessage(msg, MimeTreeParser::Delayed); handler.mViewerInstance = &v; handler.mPath = path; handler.mHandleClick = true; handler.mContent = msg->content(index); QCOMPARE(manager.handleClick(QUrl(url), &vp), ret); manager.unregisterHandler(&handler); } QTEST_MAIN(BodyPartUrlHandlerManagerTest) diff --git a/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp b/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp index 148d2e0f..f3e74a95 100644 --- a/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp +++ b/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp @@ -1,42 +1,42 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 "setupenv.h" #include "messagepartthemes/default/messagepartrendererfactory.h" #include #include #include #include #include void MessageViewer::Test::setupEnv() { - qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1( + qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String( "/.qttest")).constData()); qputenv("TZ", "UTC"); QStandardPaths::setTestModeEnabled(true); QIcon::setThemeName(QStringLiteral("breeze")); QLocale::setDefault(QLocale(QStringLiteral("en_US"))); // disable plugin loading, so kdepim-addons doesn't interfere with our tests MessagePartRendererFactory::instance()->setPluginPath(QString()); } diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp index c44bc2bf..3e96f434 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp @@ -1,1171 +1,1171 @@ /* 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 "defaultrenderer.h" #include "defaultrenderer_p.h" #include "utils/messageviewerutil.h" #include "messageviewer_debug.h" #include "converthtmltoplaintext.h" #include "messagepartrendererbase.h" #include "messagepartrendererfactory.h" #include "htmlblock.h" #include "utils/iconnamecache.h" #include "utils/mimetype.h" #include "viewer/attachmentstrategy.h" #include "viewer/csshelperbase.h" #include "messagepartrenderermanager.h" #include "htmlwriter/bufferedhtmlwriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; using namespace MessageViewer; Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient) Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(const QGpgME::Protocol *) static const int SIG_FRAME_COL_UNDEF = 99; #define SIG_FRAME_COL_RED -1 #define SIG_FRAME_COL_YELLOW 0 #define SIG_FRAME_COL_GREEN 1 QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos) { // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. showKeyInfos = true; QString result; if (cryptProto) { if (cryptProto == QGpgME::openpgp()) { // process enum according to it's definition to be read in // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h switch (status_code) { case 0: // GPGME_SIG_STAT_NONE result = i18n("Error: Signature not verified"); frameColor = SIG_FRAME_COL_YELLOW; break; case 1: // GPGME_SIG_STAT_GOOD result = i18n("Good signature"); frameColor = SIG_FRAME_COL_GREEN; break; case 2: // GPGME_SIG_STAT_BAD result = i18n("Bad signature"); frameColor = SIG_FRAME_COL_RED; break; case 3: // GPGME_SIG_STAT_NOKEY result = i18n("No public key to verify the signature"); frameColor = SIG_FRAME_COL_RED; break; case 4: // GPGME_SIG_STAT_NOSIG result = i18n("No signature found"); frameColor = SIG_FRAME_COL_RED; break; case 5: // GPGME_SIG_STAT_ERROR result = i18n("Error verifying the signature"); frameColor = SIG_FRAME_COL_RED; break; case 6: // GPGME_SIG_STAT_DIFF result = i18n("Different results for signatures"); frameColor = SIG_FRAME_COL_RED; break; /* PENDING(khz) Verify exact meaning of the following values: case 7: // GPGME_SIG_STAT_GOOD_EXP return i18n("Signature certificate is expired"); break; case 8: // GPGME_SIG_STAT_GOOD_EXPKEY return i18n("One of the certificate's keys is expired"); break; */ default: result.clear(); // do *not* return a default text here ! break; } } else if (cryptProto == QGpgME::smime()) { // process status bits according to SigStatus_... // definitions in kdenetwork/libkdenetwork/cryptplug.h if (summary == GpgME::Signature::None) { result = i18n("No status information available."); frameColor = SIG_FRAME_COL_YELLOW; showKeyInfos = false; return result; } if (summary & GpgME::Signature::Valid) { result = i18n("Good signature."); // Note: // Here we are work differently than KMail did before! // // The GOOD case ( == sig matching and the complete // certificate chain was verified and is valid today ) // by definition does *not* show any key // information but just states that things are OK. // (khz, according to LinuxTag 2002 meeting) frameColor = SIG_FRAME_COL_GREEN; showKeyInfos = false; return result; } // we are still there? OK, let's test the different cases: // we assume green, test for yellow or red (in this order!) frameColor = SIG_FRAME_COL_GREEN; QString result2; if (summary & GpgME::Signature::KeyExpired) { // still is green! result2 = i18n("One key has expired."); } if (summary & GpgME::Signature::SigExpired) { // and still is green! result2 += i18n("The signature has expired."); } // test for yellow: if (summary & GpgME::Signature::KeyMissing) { result2 += i18n("Unable to verify: key missing."); // if the signature certificate is missing // we cannot show information on it showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlMissing) { result2 += i18n("CRL not available."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlTooOld) { result2 += i18n("Available CRL is too old."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::BadPolicy) { result2 += i18n("A policy was not met."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::SysError) { result2 += i18n("A system error occurred."); // if a system error occurred // we cannot trust any information // that was given back by the plug-in showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } // test for red: if (summary & GpgME::Signature::KeyRevoked) { // this is red! result2 += i18n("One key has been revoked."); frameColor = SIG_FRAME_COL_RED; } if (summary & GpgME::Signature::Red) { if (result2.isEmpty()) { // Note: // Here we are work differently than KMail did before! // // The BAD case ( == sig *not* matching ) // by definition does *not* show any key // information but just states that things are BAD. // // The reason for this: In this case ALL information // might be falsificated, we can NOT trust the data // in the body NOT the signature - so we don't show // any key/signature information at all! // (khz, according to LinuxTag 2002 meeting) showKeyInfos = false; } frameColor = SIG_FRAME_COL_RED; } else { result.clear(); } if (SIG_FRAME_COL_GREEN == frameColor) { result = i18n("Good signature."); } else if (SIG_FRAME_COL_RED == frameColor) { result = i18n("Bad signature."); } else { result.clear(); } if (!result2.isEmpty()) { if (!result.isEmpty()) { result.append(QLatin1String("
")); } result.append(result2); } } /* // add i18n support for 3rd party plug-ins here: else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) { } */ } return result; } /** Checks whether @p str contains external references. To be precise, we only check whether @p str contains 'xxx="http[s]:' where xxx is not href. Obfuscated external references are ignored on purpose. */ bool containsExternalReferences(const QString &str, const QString &extraHead) { const bool hasBaseInHeader = extraHead.contains(QLatin1String( "= 0 || httpsPos >= 0) { // pos = index of next occurrence of "http: or "https: whichever comes first int pos = (httpPos < httpsPos) ? ((httpPos >= 0) ? httpPos : httpsPos) : ((httpsPos >= 0) ? httpsPos : httpPos); // look backwards for "href" if (pos > 5) { int hrefPos = str.lastIndexOf(QLatin1String("href"), pos - 5, Qt::CaseInsensitive); // if no 'href' is found or the distance between 'href' and '"http[s]:' // is larger than 7 (7 is the distance in 'href = "http[s]:') then // we assume that we have found an external reference if ((hrefPos == -1) || (pos - hrefPos > 7)) { // HTML messages created by KMail itself for now contain the following: // // Make sure not to show an external references warning for this string int dtdPos = str.indexOf(QLatin1String( "http://www.w3.org/TR/html4/loose.dtd"), pos + 1); if (dtdPos != (pos + 1)) { return true; } } } // find next occurrence of "http: or "https: if (pos == httpPos) { httpPos = str.indexOf(QLatin1String("\"http:"), httpPos + 6, Qt::CaseInsensitive); } else { httpsPos = str.indexOf(QLatin1String("\"https:"), httpsPos + 7, Qt::CaseInsensitive); } } return false; } // FIXME this used to go through the full webkit parser to extract the body and head blocks // until we have that back, at least attempt to fix some of the damage // yes, "parsing" HTML with regexps is very very wrong, but it's still better than not filtering // this at all... QString processHtml(const QString &htmlSource, QString &extraHead) { QString s = htmlSource.trimmed(); s = s.remove(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive)).trimmed(); s = s.remove(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive)).trimmed(); // head s = s.replace(QRegExp(QStringLiteral("^"), Qt::CaseInsensitive), QString()).trimmed(); const int startIndex = s.indexOf(QLatin1String(""), Qt::CaseInsensitive); if (startIndex >= 0) { const auto endIndex = s.indexOf(QLatin1String(""), Qt::CaseInsensitive); if (endIndex < 0) { return htmlSource; } extraHead = s.mid(startIndex + 6, endIndex - startIndex - 6); //Remove this hack with https://codereview.qt-project.org/#/c/256100/2 is merged //Don't authorize to refresh content. if (MessageViewer::Util::excludeExtraHeader(s)) { extraHead.clear(); } s = s.mid(endIndex + 7).trimmed(); } // body s = s.remove(QRegExp(QStringLiteral("]*>"), Qt::CaseInsensitive)).trimmed(); s = s.remove(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive)).trimmed(); s = s.remove(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive)).trimmed(); return s; } DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory) : mCSSHelper(cssHelper) , mRendererFactory(rendererFactory) { } DefaultRendererPrivate::~DefaultRendererPrivate() { } CSSHelperBase *DefaultRendererPrivate::cssHelper() const { return mCSSHelper; } Interface::ObjectTreeSource *DefaultRendererPrivate::source() const { return mMsgPart->source(); } void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { for (const auto &m : msgPart->subParts()) { renderFactory(m, htmlWriter); } } void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr rBlock; HTMLBlock::Ptr aBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; HTMLBlock::Ptr rBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter) { if (!mp->hasSubParts()) { return; } Grantlee::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral(":/encapsulatedrfc822messagepart.html")); Grantlee::Context c = MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("link", mp->nodeHelper()->asHREF(mp->message().data(), QStringLiteral("body"))); c.insert(QStringLiteral("msgHeader"), mCreateMessageHeader(mp->message().data())); c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderSubParts(mp, htmlWriter); })); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/htmlmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); auto preferredMode = mp->source()->preferredMode(); const bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml); block.setProperty("htmlMail", isHtmlPreferred); block.setProperty("loadExternal", htmlLoadExternal()); block.setProperty("isPrinting", isPrinting()); { QString extraHead; //laurent: FIXME port to async method webengine QString bodyText = processHtml(mp->bodyHtml(), extraHead); if (isHtmlPreferred) { mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true); htmlWriter->extraHead(extraHead); } block.setProperty("containsExternalReferences", containsExternalReferences(bodyText, extraHead)); c.insert(QStringLiteral("content"), bodyText); } { ConvertHtmlToPlainText convert; convert.setHtmlString(mp->bodyHtml()); QString plaintext = convert.generatePlainText(); plaintext.replace(QLatin1Char('\n'), QStringLiteral("
")); c.insert(QStringLiteral("plaintext"), plaintext); } mp->source()->setHtmlMode(MimeTreeParser::Util::Html, QList() << MimeTreeParser::Util::Html << MimeTreeParser::Util::Normal); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/encryptedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node || mp->hasSubParts()) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->content() && mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->cryptoProto())); if (!mp->decryptRecipients().empty()) { c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->decryptRecipients())); } c.insert(QStringLiteral("block"), &block); block.setProperty("isPrinting", isPrinting()); block.setProperty("detailHeader", showEncryptionDetails()); block.setProperty("inProgress", metaData.inProgress); block.setProperty("isDecrypted", mp->decryptMessage()); block.setProperty("isDecryptable", metaData.isDecryptable); block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral( "document-decrypt"), KIconLoader::Small)).url()); block.setProperty("errorText", metaData.errorText); block.setProperty("noSecKey", mp->isNoSecKey()); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); auto cryptoProto = mp->cryptoProto(); const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime()); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/signedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto)); c.insert(QStringLiteral("block"), &block); block.setProperty("inProgress", metaData.inProgress); block.setProperty("errorText", metaData.errorText); block.setProperty("detailHeader", showSignatureDetails()); block.setProperty("isPrinting", isPrinting()); block.setProperty("addr", metaData.signerMailAddresses.join(QLatin1Char(','))); block.setProperty("technicalProblem", metaData.technicalProblem); block.setProperty("keyId", metaData.keyId); if (metaData.creationTime.isValid()) { //should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475 block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat)); } block.setProperty("isGoodSignature", metaData.isGoodSignature); block.setProperty("isSMIME", isSMIME); if (metaData.keyTrust == GpgME::Signature::Unknown) { block.setProperty("keyTrust", QStringLiteral("unknown")); } else if (metaData.keyTrust == GpgME::Signature::Marginal) { block.setProperty("keyTrust", QStringLiteral("marginal")); } else if (metaData.keyTrust == GpgME::Signature::Full) { block.setProperty("keyTrust", QStringLiteral("full")); } else if (metaData.keyTrust == GpgME::Signature::Ultimate) { block.setProperty("keyTrust", QStringLiteral("ultimate")); } else { block.setProperty("keyTrust", QStringLiteral("untrusted")); } QString startKeyHREF; { QString keyWithWithoutURL; if (cryptoProto) { startKeyHREF = QStringLiteral("") .arg(cryptoProto->displayName(), cryptoProto->name(), QString::fromLatin1(metaData.keyId)); keyWithWithoutURL = QStringLiteral("%1%2").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral( "0x") + metaData.keyId))); } else { keyWithWithoutURL = QStringLiteral("0x") + QString::fromUtf8(metaData.keyId); } block.setProperty("keyWithWithoutURL", keyWithWithoutURL); } bool onlyShowKeyURL = false; bool showKeyInfos = false; bool cannotCheckSignature = true; QString signer = metaData.signer; QString statusStr; QString mClass; QString greenCaseWarning; if (metaData.inProgress) { mClass = QStringLiteral("signInProgress"); } else { const QStringList &blockAddrs(metaData.signerMailAddresses); // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. int frameColor = SIG_FRAME_COL_UNDEF; statusStr = sigStatusToString(cryptoProto, metaData.status_code, metaData.sigSummary, frameColor, showKeyInfos); // if needed fallback to english status text // that was reported by the plugin if (statusStr.isEmpty()) { statusStr = metaData.status; } if (metaData.technicalProblem) { frameColor = SIG_FRAME_COL_YELLOW; } switch (frameColor) { case SIG_FRAME_COL_RED: cannotCheckSignature = false; break; case SIG_FRAME_COL_YELLOW: cannotCheckSignature = true; break; case SIG_FRAME_COL_GREEN: cannotCheckSignature = false; break; } // temporary hack: always show key information! showKeyInfos = true; if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) { switch (frameColor) { case SIG_FRAME_COL_RED: mClass = QStringLiteral("signErr"); onlyShowKeyURL = true; break; case SIG_FRAME_COL_YELLOW: if (metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { mClass = QStringLiteral("signOkKeyBad"); } break; case SIG_FRAME_COL_GREEN: mClass = QStringLiteral("signOkKeyOk"); // extra hint for green case // that email addresses in DN do not match fromAddress QString msgFrom(KEmailAddress::extractEmailAddress(mp->fromAddress())); QString certificate; if (metaData.keyId.isEmpty()) { certificate = i18n("certificate"); } else { certificate = startKeyHREF + i18n("certificate") + QStringLiteral(""); } if (!blockAddrs.empty()) { if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n( "Sender's mail address is not stored in the %1 used for signing.", certificate) +QStringLiteral("
") +i18n("sender: ") +msgFrom +QStringLiteral("
") +i18n("stored: "); // We cannot use Qt's join() function here but // have to join the addresses manually to // extract the mail addresses (without '<''>') // before including it into our string: bool bStart = true; QStringList::ConstIterator end(blockAddrs.constEnd()); for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != end; ++it) { if (!bStart) { greenCaseWarning.append(QLatin1String(",
   ")); } bStart = false; greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it)); } } } else { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n("No mail address is stored in the %1 used for signing, " "so we cannot compare it to the sender's address %2.", certificate, msgFrom); } break; } if (showKeyInfos && !cannotCheckSignature) { if (metaData.signer.isEmpty()) { signer.clear(); } else { if (!blockAddrs.empty()) { const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first()); signer = QStringLiteral("%2").arg(QLatin1String(QUrl :: toPercentEncoding( address . path())), signer); } } } } else { if (metaData.signer.isEmpty() || metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { // HTMLize the signer's user id and create mailto: link signer = MessageCore::StringUtil::quoteHtmlChars(signer, true); signer = QStringLiteral("%1").arg(signer); if (metaData.isGoodSignature) { if (metaData.keyTrust < GpgME::Signature::Marginal) { mClass = QStringLiteral("signOkKeyBad"); } else { mClass = QStringLiteral("signOkKeyOk"); } } else { mClass = QStringLiteral("signErr"); } } } } block.setProperty("onlyShowKeyURL", onlyShowKeyURL); block.setProperty("showKeyInfos", showKeyInfos); block.setProperty("cannotCheckSignature", cannotCheckSignature); block.setProperty("signer", signer); block.setProperty("statusStr", statusStr); block.setProperty("signClass", mClass); block.setProperty("greenCaseWarning", greenCaseWarning); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isSigned || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSigned(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isEncrypted || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderEncrypted(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } auto mode = mp->preferredMode(); if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) { for (const auto m : mp->availableModes()) { if (m != MimeTreeParser::Util::MultipartPlain) { mode = m; break; } } } MimeMessagePart::Ptr part(mp->childParts().first()); if (mp->childParts().contains(mode)) { part = mp->childParts()[mode]; } render(part, htmlWriter); } void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const GpgME::ImportResult &importResult(mp->importResult()); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/certmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString())); block.setProperty("nImp", importResult.numImported()); block.setProperty("nUnc", importResult.numUnchanged()); block.setProperty("nSKImp", importResult.numSecretKeysImported()); block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged()); QVariantList keylist; const auto imports = importResult.imports(); auto end(imports.end()); for (auto it = imports.begin(); it != end; ++it) { QObject *key(new QObject(mp.data())); key->setProperty("error", QString::fromLocal8Bit((*it).error().asString())); key->setProperty("status", (*it).status()); key->setProperty("fingerprint", QLatin1String((*it).fingerprint())); keylist << QVariant::fromValue(key); } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { if (!mRendererFactory) { return false; } for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) { if (r->render(msgPart, htmlWriter, this)) { return true; } } return false; } void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { const QString className = QString::fromUtf8(msgPart->metaObject()->className()); if (isHiddenHint(msgPart)) { QByteArray cid = msgPart->content()->contentID()->identifier(); auto mp = msgPart.dynamicCast(); if (!cid.isEmpty() && mp) { QString fileName = mp->temporaryFilePath(); QString href = QUrl::fromLocalFile(fileName).url(); htmlWriter->embedPart(cid, href); } } if (renderWithFactory(msgPart, htmlWriter)) { return; } if (className == QStringLiteral("MimeTreeParser::MessagePartList")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::MimeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncapsulatedRfc822MessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::HtmlMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::SignedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncryptedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::AlternativeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::CertMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else { qCWarning(MESSAGEVIEWER_LOG) << "We got a unknown classname, using default behaviour for " << className; } } bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart) { auto mp = msgPart.dynamicCast(); auto content = msgPart->content(); if (!mp || !content) { return false; } if (mShowOnlyOneMimePart && mMsgPart.data() == msgPart->parentPart()) { if (mMsgPart->subParts().at(0) == msgPart.data()) { return false; } } if (msgPart->nodeHelper()->isNodeDisplayedHidden(content)) { return true; } const AttachmentStrategy *const as = mAttachmentStrategy; const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml); QByteArray mediaType("text"); if (content->contentType(false) && !content->contentType()->mediaType().isEmpty() && !content->contentType()->subType().isEmpty()) { mediaType = content->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!mp->neverDisplayInline()) { if (as) { defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon; } } // neither image nor text -> show as icon if (!mp->isImage() && !isTextPart) { defaultAsIcon = true; } bool hidden(false); if (isTextPart) { hidden = defaultHidden; } else { if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") { hidden = true; } else { hidden = defaultHidden && content->parent(); hidden |= defaultAsIcon && defaultHidden; } } msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden); return hidden; } MimeTreeParser::IconType DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart) { auto mp = msgPart.dynamicCast(); auto content = msgPart->content(); if (!content || !mp) { return MimeTreeParser::IconType::NoIcon; } const AttachmentStrategy *const as = mAttachmentStrategy; const bool defaultDisplayHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None); const bool defaultDisplayInline(as && as->defaultDisplay(content) == AttachmentStrategy::Inline); const bool defaultDisplayAsIcon(as && as->defaultDisplay(content) == AttachmentStrategy::AsIcon); const bool showOnlyOneMimePart(mShowOnlyOneMimePart); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml); QByteArray mediaType("text"); if (content->contentType(false) && !content->contentType()->mediaType().isEmpty() && !content->contentType()->subType().isEmpty()) { mediaType = content->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!mp->neverDisplayInline()) { if (as) { defaultAsIcon = defaultDisplayAsIcon; } } if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) { defaultAsIcon = false; } // neither image nor text -> show as icon if (!mp->isImage() && !isTextPart) { defaultAsIcon = true; } if (isTextPart) { if (as && !defaultDisplayInline) { return MimeTreeParser::IconExternal; } return MimeTreeParser::NoIcon; } else { if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") { return MimeTreeParser::IconInline; } if (defaultDisplayHidden && !showOnlyOneMimePart && content->parent()) { return MimeTreeParser::IconInline; } if (defaultAsIcon) { return MimeTreeParser::IconExternal; } else if (mp->isImage()) { return MimeTreeParser::IconInline; } } return MimeTreeParser::NoIcon; } bool DefaultRendererPrivate::showEmoticons() const { return mShowEmoticons; } bool DefaultRendererPrivate::isPrinting() const { return mIsPrinting; } bool DefaultRendererPrivate::htmlLoadExternal() const { return mHtmlLoadExternal; } bool DefaultRendererPrivate::showExpandQuotesMark() const { return mShowExpandQuotesMark; } bool DefaultRendererPrivate::showOnlyOneMimePart() const { return mShowOnlyOneMimePart; } bool DefaultRendererPrivate::showSignatureDetails() const { return mShowSignatureDetails; } bool DefaultRendererPrivate::showEncryptionDetails() const { return mShowEncryptionDetails; } int DefaultRendererPrivate::levelQuote() const { return mLevelQuote; } DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper) : d(new DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance())) { } DefaultRenderer::~DefaultRenderer() { delete d; } void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart) { d->mShowOnlyOneMimePart = onlyOneMimePart; } void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy) { d->mAttachmentStrategy = strategy; } void DefaultRenderer::setShowEmoticons(bool showEmoticons) { d->mShowEmoticons = showEmoticons; } void DefaultRenderer::setIsPrinting(bool isPrinting) { d->mIsPrinting = isPrinting; } void DefaultRenderer::setShowExpandQuotesMark(bool showExpandQuotesMark) { d->mShowExpandQuotesMark = showExpandQuotesMark; } void DefaultRenderer::setShowEncryptionDetails(bool showEncryptionDetails) { d->mShowEncryptionDetails = showEncryptionDetails; } void DefaultRenderer::setShowSignatureDetails(bool showSignatureDetails) { d->mShowSignatureDetails = showSignatureDetails; } void DefaultRenderer::setLevelQuote(int levelQuote) { d->mLevelQuote = levelQuote; } void DefaultRenderer::setHtmlLoadExternal(bool htmlLoadExternal) { d->mHtmlLoadExternal = htmlLoadExternal; } -void DefaultRenderer::setCreateMessageHeader(std::function createMessageHeader) +void DefaultRenderer::setCreateMessageHeader(const std::function &createMessageHeader) { d->mCreateMessageHeader = createMessageHeader; } QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent) { QString ret = QStringLiteral("%1 * %3\n").arg(indent, QString::fromUtf8(messagePart->metaObject()->className())); indent += QLatin1Char(' '); for (const auto &subPart : messagePart->subParts()) { ret += renderTreeHelper(subPart, indent); } return ret; } void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer) { qCDebug(MESSAGEVIEWER_LOG) << "MimeTreeParser structure:"; qCDebug(MESSAGEVIEWER_LOG) << qPrintable(renderTreeHelper(msgPart, QString())); d->mMsgPart = msgPart; d->renderFactory(d->mMsgPart, writer); } diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.h b/messageviewer/src/messagepartthemes/default/defaultrenderer.h index ec56ed03..4d68eb5f 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer.h +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.h @@ -1,63 +1,63 @@ /* 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. */ #ifndef MESSAGEVIEWER_DEFAULTRENDERER_H #define MESSAGEVIEWER_DEFAULTRENDERER_H #include #include namespace KMime { class Message; } namespace MimeTreeParser { class MessagePart; typedef QSharedPointer MessagePartPtr; } namespace MessageViewer { class DefaultRendererPrivate; class HtmlWriter; class AttachmentStrategy; class CSSHelperBase; class DefaultRenderer { public: explicit DefaultRenderer(CSSHelperBase *cssHelder); ~DefaultRenderer(); void setShowOnlyOneMimePart(bool onlyOneMimePart); void setAttachmentStrategy(const AttachmentStrategy *strategy); void setShowEmoticons(bool showEmoticons); void setIsPrinting(bool isPrinting); void setShowExpandQuotesMark(bool showExpandQuotesMark); void setShowSignatureDetails(bool showSignatureDetails); void setLevelQuote(int levelQuote); void setHtmlLoadExternal(bool htmlLoadExternal); - void setCreateMessageHeader(std::function); + void setCreateMessageHeader(const std::function&); void render(const MimeTreeParser::MessagePartPtr &msgPart, HtmlWriter *writer); void setShowEncryptionDetails(bool showEncryptionDetails); private: DefaultRendererPrivate *d; }; } #endif //__MESSAGEVIEWER_MAILRENDERER_H diff --git a/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp b/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp index 029525a4..da6e8329 100644 --- a/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp +++ b/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp @@ -1,310 +1,310 @@ /* 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 "quotehtml.h" #include "utils/iconnamecache.h" #include "viewer/csshelperbase.h" #include #include #include #include /** Check if the newline at position @p newLinePos in string @p s seems to separate two paragraphs (important for correct BiDi behavior, but is heuristic because paragraphs are not well-defined) */ // Guesstimate if the newline at newLinePos actually separates paragraphs in the text s // We use several heuristics: // 1. If newLinePos points after or before (=at the very beginning of) text, it is not between paragraphs // 2. If the previous line was longer than the wrap size, we want to consider it a paragraph on its own // (some clients, notably Outlook, send each para as a line in the plain-text version). // 3. Otherwise, we check if the newline could have been inserted for wrapping around; if this // was the case, then the previous line will be shorter than the wrap size (which we already // know because of item 2 above), but adding the first word from the next line will make it // longer than the wrap size. bool looksLikeParaBreak(const QString &s, int newLinePos) { const int WRAP_COL = 78; int length = s.length(); // 1. Is newLinePos at an end of the text? if (newLinePos >= length - 1 || newLinePos == 0) { return false; } // 2. Is the previous line really a paragraph -- longer than the wrap size? // First char of prev line -- works also for first line int prevStart = s.lastIndexOf(QLatin1Char('\n'), newLinePos - 1) + 1; int prevLineLength = newLinePos - prevStart; if (prevLineLength > WRAP_COL) { return true; } // find next line to delimit search for first word int nextStart = newLinePos + 1; int nextEnd = s.indexOf(QLatin1Char('\n'), nextStart); if (nextEnd == -1) { nextEnd = length; } QString nextLine = s.mid(nextStart, nextEnd - nextStart); length = nextLine.length(); // search for first word in next line int wordStart; bool found = false; for (wordStart = 0; !found && wordStart < length; wordStart++) { switch (nextLine[wordStart].toLatin1()) { case '>': case '|': case ' ': // spaces, tabs and quote markers don't count case '\t': case '\r': break; default: found = true; break; } } /* for() */ if (!found) { // next line is essentially empty, it seems -- empty lines are // para separators return true; } //Find end of first word. //Note: flowText (in kmmessage.cpp) separates words for wrap by //spaces only. This should be consistent, which calls for some //refactoring. int wordEnd = nextLine.indexOf(QLatin1Char(' '), wordStart); if (wordEnd == (-1)) { wordEnd = length; } int wordLength = wordEnd - wordStart; // 3. If adding a space and the first word to the prev line don't // make it reach the wrap column, then the break was probably // meaningful return prevLineLength + wordLength + 1 < WRAP_COL; } void quotedHTML(const QString &s, MessageViewer::RenderContext *context, MessageViewer::HtmlWriter *htmlWriter) { const auto cssHelper = context->cssHelper(); Q_ASSERT(cssHelper); KTextToHTML::Options convertFlags = KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText | KTextToHTML::ConvertPhoneNumbers; if (context->showEmoticons()) { convertFlags |= KTextToHTML::ReplaceSmileys; } const QString normalStartTag = cssHelper->nonQuotedFontTag(); QString quoteFontTag[3]; QString deepQuoteFontTag[3]; for (int i = 0; i < 3; ++i) { quoteFontTag[i] = cssHelper->quoteFontTag(i); deepQuoteFontTag[i] = cssHelper->quoteFontTag(i + 3); } const QString normalEndTag = QStringLiteral(""); const QString quoteEnd = QStringLiteral(""); const int length = s.length(); bool paraIsRTL = false; bool startNewPara = true; int pos, beg; // skip leading empty lines for (pos = 0; pos < length && s[pos] <= QLatin1Char(' '); ++pos) { } while (pos > 0 && (s[pos - 1] == QLatin1Char(' ') || s[pos - 1] == QLatin1Char('\t'))) { pos--; } beg = pos; int currQuoteLevel = -2; // -2 == no previous lines bool curHidden = false; // no hide any block QString collapseIconPath; QString expandIconPath; if (context->showExpandQuotesMark()) { collapseIconPath = MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral( "quotecollapse.png")); expandIconPath = MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral( "quoteexpand.png")); } int previousQuoteDepth = -1; while (beg < length) { /* search next occurrence of '\n' */ pos = s.indexOf(QLatin1Char('\n'), beg, Qt::CaseInsensitive); if (pos == -1) { pos = length; } QString line(s.mid(beg, pos - beg)); beg = pos + 1; bool foundQuote = false; /* calculate line's current quoting depth */ int actQuoteLevel = -1; const int numberOfCaracters(line.length()); int quoteLength = 0; for (int p = 0; p < numberOfCaracters; ++p) { switch (line[p].toLatin1()) { case '>': case '|': if (p == 0 || foundQuote) { actQuoteLevel++; quoteLength = p; foundQuote = true; } break; case ' ': // spaces and tabs are allowed between the quote markers case '\t': case '\r': quoteLength = p; break; default: // stop quoting depth calculation p = numberOfCaracters; break; } } /* for() */ if (!foundQuote) { quoteLength = 0; } bool actHidden = false; // This quoted line needs be hidden if (context->showExpandQuotesMark() && context->levelQuote() >= 0 && context->levelQuote() <= actQuoteLevel) { actHidden = true; } if (actQuoteLevel != currQuoteLevel) { /* finish last quotelevel */ if (currQuoteLevel == -1) { htmlWriter->write(normalEndTag); } else if (currQuoteLevel >= 0 && !curHidden) { htmlWriter->write(quoteEnd); } //Close blockquote if (previousQuoteDepth > actQuoteLevel) { htmlWriter->write(cssHelper->addEndBlockQuote(previousQuoteDepth - actQuoteLevel)); } /* start new quotelevel */ if (actQuoteLevel == -1) { htmlWriter->write(normalStartTag); } else { if (context->showExpandQuotesMark()) { // Add blockquote if (previousQuoteDepth < actQuoteLevel) { htmlWriter->write(cssHelper->addStartBlockQuote(actQuoteLevel - previousQuoteDepth)); } if (actHidden) { //only show the QuoteMark when is the first line of the level hidden if (!curHidden) { //Expand all quotes - htmlWriter->write(QLatin1String("
")); + htmlWriter->write(QStringLiteral("
")); htmlWriter->write(QStringLiteral("" "") .arg(-1) .arg(expandIconPath)); - htmlWriter->write(QLatin1String("

")); + htmlWriter->write(QStringLiteral("

")); } } else { - htmlWriter->write(QLatin1String("
")); + htmlWriter->write(QStringLiteral("
")); htmlWriter->write(QStringLiteral("" "") .arg(actQuoteLevel) .arg(collapseIconPath)); - htmlWriter->write(QLatin1String("
")); + htmlWriter->write(QStringLiteral("
")); if (actQuoteLevel < 3) { htmlWriter->write(quoteFontTag[actQuoteLevel]); } else { htmlWriter->write(deepQuoteFontTag[actQuoteLevel % 3]); } } } else { // Add blockquote if (previousQuoteDepth < actQuoteLevel) { htmlWriter->write(cssHelper->addStartBlockQuote(actQuoteLevel - previousQuoteDepth)); } if (actQuoteLevel < 3) { htmlWriter->write(quoteFontTag[actQuoteLevel]); } else { htmlWriter->write(deepQuoteFontTag[actQuoteLevel % 3]); } } } currQuoteLevel = actQuoteLevel; } curHidden = actHidden; if (!actHidden) { // don't write empty
blocks (they have zero height) // ignore ^M DOS linebreaks if (!line.remove(QLatin1Char('\015')).isEmpty()) { if (startNewPara) { paraIsRTL = line.isRightToLeft(); } htmlWriter->write(QStringLiteral("
") .arg(paraIsRTL ? QStringLiteral("rtl") : QStringLiteral("ltr"))); // if quoteLengh == 0 && foundQuote => a simple quote if (foundQuote) { quoteLength++; const int rightString = (line.length()) - quoteLength; if (rightString > 0) { htmlWriter->write(QStringLiteral("%1") .arg(line.left(quoteLength))); htmlWriter->write(QStringLiteral("") .arg(cssHelper->quoteColorName(actQuoteLevel))); htmlWriter->write(KTextToHTML::convertToHtml(line.right(rightString), convertFlags, 4096, 512)); htmlWriter->write(QStringLiteral("")); } else { htmlWriter->write(QStringLiteral("%1") .arg(line.left(quoteLength))); } } else { htmlWriter->write(KTextToHTML::convertToHtml(line, convertFlags, 4096, 512)); } - htmlWriter->write(QLatin1String("
")); + htmlWriter->write(QStringLiteral("")); startNewPara = looksLikeParaBreak(s, pos); } else { - htmlWriter->write(QLatin1String("
")); + htmlWriter->write(QStringLiteral("
")); // after an empty line, always start a new paragraph startNewPara = true; } } previousQuoteDepth = actQuoteLevel; } /* while() */ /* really finish the last quotelevel */ if (currQuoteLevel == -1) { htmlWriter->write(normalEndTag); } else { htmlWriter->write(quoteEnd + cssHelper->addEndBlockQuote(currQuoteLevel + 1)); } } diff --git a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp index 50fbc10b..160958f1 100644 --- a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp +++ b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp @@ -1,177 +1,177 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "scamdetectionwebenginetest.h" #include "../scamdetectionwebengine.h" #include #include #include #include TestWebEngineScamDetection::TestWebEngineScamDetection(QWidget *parent) : QWidget(parent) { QHBoxLayout *hbox = new QHBoxLayout(this); mEngineView = new QWebEngineView(this); mScamDetectionWebEngine = new MessageViewer::ScamDetectionWebEngine(this); connect(mScamDetectionWebEngine, &MessageViewer::ScamDetectionWebEngine::resultScanDetection, this, &TestWebEngineScamDetection::resultScanDetection); connect(mEngineView, &QWebEngineView::loadFinished, this, &TestWebEngineScamDetection::loadFinished); hbox->addWidget(mEngineView); } TestWebEngineScamDetection::~TestWebEngineScamDetection() { } void TestWebEngineScamDetection::setHtml(const QString &html) { mEngineView->setHtml(html); } void TestWebEngineScamDetection::loadFinished(bool b) { Q_UNUSED(b); mScamDetectionWebEngine->scanPage(mEngineView->page()); } ScamDetectionWebEngineTest::ScamDetectionWebEngineTest(QObject *parent) : QObject(parent) { } ScamDetectionWebEngineTest::~ScamDetectionWebEngineTest() { } void ScamDetectionWebEngineTest::scamtest_data() { QTest::addColumn("html"); QTest::addColumn("result"); //No Scam QTest::newRow("noscam1") << QStringLiteral( "kde") << false; QTest::newRow("noscam2") << QStringLiteral( "kde") << false; QTest::newRow("noscam3") << QStringLiteral( "kde") << false; //Hexa value QTest::newRow("hexavalue") << QStringLiteral( "test") << true; //Ip QTest::newRow("Ip value") << QStringLiteral( "test") << false; QTest::newRow("Ip scam1") << QStringLiteral( "test") << true; QTest::newRow("Ip scam2") << QStringLiteral( "test") << true; //Href no scam QTest::newRow("Href no scam") << QStringLiteral( "test") << false; //Redirect href QTest::newRow("Redirect scam") << QStringLiteral( "test") << true; QTest::newRow("Redirect no scam") << QStringLiteral( "test") << false; //Numeric value QTest::newRow("numeric no scam") << QStringLiteral( "http://baseball2.2ndhalfplays.com/nested/attribs") << false; QTest::newRow("numeric scam1") << QStringLiteral( "test") << true; QTest::newRow("numeric scam2") << QStringLiteral( "test") << true; QTest::newRow("numeric scam3") << QStringLiteral( "test") << true; QTest::newRow("numeric scam4") << QStringLiteral( "test") << true; QTest::newRow("numeric scam5") << QStringLiteral( "http://baseball2.2ndhalfplays.com/nested/attribs") << true; QTest::newRow("scam") << QStringLiteral( "https://www.bli.com/manager/dedicated/index.html#/billing/mean") << true; QTest::newRow("scam-amp") << QStringLiteral( "https://bugs.kde.org/enter_bug.cgi?format=guided&amp;product=gcompris") << false; QTest::newRow("scam-encoded-url1") << QStringLiteral( "https://github.com/KDAB/KDStateMachineEditor.git|1.2") << false; QTest::newRow("scam-lowercase") << QStringLiteral( "http://www.Kde.org") << false; QTest::newRow("scam-lowercase-2") << QStringLiteral( "http://www.Kde.org/KDE/bla") << false; QTest::newRow("scam-lowercase-3") << QStringLiteral( "http://code.qt.io/cgit/%7bnon-gerrit%7d/qt-labs/opencl.git") << false; QTest::newRow("toplevelrepo") << QStringLiteral( "https://www.amazon.fr/gp/../gp/goldbox/ref=pe_btn/?nocache=1510065600354") << false; QTest::newRow("toplevelrepo2") << QStringLiteral( "https://www.amazon.fr/gp/goldbox/ref=pe_btn/?nocache=1510065600354") << false; QTest::newRow("toplevelrepo3") << QStringLiteral( "https://www.amazon.fr/gp/../gp/goldbox/ref=pe_d//") << false; #if 0 QTest::newRow("wierd1") << QStringLiteral( "http://www.weezevent.com?c=sys_mail") << false; #endif } void ScamDetectionWebEngineTest::scamtest() { QFETCH(QString, html); QFETCH(bool, result); TestWebEngineScamDetection scamDetection; - QSignalSpy scamDetectionSpy(&scamDetection, SIGNAL(resultScanDetection(bool))); + QSignalSpy scamDetectionSpy(&scamDetection, &TestWebEngineScamDetection::resultScanDetection); scamDetection.setHtml(html); QVERIFY(scamDetectionSpy.wait()); QCOMPARE(scamDetectionSpy.count(), 1); const bool scamResult = scamDetectionSpy.at(0).at(0).toBool(); QCOMPARE(scamResult, result); } QTEST_MAIN(ScamDetectionWebEngineTest) diff --git a/messageviewer/src/viewer/csshelperbase.cpp b/messageviewer/src/viewer/csshelperbase.cpp index 6f21434e..b08dbbb2 100644 --- a/messageviewer/src/viewer/csshelperbase.cpp +++ b/messageviewer/src/viewer/csshelperbase.cpp @@ -1,759 +1,755 @@ /* csshelper.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz 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. */ #include "csshelperbase.h" #include "header/headerstyleplugin.h" #include "utils/iconnamecache.h" #include #include #include #include #define USE_HTML_STYLE_COLOR 1 namespace MessageViewer { namespace { // some QColor manipulators that hide the ugly QColor API w.r.t. HSV: inline QColor darker(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv(h, s, v * 4 / 5); } inline QColor desaturate(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv(h, s / 8, v); } inline QColor fixValue(const QColor &c, int newV) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv(h, s, newV); } inline int getValueOf(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return v; } } CSSHelperBase::CSSHelperBase(const QPaintDevice *pd) : mRecycleQuoteColors(false) , mShrinkQuotes(false) , cHtmlWarning(QColor(0xFF, 0x40, 0x40)) , mPaintDevice(pd) { recalculatePGPColors(); const QString imgSrcShow = QStringLiteral("quicklistClosed.png"); const QString imgSrcHide = QStringLiteral("quicklistOpened.png"); imgShowUrl = QUrl::fromLocalFile(MessageViewer::IconNameCache::instance()->iconPathFromLocal(imgSrcShow)).url(); imgHideUrl = QUrl::fromLocalFile(MessageViewer::IconNameCache::instance()->iconPathFromLocal(imgSrcHide)).url(); } CSSHelperBase::~CSSHelperBase() { } void CSSHelperBase::recalculatePGPColors() { // determine the frame and body color for PGP messages from the header color // if the header color equals the background color then the other colors are // also set to the background color (-> old style PGP message viewing) // else // the brightness of the frame is set to 4/5 of the brightness of the header // and in case of a light background color // the saturation of the body is set to 1/8 of the saturation of the header // while in case of a dark background color // the value of the body is set to the value of the background color // Check whether the user uses a light color scheme const int vBG = getValueOf(mBackgroundColor); const bool lightBG = vBG >= 128; if (cPgpOk1H == mBackgroundColor) { cPgpOk1F = mBackgroundColor; cPgpOk1B = mBackgroundColor; } else { cPgpOk1F = darker(cPgpOk1H); cPgpOk1B = lightBG ? desaturate(cPgpOk1H) : fixValue(cPgpOk1H, vBG); } if (cPgpOk0H == mBackgroundColor) { cPgpOk0F = mBackgroundColor; cPgpOk0B = mBackgroundColor; } else { cPgpOk0F = darker(cPgpOk0H); cPgpOk0B = lightBG ? desaturate(cPgpOk0H) : fixValue(cPgpOk0H, vBG); } if (cPgpWarnH == mBackgroundColor) { cPgpWarnF = mBackgroundColor; cPgpWarnB = mBackgroundColor; } else { cPgpWarnF = darker(cPgpWarnH); cPgpWarnB = lightBG ? desaturate(cPgpWarnH) : fixValue(cPgpWarnH, vBG); } if (cPgpErrH == mBackgroundColor) { cPgpErrF = mBackgroundColor; cPgpErrB = mBackgroundColor; } else { cPgpErrF = darker(cPgpErrH); cPgpErrB = lightBG ? desaturate(cPgpErrH) : fixValue(cPgpErrH, vBG); } if (cPgpEncrH == mBackgroundColor) { cPgpEncrF = mBackgroundColor; cPgpEncrB = mBackgroundColor; } else { cPgpEncrF = darker(cPgpEncrH); cPgpEncrB = lightBG ? desaturate(cPgpEncrH) : fixValue(cPgpEncrH, vBG); } } QString CSSHelperBase::addEndBlockQuote(int numberBlock) const { QString blockQuote; for (int i = 0; i < numberBlock; ++i) { blockQuote += QLatin1String(""); } return blockQuote; } QString CSSHelperBase::addStartBlockQuote(int numberBlock) const { QString blockQuote; for (int i = 0; i < numberBlock; ++i) { blockQuote += QLatin1String("
"); } return blockQuote; } QString CSSHelperBase::extraScreenCss(const QString &headerFont) const { if (mHeaderPlugin) { return mHeaderPlugin->extraScreenCss(headerFont); } return {}; } QString CSSHelperBase::extraPrintCss(const QString &headerFont) const { if (mHeaderPlugin) { return mHeaderPlugin->extraPrintCss(headerFont); } return {}; } QString CSSHelperBase::extraCommonCss(const QString &headerFont) const { QString result; if (mHeaderPlugin) { result = mHeaderPlugin->extraCommonCss(headerFont); if (result.isEmpty()) { //Add default value result = QStringLiteral("div.header table {\n" " width: 100% ! important;\n" " border-width: 0px ! important;\n" " line-height: normal;\n" "}\n\n"); } } return result; } QString CSSHelperBase::cssDefinitions(bool fixed) const { return commonCssDefinitions() + QLatin1String("@media screen {\n\n") + screenCssDefinitions(this, fixed) + QLatin1String("}\n" "@media print {\n\n") + printCssDefinitions(fixed) + QLatin1String("}\n"); } QString CSSHelperBase::htmlHead(bool fixedFont) const { Q_UNUSED(fixedFont); return QStringLiteral("\n" "\n" "\n"); } QString CSSHelperBase::quoteFontTag(int level) const { if (level < 0) { level = 0; } static const int numQuoteLevels = 3; const int effectiveLevel = mRecycleQuoteColors ? level % numQuoteLevels + 1 : qMin(level + 1, numQuoteLevels); if (level >= numQuoteLevels) { return QStringLiteral("
").arg(effectiveLevel); } else { return QStringLiteral("
").arg(effectiveLevel); } } QString CSSHelperBase::fullAddressList() const { QString css = QStringLiteral("input[type=checkbox].addresslist_checkbox {display: none}\n" ".addresslist_label_short {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}\n" ".addresslist_label_full {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}\n"); css += QStringLiteral(".addresslist_label_short {background-image:url(%1);\nbackground-repeat: no-repeat}\n").arg(imgShowUrl); css += QStringLiteral(".addresslist_label_full {background-image:url(%1);\nbackground-repeat: no-repeat}\n\n").arg(imgHideUrl); for (const QString &str : {QStringLiteral("Cc"), QStringLiteral("To"), QStringLiteral("Bcc")}) { css += QStringLiteral("input ~ span.fullFull%1AddressList {display: block}\n" "input ~ span.shortFull%1AddressList {display: none}\n" "input:checked ~ span.fullFull%1AddressList {display: none}\n" "input:checked ~ span.shortFull%1AddressList {display: block}\n\n").arg(str); } return css; } QString CSSHelperBase::nonQuotedFontTag() const { return QStringLiteral("
"); } QFont CSSHelperBase::bodyFont(bool fixed, bool print) const { return fixed ? (print ? mFixedPrintFont : mFixedFont) : (print ? mPrintFont : mBodyFont); } int CSSHelperBase::fontSize(bool fixed, bool print) const { return bodyFont(fixed, print).pointSize(); } namespace { int pointsToPixel(const QPaintDevice *pd, int pointSize) { return (pointSize * pd->logicalDpiY() + 36) / 72; } } void CSSHelperBase::setHeaderPlugin(const HeaderStylePlugin *headerPlugin) { mHeaderPlugin = headerPlugin; } static const char *const quoteFontSizes[] = { "85", "80", "75" }; QString CSSHelperBase::printCssDefinitions(bool fixed) const { const QString headerFont = defaultPrintHeaderFont(); const QFont printFont = bodyFont(fixed, true /* print */); QString quoteCSS; if (printFont.italic()) { quoteCSS += QLatin1String(" font-style: italic ! important;\n"); } if (printFont.bold()) { quoteCSS += QLatin1String(" font-weight: bold ! important;\n"); } if (!quoteCSS.isEmpty()) { quoteCSS = QLatin1String("div.noquote {\n") + quoteCSS + QLatin1String("}\n\n"); } quoteCSS += quoteCssDefinition(); return QStringLiteral("body {\n" " font-family: \"%1\" ! important;\n" " font-size: %2pt ! important;\n" " color: #000000 ! important;\n" " background-color: #ffffff ! important\n" "}\n\n") .arg(printFont.family(), QString::number(printFont.pointSize())) + linkColorDefinition() +QStringLiteral( "tr.textAtmH,\n" "tr.signInProgressH,\n" "tr.rfc822H,\n" "tr.encrH,\n" "tr.signOkKeyOkH,\n" "tr.signOkKeyBadH,\n" "tr.signWarnH,\n" "tr.signErrH,\n" "div.header {\n" "%1" "}\n\n" "%2" "div.spamheader {\n" " display:none ! important;\n" "}\n\n" "div.htmlWarn {\n" " border: 2px solid #ffffff ! important;\n" " line-height: normal;\n" "}\n\n" "div.senderpic{\n" " font-size:0.8em ! important;\n" " border:1px solid black ! important;\n" " background-color:%2 ! important;\n" "}\n\n" "div.senderstatus{\n" " text-align:center ! important;\n" "}\n\n" "div.noprint {\n" " display:none ! important;\n" "}\n\n" ) - .arg(headerFont) - .arg(extraPrintCss(headerFont)) + .arg(headerFont, extraPrintCss(headerFont)) + quoteCSS + fullAddressList(); } QString CSSHelperBase::linkColorDefinition() const { const QString linkColor = mLinkColor.name(); if (mUseBrowserColor) { #ifdef USE_HTML_STYLE_COLOR const QString bgColor = mBackgroundColor.name(); const QString background = QStringLiteral(" background: %1 ! important;\n").arg(bgColor); return QStringLiteral("div#headerbox a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n" "div.htmlWarn a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n" "div#header a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n" "div.header {\n" " %2" "}\n\n" "div#headerbox {\n" " %2" - "}\n\n").arg(linkColor).arg(background); + "}\n\n").arg(linkColor, background); #else return QStringLiteral("div#headerbox a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n" "div.htmlWarn a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n" "div#header a:link {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n").arg(linkColor); #endif } else { return QStringLiteral("a {\n" " color: %1 ! important;\n" " text-decoration: none ! important;\n" "}\n\n").arg(linkColor); } } QString CSSHelperBase::quoteCssDefinition() const { QString quoteCSS; QString blockQuote; for (int i = 0; i < 9; ++i) { blockQuote += QStringLiteral("blockquote "); quoteCSS += QStringLiteral("%2{\n" " margin: 4pt 0 4pt 0;\n" " padding: 0 0 0 1em;\n" " border-left: 2px solid %1;\n" " unicode-bidi: -webkit-plaintext\n" - "}\n\n").arg(quoteColorName(i)).arg(blockQuote); + "}\n\n").arg(quoteColorName(i), blockQuote); } quoteCSS += QStringLiteral(".quotemarks{\n" " color:transparent;\n" " font-size:0px;\n" "}\n\n"); quoteCSS += QStringLiteral(".quotemarksemptyline{\n" " color:transparent;\n" " font-size:0px;\n" " line-height: 12pt;\n" "}\n\n"); return quoteCSS; } QString CSSHelperBase::defaultPrintHeaderFont() const { const QString headerFont = QStringLiteral(" font-family: \"%1\" ! important;\n" " font-size: %2pt ! important;\n") .arg(mPrintFont.family()) .arg(mPrintFont.pointSize()); return headerFont; } QString CSSHelperBase::defaultScreenHeaderFont() const { const QString headerFont = QStringLiteral(" font-family: \"%1\" ! important;\n" " font-size: %2px ! important;\n") .arg(mBodyFont.family()) .arg(pointsToPixel(this->mPaintDevice, mBodyFont.pointSize())); return headerFont; } QString CSSHelperBase::screenCssDefinitions(const CSSHelperBase *helper, bool fixed) const { const QString bgColor = mBackgroundColor.name(); const QString headerFont = defaultScreenHeaderFont(); #ifdef USE_HTML_STYLE_COLOR const QString fgColor = mUseBrowserColor ? QStringLiteral("black") : mForegroundColor.name(); const QString background = mUseBrowserColor ? QString() : QStringLiteral(" background-color: %1 ! important;\n").arg(bgColor); const QString signWarnBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpWarnB.name(); const QString cPgpErrBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpErrB.name(); const QString cPgpEncrBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpEncrB.name(); const QString cPgpOk1BColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpOk1B.name(); const QString cPgpOk0BColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpOk0B.name(); #else const QString fgColor = mForegroundColor.name(); const QString background = QStringLiteral(" background-color: %1 ! important;\n").arg(bgColor); const QString signWarnBColorName = cPgpWarnB.name(); const QString cPgpErrBColorName = cPgpErrB.name(); const QString cPgpEncrBColorName = cPgpEncrB.name(); const QString cPgpOk1BColorName = cPgpOk1B.name(); const QString cPgpOk0BColorName = cPgpOk0B.name(); #endif const QString bodyFontSize = QString::number(pointsToPixel(helper->mPaintDevice, fontSize( fixed))) + QLatin1String("px"); const QPalette &pal = QApplication::palette(); QString quoteCSS; if (bodyFont(fixed).italic()) { quoteCSS += QLatin1String(" font-style: italic ! important;\n"); } if (bodyFont(fixed).bold()) { quoteCSS += QLatin1String(" font-weight: bold ! important;\n"); } if (!quoteCSS.isEmpty()) { quoteCSS = QLatin1String("div.noquote {\n") + quoteCSS + QLatin1String("}\n\n"); } // CSS definitions for quote levels 1-3 for (int i = 0; i < 3; ++i) { quoteCSS += QStringLiteral("div.quotelevel%1 {\n" " color: %2 ! important;\n") .arg(QString::number(i + 1), quoteColorName(i)); if (mQuoteFont.italic()) { quoteCSS += QStringLiteral(" font-style: italic ! important;\n"); } if (mQuoteFont.bold()) { quoteCSS += QStringLiteral(" font-weight: bold ! important;\n"); } if (mShrinkQuotes) { quoteCSS += QLatin1String(" font-size: ") + QString::fromLatin1(quoteFontSizes[i]) + QLatin1String("% ! important;\n"); } quoteCSS += QStringLiteral("}\n\n"); } // CSS definitions for quote levels 4+ for (int i = 0; i < 3; ++i) { quoteCSS += QStringLiteral("div.deepquotelevel%1 {\n" " color: %2 ! important;\n") .arg(QString::number(i + 1), quoteColorName(i)); if (mQuoteFont.italic()) { quoteCSS += QStringLiteral(" font-style: italic ! important;\n"); } if (mQuoteFont.bold()) { quoteCSS += QStringLiteral(" font-weight: bold ! important;\n"); } if (mShrinkQuotes) { quoteCSS += QStringLiteral(" font-size: 70% ! important;\n"); } quoteCSS += QLatin1String("}\n\n"); } quoteCSS += quoteCssDefinition(); return QStringLiteral("body {\n" " font-family: \"%1\" ! important;\n" " font-size: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n") .arg(bodyFont(fixed).family(), bodyFontSize, fgColor, background) + linkColorDefinition() + QStringLiteral("a.white {\n" " color: white ! important;\n" "}\n\n" "a.black {\n" " color: black ! important;\n" "}\n\n" "table.textAtm { background-color: %1 ! important; }\n\n" "tr.textAtmH {\n" " background-color: %2 ! important;\n" "%3" "}\n\n" "tr.textAtmB {\n" " background-color: %2 ! important;\n" "}\n\n" "table.signInProgress,\n" "table.rfc822 {\n" " background-color: %2 ! important;\n" "}\n\n" "tr.signInProgressH,\n" "tr.rfc822H {\n" "%3" "}\n\n").arg(fgColor, bgColor, headerFont) + QStringLiteral("table.encr {\n" " background-color: %1 ! important;\n" "}\n\n" "tr.encrH {\n" " background-color: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n" "tr.encrB { background-color: %5 ! important; }\n\n") .arg(cPgpEncrF.name(), cPgpEncrH.name(), cPgpEncrHT.name(), headerFont, cPgpEncrBColorName) + QStringLiteral("table.signOkKeyOk {\n" " background-color: %1 ! important;\n" "}\n\n" "tr.signOkKeyOkH {\n" " background-color: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n" "tr.signOkKeyOkB { background-color: %5 ! important; }\n\n") .arg(cPgpOk1F.name(), cPgpOk1H.name(), cPgpOk1HT.name(), headerFont, cPgpOk1BColorName) + QStringLiteral("table.signOkKeyBad {\n" " background-color: %1 ! important;\n" "}\n\n" "tr.signOkKeyBadH {\n" " background-color: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n" "tr.signOkKeyBadB { background-color: %5 ! important; }\n\n") .arg(cPgpOk0F.name(), cPgpOk0H.name(), cPgpOk0HT.name(), headerFont, cPgpOk0BColorName) + QStringLiteral("table.signWarn {\n" " background-color: %1 ! important;\n" "}\n\n" "tr.signWarnH {\n" " background-color: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n" "tr.signWarnB { background-color: %5 ! important; }\n\n") .arg(cPgpWarnF.name(), cPgpWarnH.name(), cPgpWarnHT.name(), headerFont, signWarnBColorName) + QStringLiteral("table.signErr {\n" " background-color: %1 ! important;\n" "}\n\n" "tr.signErrH {\n" " background-color: %2 ! important;\n" " color: %3 ! important;\n" "%4" "}\n\n" "tr.signErrB { background-color: %5 ! important; }\n\n") .arg(cPgpErrF.name(), cPgpErrH.name(), cPgpErrHT.name(), headerFont, cPgpErrBColorName) + QStringLiteral("div.htmlWarn {\n" " border: 2px solid %1 ! important;\n" " line-height: normal;\n" "}\n\n") .arg(cHtmlWarning.name()) + QStringLiteral("div.header {\n" "%1" "}\n\n" "%2" "div.senderpic{\n" " padding: 0px ! important;\n" " font-size:0.8em ! important;\n" " border:1px solid %4 ! important;\n" " background-color:%3 ! important;\n" "}\n\n" "div.senderstatus{\n" " text-align:center ! important;\n" "}\n\n" ) - .arg(headerFont) - .arg(extraScreenCss(headerFont)) - .arg(pal.color(QPalette::Highlight).name()) - .arg(pal.color(QPalette::Window).name()) + .arg(headerFont, extraScreenCss(headerFont), pal.color(QPalette::Highlight).name(), pal.color(QPalette::Window).name()) + quoteCSS + fullAddressList(); } QString CSSHelperBase::commonCssDefinitions() const { const QString headerFont = defaultScreenHeaderFont(); return QStringLiteral("div.header {\n" " margin-bottom: 10pt ! important;\n" "}\n\n" "table.textAtm {\n" " margin-top: 10pt ! important;\n" " margin-bottom: 10pt ! important;\n" "}\n\n" "tr.textAtmH,\n" "tr.textAtmB,\n" "tr.rfc822B {\n" " font-weight: normal ! important;\n" "}\n\n" "tr.signInProgressH,\n" "tr.rfc822H,\n" "tr.encrH,\n" "tr.signOkKeyOkH,\n" "tr.signOkKeyBadH,\n" "tr.signWarnH,\n" "tr.signErrH {\n" " font-weight: bold ! important;\n" "}\n\n" "tr.textAtmH td,\n" "tr.textAtmB td {\n" " padding: 3px ! important;\n" "}\n\n" "table.rfc822 {\n" " width: 100% ! important;\n" " border: solid 1px black ! important;\n" " margin-top: 10pt ! important;\n" " margin-bottom: 10pt ! important;\n" "}\n\n" "table.textAtm,\n" "table.encr,\n" "table.signWarn,\n" "table.signErr,\n" "table.signOkKeyBad,\n" "table.signOkKeyOk,\n" "table.signInProgress,\n" "%1" "div.htmlWarn {\n" " margin: 0px 5% ! important;\n" " padding: 10px ! important;\n" " text-align: left ! important;\n" " line-height: normal;\n" "}\n\n" "div.quotelevelmark {\n" " position: absolute;\n" " margin-left:-10px;\n" "}\n\n").arg(extraCommonCss(headerFont)); } void CSSHelperBase::setBodyFont(const QFont &font) { mBodyFont = font; } void CSSHelperBase::setPrintFont(const QFont &font) { mPrintFont = font; } QString CSSHelperBase::quoteColorName(int level) const { return quoteColor(level).name(); } QColor CSSHelperBase::quoteColor(int level) const { const int actualLevel = qMax(level, 0) % 3; return mQuoteColor[actualLevel]; } QColor CSSHelperBase::pgpWarnColor() const { return cPgpWarnH; } } diff --git a/messageviewer/src/viewer/viewer.cpp b/messageviewer/src/viewer/viewer.cpp index 8676e547..b8793fa3 100644 --- a/messageviewer/src/viewer/viewer.cpp +++ b/messageviewer/src/viewer/viewer.cpp @@ -1,734 +1,734 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (C) 2013-2019 Laurent Montel 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. */ // define this to copy all html that is written to the readerwindow to // filehtmlwriter.out in the current working directory //#define KMAIL_READER_HTML_DEBUG 1 #include "viewer.h" #include "viewer_p.h" #include "widgets/configurewidget.h" #include "csshelper.h" #include "settings/messageviewersettings.h" #include "viewer/webengine/mailwebengineview.h" #include #include #include "viewer/mimeparttree/mimetreemodel.h" #include "viewer/mimeparttree/mimeparttreeview.h" #include "widgets/zoomactionmenu.h" #include #include #include #include #include #include #include namespace MessageViewer { class AbstractMessageLoadedHandler::Private { public: Akonadi::Session *mSession = nullptr; }; AbstractMessageLoadedHandler::AbstractMessageLoadedHandler() : d(new Private) { } AbstractMessageLoadedHandler::~AbstractMessageLoadedHandler() { delete d; } void AbstractMessageLoadedHandler::setSession(Akonadi::Session *session) { d->mSession = session; } Akonadi::Session *AbstractMessageLoadedHandler::session() const { return d->mSession; } Viewer::Viewer(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QWidget(aParent) , d_ptr(new ViewerPrivate(this, mainWindow, actionCollection)) { initialize(); } Viewer::~Viewer() { //the d_ptr is automatically deleted } void Viewer::initialize() { connect(d_ptr, &ViewerPrivate::displayPopupMenu, this, &Viewer::displayPopupMenu); connect(d_ptr, &ViewerPrivate::popupMenu, this, &Viewer::popupMenu); connect(d_ptr, SIGNAL(urlClicked(Akonadi::Item,QUrl)), SIGNAL(urlClicked(Akonadi::Item,QUrl))); connect(d_ptr, &ViewerPrivate::requestConfigSync, this, &Viewer::requestConfigSync); connect(d_ptr, &ViewerPrivate::makeResourceOnline, this, &Viewer::makeResourceOnline); connect(d_ptr, &ViewerPrivate::showReader, this, &Viewer::showReader); connect(d_ptr, &ViewerPrivate::showMessage, this, &Viewer::showMessage); connect(d_ptr, &ViewerPrivate::replyMessageTo, this, &Viewer::replyMessageTo); connect(d_ptr, &ViewerPrivate::showStatusBarMessage, this, &Viewer::showStatusBarMessage); connect(d_ptr, &ViewerPrivate::itemRemoved, this, &Viewer::itemRemoved); connect(d_ptr, &ViewerPrivate::changeDisplayMail, this, &Viewer::slotChangeDisplayMail); connect(d_ptr, &ViewerPrivate::moveMessageToTrash, this, &Viewer::moveMessageToTrash); connect(d_ptr, &ViewerPrivate::pageIsScrolledToBottom, this, &Viewer::pageIsScrolledToBottom); connect(d_ptr, &ViewerPrivate::printingFinished, this, &Viewer::printingFinished); connect(d_ptr, &ViewerPrivate::zoomChanged, this, &Viewer::zoomChanged); setMessage(KMime::Message::Ptr(), MimeTreeParser::Delayed); } void Viewer::changeEvent(QEvent *event) { Q_D(Viewer); if (event->type() == QEvent::FontChange) { d->slotGeneralFontChanged(); } QWidget::changeEvent(event); } void Viewer::setMessage(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); if (message == d->message()) { return; } d->setMessage(message, updateMode); } void Viewer::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); if (d->messageItem() == item) { return; } if (!item.isValid() || item.loadedPayloadParts().contains(Akonadi::MessagePart::Body)) { d->setMessageItem(item, updateMode); } else { Akonadi::ItemFetchJob *job = createFetchJob(item); - connect(job, &Akonadi::ItemFetchJob::result, [this, d](KJob *job) { + connect(job, &Akonadi::ItemFetchJob::result, [ d](KJob *job) { d->itemFetchResult(job); }); d->displaySplashPage(i18n("Loading message...")); } } QString Viewer::messagePath() const { Q_D(const Viewer); return d->mMessagePath; } void Viewer::setMessagePath(const QString &path) { Q_D(Viewer); d->mMessagePath = path; } void Viewer::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain) { Q_D(Viewer); d->displaySplashPage(templateName, data, domain); } void Viewer::enableMessageDisplay() { Q_D(Viewer); d->enableMessageDisplay(); } void Viewer::printMessage(const Akonadi::Item &msg) { Q_D(Viewer); d->printMessage(msg); } void Viewer::printPreviewMessage(const Akonadi::Item &message) { Q_D(Viewer); d->printPreviewMessage(message); } void Viewer::printPreview() { Q_D(Viewer); d->slotPrintPreview(); } void Viewer::print() { Q_D(Viewer); d->slotPrintMessage(); } void Viewer::resizeEvent(QResizeEvent *) { Q_D(Viewer); if (!d->mResizeTimer.isActive()) { // // Combine all resize operations that are requested as long a // the timer runs. // d->mResizeTimer.start(100); } } void Viewer::closeEvent(QCloseEvent *e) { Q_D(Viewer); QWidget::closeEvent(e); d->writeConfig(); } void Viewer::slotAttachmentSaveAs() { Q_D(Viewer); d->slotAttachmentSaveAs(); } void Viewer::slotAttachmentSaveAll() { Q_D(Viewer); d->slotAttachmentSaveAll(); } void Viewer::slotSaveMessage() { Q_D(Viewer); d->slotSaveMessage(); } void Viewer::slotScrollUp() { Q_D(Viewer); d->mViewer->scrollUp(10); } void Viewer::slotScrollDown() { Q_D(Viewer); d->mViewer->scrollDown(10); } void Viewer::atBottom() { Q_D(const Viewer); d->mViewer->isScrolledToBottom(); } void Viewer::slotJumpDown() { Q_D(Viewer); d->mViewer->scrollPageDown(100); } void Viewer::slotScrollPrior() { Q_D(Viewer); d->mViewer->scrollPageUp(80); } void Viewer::slotScrollNext() { Q_D(Viewer); d->mViewer->scrollPageDown(80); } QString Viewer::selectedText() const { Q_D(const Viewer); return d->mViewer->selectedText(); } Viewer::DisplayFormatMessage Viewer::displayFormatMessageOverwrite() const { Q_D(const Viewer); return d->displayFormatMessageOverwrite(); } void Viewer::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format) { Q_D(Viewer); d->setDisplayFormatMessageOverwrite(format); } void Viewer::setHtmlLoadExtDefault(bool loadExtDefault) { Q_D(Viewer); d->setHtmlLoadExtDefault(loadExtDefault); } void Viewer::setHtmlLoadExtOverride(bool loadExtOverride) { Q_D(Viewer); d->setHtmlLoadExtOverride(loadExtOverride); } bool Viewer::htmlLoadExtOverride() const { Q_D(const Viewer); return d->htmlLoadExtOverride(); } bool Viewer::htmlMail() const { Q_D(const Viewer); return d->htmlMail(); } bool Viewer::htmlLoadExternal() const { Q_D(const Viewer); return d->htmlLoadExternal(); } bool Viewer::isFixedFont() const { Q_D(const Viewer); return d->mUseFixedFont; } void Viewer::setUseFixedFont(bool useFixedFont) { Q_D(Viewer); d->setUseFixedFont(useFixedFont); } QWidget *Viewer::mainWindow() { Q_D(Viewer); return d->mMainWindow; } void Viewer::setDecryptMessageOverwrite(bool overwrite) { Q_D(Viewer); d->setDecryptMessageOverwrite(overwrite); } KMime::Message::Ptr Viewer::message() const { Q_D(const Viewer); return d->mMessage; } Akonadi::Item Viewer::messageItem() const { Q_D(const Viewer); return d->mMessageItem; } bool Viewer::event(QEvent *e) { Q_D(Viewer); if (e->type() == QEvent::PaletteChange) { d->recreateCssHelper(); d->update(MimeTreeParser::Force); e->accept(); return true; } return QWidget::event(e); } void Viewer::slotFind() { Q_D(Viewer); d->slotFind(); } const AttachmentStrategy *Viewer::attachmentStrategy() const { Q_D(const Viewer); return d->attachmentStrategy(); } void Viewer::setAttachmentStrategy(const AttachmentStrategy *strategy) { Q_D(Viewer); d->setAttachmentStrategy(strategy); } QString Viewer::overrideEncoding() const { Q_D(const Viewer); return d->overrideEncoding(); } void Viewer::setOverrideEncoding(const QString &encoding) { Q_D(Viewer); d->setOverrideEncoding(encoding); } CSSHelper *Viewer::cssHelper() const { Q_D(const Viewer); return d->cssHelper(); } KToggleAction *Viewer::toggleFixFontAction() const { Q_D(const Viewer); return d->mToggleFixFontAction; } bool Viewer::mimePartTreeIsEmpty() const { Q_D(const Viewer); return d->mimePartTreeIsEmpty(); } KToggleAction *Viewer::toggleMimePartTreeAction() const { Q_D(const Viewer); return d->mToggleMimePartTreeAction; } QAction *Viewer::selectAllAction() const { Q_D(const Viewer); return d->mSelectAllAction; } QAction *Viewer::viewSourceAction() const { Q_D(const Viewer); return d->mViewSourceAction; } QAction *Viewer::copyURLAction() const { Q_D(const Viewer); return d->mCopyURLAction; } QAction *Viewer::copyAction() const { Q_D(const Viewer); return d->mCopyAction; } QAction *Viewer::speakTextAction() const { Q_D(const Viewer); return d->mSpeakTextAction; } QAction *Viewer::copyImageLocation() const { Q_D(const Viewer); return d->mCopyImageLocation; } QAction *Viewer::saveAsAction() const { Q_D(const Viewer); return d->mSaveMessageAction; } QAction *Viewer::urlOpenAction() const { Q_D(const Viewer); return d->mUrlOpenAction; } bool Viewer::printingMode() const { Q_D(const Viewer); return d->printingMode(); } void Viewer::setPrinting(bool enable) { Q_D(Viewer); d->setPrinting(enable); } void Viewer::writeConfig(bool force) { Q_D(Viewer); d->writeConfig(force); } QUrl Viewer::urlClicked() const { Q_D(const Viewer); return d->mClickedUrl; } QUrl Viewer::imageUrlClicked() const { Q_D(const Viewer); return d->imageUrl(); } void Viewer::update(MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); d->update(updateMode); } void Viewer::setMessagePart(KMime::Content *aMsgPart) { Q_D(Viewer); d->setMessagePart(aMsgPart); } void Viewer::clear(MimeTreeParser::UpdateMode updateMode) { setMessage(KMime::Message::Ptr(), updateMode); } void Viewer::slotShowMessageSource() { Q_D(Viewer); d->slotShowMessageSource(); } void Viewer::readConfig() { Q_D(Viewer); d->readConfig(); } QAbstractItemModel *Viewer::messageTreeModel() const { #ifndef QT_NO_TREEVIEW return d_func()->mMimePartTree->mimePartModel(); #else return nullptr; #endif } Akonadi::ItemFetchJob *Viewer::createFetchJob(const Akonadi::Item &item) { Q_D(Viewer); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, d->mSession); job->fetchScope().fetchAllAttributes(); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->fetchScope().fetchFullPayload(true); job->fetchScope().setFetchRelations(true); // needed to know if we have notes or not job->fetchScope().fetchAttribute(); return job; } void Viewer::addMessageLoadedHandler(AbstractMessageLoadedHandler *handler) { Q_D(Viewer); if (!handler) { return; } handler->setSession(d->mSession); d->mMessageLoadedHandlers.insert(handler); } void Viewer::removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler) { Q_D(Viewer); d->mMessageLoadedHandlers.remove(handler); } void Viewer::deleteMessage() { Q_D(Viewer); Q_EMIT deleteMessage(d->messageItem()); } void Viewer::selectAll() { Q_D(Viewer); d->selectAll(); } void Viewer::copySelectionToClipboard() { Q_D(Viewer); d->slotCopySelectedText(); } void Viewer::setZoomFactor(qreal zoomFactor) { Q_D(Viewer); d->mZoomActionMenu->setZoomFactor(zoomFactor); } void Viewer::slotZoomReset() { Q_D(Viewer); d->mZoomActionMenu->slotZoomReset(); } void Viewer::slotZoomIn() { Q_D(Viewer); d->mZoomActionMenu->slotZoomIn(); } void Viewer::slotZoomOut() { Q_D(Viewer); d->mZoomActionMenu->slotZoomOut(); } QAction *Viewer::findInMessageAction() const { Q_D(const Viewer); return d->mFindInMessageAction; } void Viewer::slotChangeDisplayMail(Viewer::DisplayFormatMessage mode, bool loadExternal) { if ((htmlLoadExtOverride() != loadExternal) || (displayFormatMessageOverwrite() != mode)) { setHtmlLoadExtOverride(loadExternal); setDisplayFormatMessageOverwrite(mode); update(MimeTreeParser::Force); } } QAction *Viewer::saveMessageDisplayFormatAction() const { Q_D(const Viewer); return d->mSaveMessageDisplayFormat; } QAction *Viewer::resetMessageDisplayFormatAction() const { Q_D(const Viewer); return d->mResetMessageDisplayFormat; } KToggleAction *Viewer::disableEmoticonAction() const { Q_D(const Viewer); return d->mDisableEmoticonAction; } void Viewer::saveMainFrameScreenshotInFile(const QString &filename) { Q_D(Viewer); - return d->saveMainFrameScreenshotInFile(filename); + d->saveMainFrameScreenshotInFile(filename); } KActionMenu *Viewer::shareServiceUrlMenu() const { Q_D(const Viewer); return d->mShareServiceUrlMenu; } HeaderStylePlugin *Viewer::headerStylePlugin() const { Q_D(const Viewer); return d->mHeaderStylePlugin; } void Viewer::setPluginName(const QString &pluginName) { Q_D(Viewer); - return d->setPluginName(pluginName); + d->setPluginName(pluginName); } void Viewer::showOpenAttachmentFolderWidget(const QList &urls) { Q_D(Viewer); d->showOpenAttachmentFolderWidget(urls); } QList Viewer::viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features) { Q_D(Viewer); return d->viewerPluginActionList(features); } QList Viewer::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const { Q_D(const Viewer); return d->interceptorUrlActions(result); } void Viewer::runJavaScript(const QString &code) { Q_D(Viewer); d->mViewer->page()->runJavaScript(code, WebEngineViewer::WebEngineManageScript::scriptWordId()); } void Viewer::setPrintElementBackground(bool printElementBackground) { Q_D(Viewer); d->mViewer->setPrintElementBackground(printElementBackground); } bool Viewer::showSignatureDetails() const { Q_D(const Viewer); return d->showSignatureDetails(); } void Viewer::setShowSignatureDetails(bool showDetails) { Q_D(Viewer); d->setShowSignatureDetails(showDetails); } bool Viewer::showEncryptionDetails() const { Q_D(const Viewer); return d->showEncryptionDetails(); } void Viewer::setShowEncryptionDetails(bool showDetails) { Q_D(Viewer); d->setShowEncryptionDetails(showDetails); } qreal Viewer::webViewZoomFactor() const { Q_D(const Viewer); return d->webViewZoomFactor(); } void Viewer::setWebViewZoomFactor(qreal factor) { Q_D(Viewer); d->setWebViewZoomFactor(factor); } } diff --git a/messageviewer/src/viewer/viewer_p.cpp b/messageviewer/src/viewer/viewer_p.cpp index b9cf25c8..4e285dac 100644 --- a/messageviewer/src/viewer/viewer_p.cpp +++ b/messageviewer/src/viewer/viewer_p.cpp @@ -1,3123 +1,3123 @@ /* Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2010 Torgny Nyblom Copyright (C) 2011-2019 Laurent Montel 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 "viewer_p.h" #include "viewer.h" #include "messageviewer_debug.h" #include "utils/mimetype.h" #include "viewer/objecttreeemptysource.h" #include "viewer/objecttreeviewersource.h" #include "messagedisplayformatattribute.h" #include "utils/iconnamecache.h" #include "scamdetection/scamdetectionwarningwidget.h" #include "scamdetection/scamattribute.h" #include "viewer/mimeparttree/mimeparttreeview.h" #include "widgets/openattachmentfolderwidget.h" #include "messageviewer/headerstyle.h" #include "messageviewer/headerstrategy.h" #include "kpimtextedit/slidecontainer.h" #include #include #include "job/modifymessagedisplayformatjob.h" #include "config-messageviewer.h" #include "viewerplugins/viewerplugintoolmanager.h" #include #include "htmlwriter/webengineembedpart.h" #include #include // link() //KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include //libkdepim #include "Libkdepim/BroadcastStatus" #include #include #include #include #include #include #include #include //own includes #include "widgets/attachmentdialog.h" #include "csshelper.h" #include "settings/messageviewersettings.h" #include "widgets/htmlstatusbar.h" #include "viewer/attachmentstrategy.h" #include "viewer/mimeparttree/mimetreemodel.h" #include "viewer/urlhandlermanager.h" #include "messageviewer/messageviewerutil.h" #include "utils/messageviewerutil_p.h" #include "widgets/vcardviewer.h" #include #include "viewer/webengine/mailwebengineview.h" #include "htmlwriter/webengineparthtmlwriter.h" #include #include #include "header/headerstylemenumanager.h" #include "widgets/submittedformwarningwidget.h" #include #include #include "interfaces/htmlwriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
#include #include #include #include #include #include using namespace boost; using namespace MailTransport; using namespace MessageViewer; using namespace MessageCore; static QAtomicInt _k_attributeInitialized; template struct InvokeWrapper { R *receiver; void (C::*memberFun)(Arg); void operator()(Arg result) { (receiver->*memberFun)(result); } }; template InvokeWrapper invoke(R *receiver, void (C::*memberFun)(Arg)) { InvokeWrapper wrapper = {receiver, memberFun}; return wrapper; } ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QObject(aParent) , mNodeHelper(new MimeTreeParser::NodeHelper) , mOldGlobalOverrideEncoding(QStringLiteral("---")) , mMsgDisplay(true) , mCSSHelper(nullptr) , mMainWindow(mainWindow) , mActionCollection(actionCollection) , mCanStartDrag(false) , mRecursionCountForDisplayMessage(0) , q(aParent) , mSession(new Akonadi::Session("MessageViewer-" + QByteArray::number(reinterpret_cast(this)), this)) , mPreviouslyViewedItem(-1) { mMimePartTree = nullptr; if (!mainWindow) { mMainWindow = aParent; } if (_k_attributeInitialized.testAndSetAcquire(0, 1)) { Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); } mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this); mPhishingDatabase->initialize(); connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ViewerPrivate::slotCheckedUrlFinished); mShareServiceManager = new PimCommon::ShareServiceUrlManager(this); mDisplayFormatMessageOverwrite = MessageViewer::Viewer::UseGlobalSetting; mHtmlLoadExtOverride = false; mHtmlLoadExternalDefaultSetting = false; mHtmlMailGlobalSetting = false; mUpdateReaderWinTimer.setObjectName(QStringLiteral("mUpdateReaderWinTimer")); mResizeTimer.setObjectName(QStringLiteral("mResizeTimer")); mPrinting = false; createWidgets(); createActions(); initHtmlWidget(); readConfig(); mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; mResizeTimer.setSingleShot(true); connect(&mResizeTimer, &QTimer::timeout, this, &ViewerPrivate::slotDelayedResize); mUpdateReaderWinTimer.setSingleShot(true); connect(&mUpdateReaderWinTimer, &QTimer::timeout, this, &ViewerPrivate::updateReaderWin); connect(mNodeHelper, &MimeTreeParser::NodeHelper::update, this, &ViewerPrivate::update); connect(mColorBar, &HtmlStatusBar::clicked, this, &ViewerPrivate::slotToggleHtmlMode); // FIXME: Don't use the full payload here when attachment loading on demand is used, just // like in KMMainWidget::slotMessageActivated(). mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor")); mMonitor.setSession(mSession); Akonadi::ItemFetchScope fs; fs.fetchFullPayload(); fs.fetchAttribute(); fs.fetchAttribute(); fs.fetchAttribute(); mMonitor.setItemFetchScope(fs); connect(&mMonitor, &Akonadi::Monitor::itemChanged, this, &ViewerPrivate::slotItemChanged); connect(&mMonitor, &Akonadi::Monitor::itemRemoved, this, &ViewerPrivate::slotClear); connect(&mMonitor, &Akonadi::Monitor::itemMoved, this, &ViewerPrivate::slotItemMoved); } ViewerPrivate::~ViewerPrivate() { MessageViewer::MessageViewerSettings::self()->save(); delete mHtmlWriter; mHtmlWriter = nullptr; delete mViewer; mViewer = nullptr; delete mCSSHelper; mNodeHelper->forceCleanTempFiles(); qDeleteAll(mListMailSourceViewer); delete mNodeHelper; } //----------------------------------------------------------------------------- KMime::Content *ViewerPrivate::nodeFromUrl(const QUrl &url) const { return mNodeHelper->fromHREF(mMessage, url); } void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url) { if (!node) { return; } if (node->contentType(false)) { if (node->contentType()->mimeType() == "text/x-moz-deleted") { return; } if (node->contentType()->mimeType() == "message/external-body") { if (node->contentType()->hasParameter(QStringLiteral("url"))) { KRun::RunFlags flags; flags |= KRun::RunExecutables; const QString url = node->contentType()->parameter(QStringLiteral("url")); KRun::runUrl(QUrl(url), QStringLiteral("text/html"), q, flags); return; } } } const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { // the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation // in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree. KMime::Message::Ptr m(new KMime::Message); m->setContent(node->parent()->bodyAsMessage()->encodedContent()); m->parse(); atmViewMsg(m); return; } // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower())); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { showVCard(node); return; } // special case treatment on mac and windows QUrl atmUrl = url; if (url.isEmpty()) { atmUrl = mNodeHelper->tempFileUrlFromNode(node); } if (Util::handleUrlWithQDesktopServices(atmUrl)) { return; } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { mimetype = MimeTreeParser::Util::mimetype( url.isLocalFile() ? url.toLocalFile() : url.fileName()); } KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); const QString filenameText = MimeTreeParser::NodeHelper::fileName(node); QPointer dialog = new AttachmentDialog(mMainWindow, filenameText, offer, QLatin1String( "askSave_") + mimetype.name()); const int choice = dialog->exec(); delete dialog; if (choice == AttachmentDialog::Save) { QList urlList; if (Util::saveContents(mMainWindow, KMime::Content::List() << node, urlList)) { showOpenAttachmentFolderWidget(urlList); } } else if (choice == AttachmentDialog::Open) { // Open if (offer) { attachmentOpenWith(node, offer); } else { attachmentOpen(node); } } else if (choice == AttachmentDialog::OpenWith) { attachmentOpenWith(node); } else { // Cancel qCDebug(MESSAGEVIEWER_LOG) << "Canceled opening attachment"; } } bool ViewerPrivate::deleteAttachment(KMime::Content *node, bool showWarning) { if (!node) { return true; } KMime::Content *parent = node->parent(); if (!parent) { return true; } QList extraNodes = mNodeHelper->extraContents(mMessage.data()); if (extraNodes.contains(node->topLevel())) { KMessageBox::error(mMainWindow, i18n( "Deleting an attachment from an encrypted or old-style mailman message is not supported."), i18n("Delete Attachment")); return true; //cancelled } if (showWarning && KMessageBox::warningContinueCancel(mMainWindow, i18n( "Deleting an attachment might invalidate any digital signature on this message."), i18n("Delete Attachment"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral( "DeleteAttachmentSignatureWarning")) != KMessageBox::Continue) { return false; //cancelled } //don't confuse the model #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif QString filename; QString name; QByteArray mimetype; if (node->contentDisposition(false)) { filename = node->contentDisposition()->filename(); } if (node->contentType(false)) { name = node->contentType()->name(); mimetype = node->contentType()->mimeType(); } // text/plain part: KMime::Content *deletePart = new KMime::Content(parent); deletePart->contentType()->setMimeType("text/x-moz-deleted"); deletePart->contentType()->setName(QStringLiteral("Deleted: %1").arg(name), "utf8"); deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment); deletePart->contentDisposition()->setFilename(QStringLiteral("Deleted: %1").arg(name)); deletePart->contentType()->setCharset("utf-8"); deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray bodyMessage = QByteArrayLiteral( "\nYou deleted an attachment from this message. The original MIME headers for the attachment were:"); bodyMessage += ("\nContent-Type: ") + mimetype; bodyMessage += ("\nname=\"") + name.toUtf8() + "\""; bodyMessage += ("\nfilename=\"") + filename.toUtf8() + "\""; deletePart->setBody(bodyMessage); parent->replaceContent(node, deletePart); parent->assemble(); KMime::Message *modifiedMessage = mNodeHelper->messageWithExtraContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->mimePartModel()->setRoot(modifiedMessage); #endif mMessageItem.setPayloadFromData(modifiedMessage->encodedContent()); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession); job->disableRevisionCheck(); connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult); return true; } void ViewerPrivate::itemModifiedResult(KJob *job) { if (job->error()) { qCDebug(MESSAGEVIEWER_LOG) << "Item update failed:" << job->errorString(); } else { setMessageItem(mMessageItem, MimeTreeParser::Force); } } void ViewerPrivate::scrollToAnchor(const QString &anchor) { mViewer->scrollToAnchor(anchor); } void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent) { const KService::List offers = KFileItemActions::associatedApplications( QStringList() << contentTypeStr, QString()); if (!offers.isEmpty()) { QMenu *menu = topMenu; QActionGroup *actionGroup = new QActionGroup(menu); if (fromCurrentContent) { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithActionCurrentContent); } else { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithAction); } if (offers.count() > 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } //qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu; KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for (; it != end; ++it) { QAction *act = MessageViewer::Util::createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } QAction *openWithAct = new QAction(menu); openWithAct->setText(openWithActionName); if (fromCurrentContent) { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } menu->addAction(openWithAct); } else { // no app offers -> Open With... QAction *act = new QAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); if (fromCurrentContent) { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } topMenu->addAction(act); } } void ViewerPrivate::slotOpenWithDialogCurrentContent() { if (!mCurrentContent) { return; } attachmentOpenWith(mCurrentContent); } void ViewerPrivate::slotOpenWithDialog() { auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first()); } } void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act) { if (!mCurrentContent) { return; } KService::Ptr app = act->data().value(); attachmentOpenWith(mCurrentContent, app); } void ViewerPrivate::slotOpenWithAction(QAction *act) { KService::Ptr app = act->data().value(); auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first(), app); } } void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos) { Q_UNUSED(name); prepareHandleAttachment(node); bool deletedAttachment = false; if (node->contentType(false)) { deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted"); } //Not necessary to show popup menu when attachment was removed if (deletedAttachment) { return; } QMenu menu; const QString contentTypeStr = QLatin1String(node->contentType()->mimeType()); QAction *action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open")); action->setEnabled(!deletedAttachment); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::Open); }); if (!deletedAttachment) { createOpenWithMenu(&menu, contentTypeStr, true); } QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid()) { const QStringList parentMimeType = mimetype.parentMimeTypes(); if ((contentTypeStr == QLatin1String("text/plain")) || (contentTypeStr == QLatin1String("image/png")) || (contentTypeStr == QLatin1String("image/jpeg")) || parentMimeType.contains(QLatin1String("text/plain")) || parentMimeType.contains(QLatin1String("image/png")) || parentMimeType.contains(QLatin1String("image/jpeg")) ) { action = menu.addAction(i18nc("to view something", "View")); action->setEnabled(!deletedAttachment); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::View); }); } } action = menu.addAction(i18n("Scroll To")); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::ScrollTo); }); action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "Save As...")); action->setEnabled(!deletedAttachment); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::Save); }); action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); action->setEnabled(!deletedAttachment); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::Copy); }); const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid() && (mMessageItem.parentCollection().rights() != Akonadi::Collection::ReadOnly) && !isEncapsulatedMessage; action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Attachment")); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::Delete); }); action->setEnabled(canChange && !deletedAttachment); #if 0 menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply To Author")); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::ReplyMessageToAuthor); }); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n( "Reply To All")); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::ReplyMessageToAll); }); #endif menu.addSeparator(); action = menu.addAction(i18n("Properties")); connect(action, &QAction::triggered, this, [this]() { slotHandleAttachment(Viewer::Properties); }); menu.exec(globalPos); } void ViewerPrivate::prepareHandleAttachment(KMime::Content *node) { mCurrentContent = node; } QString ViewerPrivate::createAtmFileLink(const QString &atmFileName) const { QFileInfo atmFileInfo(atmFileName); // tempfile name is /TMP/attachmentsRANDOM/atmFileInfo.fileName()" const QString tmpPath = QDir::tempPath() + QLatin1String("/attachments"); QDir().mkpath(tmpPath); QTemporaryDir *linkDir = new QTemporaryDir(tmpPath); QString linkPath = linkDir->path() + QLatin1Char('/') + atmFileInfo.fileName(); QFile *linkFile = new QFile(linkPath); linkFile->open(QIODevice::ReadWrite); const QString linkName = linkFile->fileName(); delete linkFile; delete linkDir; if (::link(QFile::encodeName(atmFileName).constData(), QFile::encodeName(linkName).constData()) == 0) { return linkName; // success } return QString(); } KService::Ptr ViewerPrivate::getServiceOffer(KMime::Content *content) { const QString fileName = mNodeHelper->writeNodeToTempFile(content); const QString contentTypeStr = QLatin1String(content->contentType()->mimeType()); // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { attachmentView(content); return KService::Ptr(nullptr); } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { /*TODO(Andris) port when on-demand loading is done && msgPart.isComplete() */ mimetype = MimeTreeParser::Util::mimetype(fileName); } return KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); } KMime::Content::List ViewerPrivate::selectedContents() { return mMimePartTree->selectedContents(); } void ViewerPrivate::attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer) { QString name = mNodeHelper->writeNodeToTempFile(node); // Make sure that it will not deleted when we switch from message. QTemporaryDir *tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX")); if (tmpDir->isValid()) { tmpDir->setAutoRemove(false); const QString path = tmpDir->path(); delete tmpDir; QFile f(name); const QUrl tmpFileName = QUrl::fromLocalFile(name); const QString newPath = path + QLatin1Char('/') + tmpFileName.fileName(); if (!f.copy(newPath)) { qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name << " to " << path; } else { name = newPath; } f.close(); } else { delete tmpDir; } QList lst; const QFileDevice::Permissions perms = QFile::permissions(name); QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser); const QUrl url = QUrl::fromLocalFile(name); lst.append(url); if (offer) { if ((!KRun::runService(*offer, lst, nullptr, true))) { QFile::remove(url.toLocalFile()); } } else { if ((!KRun::displayOpenWithDialog(lst, mMainWindow, true))) { QFile::remove(url.toLocalFile()); } } } void ViewerPrivate::attachmentOpen(KMime::Content *node) { KService::Ptr offer = getServiceOffer(node); if (!offer) { qCDebug(MESSAGEVIEWER_LOG) << "got no offer"; return; } attachmentOpenWith(node, offer); } bool ViewerPrivate::showEmoticons() const { return mForceEmoticons; } HtmlWriter *ViewerPrivate::htmlWriter() const { return mHtmlWriter; } CSSHelper *ViewerPrivate::cssHelper() const { return mCSSHelper; } MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const { return mNodeHelper; } Viewer *ViewerPrivate::viewer() const { return q; } Akonadi::Item ViewerPrivate::messageItem() const { return mMessageItem; } KMime::Message::Ptr ViewerPrivate::message() const { return mMessage; } bool ViewerPrivate::decryptMessage() const { if (!MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) { return mDecrytMessageOverwrite; } else { return true; } } void ViewerPrivate::displaySplashPage(const QString &message) { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("icon"), QStringLiteral("kmail") }, { QStringLiteral("name"), i18n("KMail") }, { QStringLiteral("subtitle"), i18n("The KDE Mail Client") }, { QStringLiteral("message"), message } }); } void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain) { mMsgDisplay = false; adjustLayout(); GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/")); GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default")); if (theme.isValid()) { mViewer->setHtml(theme.render(templateName, data, domain), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/'))); } else { qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme"; } mViewer->show(); } void ViewerPrivate::enableMessageDisplay() { if (mMsgDisplay) { return; } mMsgDisplay = true; adjustLayout(); } void ViewerPrivate::displayMessage() { showHideMimeTree(); mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); if (mMessageItem.hasAttribute()) { const MessageViewer::MessageDisplayFormatAttribute *const attr = mMessageItem.attribute(); setHtmlLoadExtOverride(attr->remoteContent()); setDisplayFormatMessageOverwrite(attr->messageFormat()); } htmlWriter()->begin(); htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont)); if (!mMainWindow) { q->setWindowTitle(mMessage->subject()->asUnicodeString()); } // Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker. // It is updated right after parseMsg() instead. mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate); if (mMessageItem.hasAttribute()) { //TODO: Insert link to clear error so that message might be resent const ErrorAttribute *const attr = mMessageItem.attribute(); Q_ASSERT(attr); if (!mForegroundError.isValid()) { const KColorScheme scheme = KColorScheme(QPalette::Active, KColorScheme::View); mForegroundError = scheme.foreground(KColorScheme::NegativeText).color(); mBackgroundError = scheme.background(KColorScheme::NegativeBackground).color(); } htmlWriter()->write(QStringLiteral( "
%4
").arg( mBackgroundError. name(), mForegroundError . name(), mForegroundError . name(), attr->message().toHtmlEscaped())); htmlWriter()->write(QStringLiteral("

")); } parseContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data())); #endif mColorBar->update(); htmlWriter()->write(QStringLiteral("")); connect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered, Qt::UniqueConnection); htmlWriter()->end(); } void ViewerPrivate::parseContent(KMime::Content *content) { assert(content != nullptr); mNodeHelper->removeTempFiles(); // Check if any part of this message is a v-card // v-cards can be either text/x-vcard or text/directory, so we need to check // both. KMime::Content *vCardContent = findContentByType(content, "text/x-vcard"); if (!vCardContent) { vCardContent = findContentByType(content, "text/directory"); } bool hasVCard = false; if (vCardContent) { // ### FIXME: We should only do this if the vCard belongs to the sender, // ### i.e. if the sender's email address is contained in the vCard. const QByteArray vCard = vCardContent->decodedContent(); KContacts::VCardConverter t; if (!t.parseVCards(vCard).isEmpty()) { hasVCard = true; mNodeHelper->writeNodeToTempFile(vCardContent); } } KMime::Message *message = dynamic_cast(content); if (message) { htmlWriter()->write(writeMessageHeader(message, hasVCard ? vCardContent : nullptr, true)); } // Pass control to the OTP now, which does the real work mNodeHelper->setNodeUnprocessed(mMessage.data(), true); MailViewerSource otpSource(this); MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper); //TODO: needs to end up in renderer: mMessage.data() != content /* show only single node */); otp.setAllowAsync(!mPrinting); otp.parseObjectTree(content, mMessage.data() != content /* parse/show only single node */); // TODO: Setting the signature state to nodehelper is not enough, it should actually // be added to the store, so that the message list correctly displays the signature state // of messages that were parsed at least once // store encrypted/signed status information in the KMMessage // - this can only be done *after* calling parseObjectTree() MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState( content); MimeTreeParser::KMMsgSignatureState signatureState = mNodeHelper->overallSignatureState(content); mNodeHelper->setEncryptionState(content, encryptionState); // Don't reset the signature state to "not signed" (e.g. if one canceled the // decryption of a signed messages which has already been decrypted before). if (signatureState != MimeTreeParser::KMMsgNotSigned || mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) { mNodeHelper->setSignatureState(content, signatureState); } showHideMimeTree(); } QString ViewerPrivate::writeMessageHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel) { if (!headerStylePlugin()) { qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMessageHeader() without a header style set!"; return {}; } QString href; if (vCardNode) { href = mNodeHelper->asHREF(vCardNode, QStringLiteral("body")); } headerStylePlugin()->headerStyle()->setHeaderStrategy(headerStylePlugin()->headerStrategy()); headerStylePlugin()->headerStyle()->setVCardName(href); headerStylePlugin()->headerStyle()->setPrinting(mPrinting); headerStylePlugin()->headerStyle()->setTopLevel(topLevel); headerStylePlugin()->headerStyle()->setAllowAsync(true); headerStylePlugin()->headerStyle()->setSourceObject(this); headerStylePlugin()->headerStyle()->setNodeHelper(mNodeHelper); headerStylePlugin()->headerStyle()->setMessagePath(mMessagePath); headerStylePlugin()->headerStyle()->setAttachmentHtml(attachmentHtml()); if (mMessageItem.isValid()) { Akonadi::MessageStatus status; status.setStatusFromFlags(mMessageItem.flags()); headerStylePlugin()->headerStyle()->setMessageStatus(status); headerStylePlugin()->headerStyle()->setCollectionName( mMessageItem.parentCollection().displayName()); } else { headerStylePlugin()->headerStyle()->setCollectionName(QString()); headerStylePlugin()->headerStyle()->setReadOnlyMessage(true); } return headerStylePlugin()->headerStyle()->format(aMsg); } void ViewerPrivate::showVCard(KMime::Content *msgPart) { const QByteArray vCard = msgPart->decodedContent(); VCardViewer *vcv = new VCardViewer(mMainWindow, vCard); vcv->setAttribute(Qt::WA_DeleteOnClose); vcv->show(); } void ViewerPrivate::initHtmlWidget() { if (!htmlWriter()) { mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr); mHtmlWriter = mPartHtmlWriter; } connect(mViewer->page(), &QWebEnginePage::linkHovered, this, &ViewerPrivate::slotUrlOn); connect(mViewer, &MailWebEngineView::openUrl, this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection); connect(mViewer, &MailWebEngineView::popupMenu, this, &ViewerPrivate::slotUrlPopup); connect(mViewer, &MailWebEngineView::wheelZoomChanged, this, &ViewerPrivate::slotWheelZoomChanged); connect(mViewer, &MailWebEngineView::messageMayBeAScam, this, &ViewerPrivate::slotMessageMayBeAScam); connect(mViewer, &MailWebEngineView::formSubmittedForbidden, this, &ViewerPrivate::slotFormSubmittedForbidden); connect(mViewer, &MailWebEngineView::mailTrackingFound, this, &ViewerPrivate::slotMailTrackingFound); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::showDetails, mViewer, &MailWebEngineView::slotShowDetails); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::moveMessageToTrash, this, &ViewerPrivate::moveMessageToTrash); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::messageIsNotAScam, this, &ViewerPrivate::slotMessageIsNotAScam); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::addToWhiteList, this, &ViewerPrivate::slotAddToWhiteList); connect(mViewer, &MailWebEngineView::pageIsScrolledToBottom, this, &ViewerPrivate::pageIsScrolledToBottom); } void ViewerPrivate::applyZoomValue(qreal factor, bool saveConfig) { if (mZoomActionMenu) { if (factor >= 10 && factor <= 300) { if (mZoomActionMenu->zoomFactor() != factor) { mZoomActionMenu->setZoomFactor(factor); mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0); if (saveConfig) { MessageViewer::MessageViewerSettings::self()->setZoomFactor(factor); } } } } } void ViewerPrivate::setWebViewZoomFactor(qreal factor) { applyZoomValue(factor, false); } qreal ViewerPrivate::webViewZoomFactor() const { qreal zoomFactor = -1; if (mZoomActionMenu) { zoomFactor = mZoomActionMenu->zoomFactor(); } return zoomFactor; } void ViewerPrivate::slotWheelZoomChanged(int numSteps) { const qreal factor = mZoomActionMenu->zoomFactor() + numSteps * 10; applyZoomValue(factor); } void ViewerPrivate::readConfig() { recreateCssHelper(); mForceEmoticons = MessageViewer::MessageViewerSettings::self()->showEmoticons(); if (mDisableEmoticonAction) { mDisableEmoticonAction->setChecked(!mForceEmoticons); } if (headerStylePlugin()) { headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons); } mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } mHtmlMailGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail(); readGravatarConfig(); if (mHeaderStyleMenuManager) { mHeaderStyleMenuManager->readConfig(); } setAttachmentStrategy(AttachmentStrategy::create(MessageViewer:: MessageViewerSettings::self()-> attachmentStrategy())); KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy()); if (raction) { raction->setChecked(true); } adjustLayout(); readGlobalOverrideCodec(); mViewer->readConfig(); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); if (mMessage) { update(); } mColorBar->update(); applyZoomValue(MessageViewer::MessageViewerSettings::self()->zoomFactor(), false); } void ViewerPrivate::readGravatarConfig() { Gravatar::GravatarCache::self()->setMaximumSize( Gravatar::GravatarSettings::self()->gravatarCacheSize()); if (!Gravatar::GravatarSettings::self()->gravatarSupportEnabled()) { Gravatar::GravatarCache::self()->clear(); } } void ViewerPrivate::recreateCssHelper() { delete mCSSHelper; mCSSHelper = new CSSHelper(mViewer); } void ViewerPrivate::slotGeneralFontChanged() { recreateCssHelper(); if (mMessage) { update(); } } void ViewerPrivate::writeConfig(bool sync) { MessageViewer::MessageViewerSettings::self()->setShowEmoticons(mForceEmoticons); MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mUseFixedFont); if (attachmentStrategy()) { MessageViewer::MessageViewerSettings::self()->setAttachmentStrategy(QLatin1String( attachmentStrategy() ->name())); } saveSplitterSizes(); if (sync) { Q_EMIT requestConfigSync(); } } const AttachmentStrategy *ViewerPrivate::attachmentStrategy() const { return mAttachmentStrategy; } void ViewerPrivate::setAttachmentStrategy(const AttachmentStrategy *strategy) { if (mAttachmentStrategy == strategy) { return; } mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart(); update(MimeTreeParser::Force); } QString ViewerPrivate::overrideEncoding() const { return mOverrideEncoding; } void ViewerPrivate::setOverrideEncoding(const QString &encoding) { if (encoding == mOverrideEncoding) { return; } mOverrideEncoding = encoding; if (mSelectEncodingAction) { if (encoding.isEmpty()) { mSelectEncodingAction->setCurrentItem(0); } else { const QStringList encodings = mSelectEncodingAction->items(); int i = 0; for (QStringList::const_iterator it = encodings.constBegin(), end = encodings.constEnd(); it != end; ++it, ++i) { if (MimeTreeParser::NodeHelper::encodingForName(*it) == encoding) { mSelectEncodingAction->setCurrentItem(i); break; } } if (i == encodings.size()) { // the value of encoding is unknown => use Auto qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding" << encoding << ". Using Auto instead."; mSelectEncodingAction->setCurrentItem(0); mOverrideEncoding.clear(); } } } update(MimeTreeParser::Force); } void ViewerPrivate::setPrinting(bool enable) { mPrinting = enable; } bool ViewerPrivate::printingMode() const { return mPrinting; } void ViewerPrivate::printMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::printPreviewMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::resetStateForNewMessage() { mHtmlLoadExtOverride = false; mClickedUrl.clear(); mImageUrl.clear(); enableMessageDisplay(); // just to make sure it's on mMessage.reset(); mNodeHelper->clear(); mMessagePartNode = nullptr; #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif mViewer->clearRelativePosition(); mViewer->hideAccessKeys(); if (!mPrinting) { setShowSignatureDetails(false); } mFindBar->closeBar(); mViewerPluginToolManager->closeAllTools(); mScamDetectionWarning->setVisible(false); mOpenAttachmentFolderWidget->setVisible(false); mSubmittedFormWarning->setVisible(false); mMailTrackingWarning->hideAndClear(); if (mPrinting) { if (MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettings()) { if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) { mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; } else { mLevelQuote = -1; } } else { mLevelQuote = -1; } } else { // mDisplayFormatMessageOverwrite // = (mDisplayFormatMessageOverwrite // == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting // : // MessageViewer::Viewer::Unknown; } } void ViewerPrivate::setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode) { mViewerPluginToolManager->updateActions(mMessageItem); mMessage = message; if (message) { mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); } #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(message.data())); update(updateMode); #endif } void ViewerPrivate::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); foreach (const Akonadi::Item::Id monitoredId, mMonitor.itemsMonitoredEx()) { mMonitor.setItemMonitored(Akonadi::Item(monitoredId), false); } Q_ASSERT(mMonitor.itemsMonitoredEx().isEmpty()); mMessageItem = item; if (mMessageItem.isValid()) { mMonitor.setItemMonitored(mMessageItem, true); } if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } setMessageInternal(mMessageItem.payload(), updateMode); } void ViewerPrivate::setMessage(const KMime::Message::Ptr &aMsg, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); Akonadi::Item item; item.setMimeType(KMime::Message::mimeType()); item.setPayload(aMsg); mMessageItem = item; setMessageInternal(aMsg, updateMode); } void ViewerPrivate::setMessagePart(KMime::Content *node) { // Cancel scheduled updates of the reader window, as that would stop the // timer of the HTML writer, which would make viewing attachment not work // anymore as not all HTML is written to the HTML part. // We're updating the reader window here ourselves anyway. mUpdateReaderWinTimer.stop(); if (node) { mMessagePartNode = node; if (node->bodyIsMessage()) { mMainWindow->setWindowTitle(node->bodyAsMessage()->subject()->asUnicodeString()); } else { QString windowTitle = MimeTreeParser::NodeHelper::fileName(node); if (windowTitle.isEmpty()) { windowTitle = node->contentDescription()->asUnicodeString(); } if (!windowTitle.isEmpty()) { mMainWindow->setWindowTitle(i18n("View Attachment: %1", windowTitle)); } } htmlWriter()->begin(); htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont)); parseContent(node); htmlWriter()->write(QStringLiteral("")); htmlWriter()->end(); } } void ViewerPrivate::showHideMimeTree() { #ifndef QT_NO_TREEVIEW if (mimePartTreeIsEmpty()) { mMimePartTree->hide(); return; } bool showMimeTree = false; if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always) { mMimePartTree->show(); showMimeTree = true; } else { // don't rely on QSplitter maintaining sizes for hidden widgets: saveSplitterSizes(); mMimePartTree->hide(); showMimeTree = false; } if (mToggleMimePartTreeAction && (mToggleMimePartTreeAction->isChecked() != showMimeTree)) { mToggleMimePartTreeAction->setChecked(showMimeTree); } #endif } void ViewerPrivate::atmViewMsg(const KMime::Message::Ptr &message) { Q_ASSERT(message); Q_EMIT showMessage(message, overrideEncoding()); } void ViewerPrivate::adjustLayout() { #ifndef QT_NO_TREEVIEW const int mimeH = MessageViewer::MessageViewerSettings::self()->mimePaneHeight(); const int messageH = MessageViewer::MessageViewerSettings::self()->messagePaneHeight(); QList splitterSizes; splitterSizes << messageH << mimeH; mSplitter->addWidget(mMimePartTree); mSplitter->setSizes(splitterSizes); if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always && mMsgDisplay) { mMimePartTree->show(); } else { mMimePartTree->hide(); } #endif if (mMsgDisplay) { mColorBar->show(); } else { mColorBar->hide(); } } void ViewerPrivate::saveSplitterSizes() const { #ifndef QT_NO_TREEVIEW if (!mSplitter || !mMimePartTree) { return; } if (mMimePartTree->isHidden()) { return; // don't rely on QSplitter maintaining sizes for hidden widgets. } MessageViewer::MessageViewerSettings::self()->setMimePaneHeight(mSplitter->sizes().at(1)); MessageViewer::MessageViewerSettings::self()->setMessagePaneHeight(mSplitter->sizes().at(0)); #endif } void ViewerPrivate::createWidgets() { //TODO: Make a MDN bar similar to Mozillas password bar and show MDNs here as soon as a // MDN enabled message is shown. QVBoxLayout *vlay = new QVBoxLayout(q); vlay->setContentsMargins(0, 0, 0, 0); mSplitter = new QSplitter(Qt::Vertical, q); connect(mSplitter, &QSplitter::splitterMoved, this, &ViewerPrivate::saveSplitterSizes); mSplitter->setObjectName(QStringLiteral("mSplitter")); mSplitter->setChildrenCollapsible(false); vlay->addWidget(mSplitter); #ifndef QT_NO_TREEVIEW mMimePartTree = new MimePartTreeView(mSplitter); connect(mMimePartTree, &QAbstractItemView::activated, this, &ViewerPrivate::slotMimePartSelected); connect(mMimePartTree, &QWidget::customContextMenuRequested, this, &ViewerPrivate::slotMimeTreeContextMenuRequested); #endif mBox = new QWidget(mSplitter); QHBoxLayout *mBoxHBoxLayout = new QHBoxLayout(mBox); mBoxHBoxLayout->setContentsMargins(0, 0, 0, 0); mColorBar = new HtmlStatusBar(mBox); mBoxHBoxLayout->addWidget(mColorBar); QWidget *readerBox = new QWidget(mBox); QVBoxLayout *readerBoxVBoxLayout = new QVBoxLayout(readerBox); readerBoxVBoxLayout->setContentsMargins(0, 0, 0, 0); mBoxHBoxLayout->addWidget(readerBox); mColorBar->setObjectName(QStringLiteral("mColorBar")); mColorBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); mSubmittedFormWarning = new SubmittedFormWarningWidget(readerBox); mSubmittedFormWarning->setObjectName(QStringLiteral("submittedformwarning")); readerBoxVBoxLayout->addWidget(mSubmittedFormWarning); mMailTrackingWarning = new MailTrackingWarningWidget(readerBox); mMailTrackingWarning->setObjectName(QStringLiteral("mailtrackingwarning")); readerBoxVBoxLayout->addWidget(mMailTrackingWarning); mScamDetectionWarning = new ScamDetectionWarningWidget(readerBox); mScamDetectionWarning->setObjectName(QStringLiteral("scandetectionwarning")); readerBoxVBoxLayout->addWidget(mScamDetectionWarning); mOpenAttachmentFolderWidget = new OpenAttachmentFolderWidget(readerBox); mOpenAttachmentFolderWidget->setObjectName(QStringLiteral("openattachementfolderwidget")); readerBoxVBoxLayout->addWidget(mOpenAttachmentFolderWidget); mTextToSpeechWidget = new KPIMTextEdit::TextToSpeechWidget(readerBox); mTextToSpeechWidget->setObjectName(QStringLiteral("texttospeechwidget")); readerBoxVBoxLayout->addWidget(mTextToSpeechWidget); mViewer = new MailWebEngineView(mActionCollection, readerBox); mViewer->setViewer(this); readerBoxVBoxLayout->addWidget(mViewer); mViewer->setObjectName(QStringLiteral("mViewer")); mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(readerBox, this); mViewerPluginToolManager->setActionCollection(mActionCollection); mViewerPluginToolManager->setPluginName(QStringLiteral("messageviewer")); mViewerPluginToolManager->setServiceTypeName(QStringLiteral("MessageViewer/ViewerPlugin")); if (!mViewerPluginToolManager->initializePluginList()) { qCDebug(MESSAGEVIEWER_LOG) << " Impossible to initialize plugins"; } mViewerPluginToolManager->createView(); connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this, &ViewerPrivate::slotActivatePlugin); mSliderContainer = new KPIMTextEdit::SlideContainer(readerBox); mSliderContainer->setObjectName(QStringLiteral("slidercontainer")); readerBoxVBoxLayout->addWidget(mSliderContainer); mFindBar = new WebEngineViewer::FindBarWebEngineView(mViewer, q); connect(mFindBar, &WebEngineViewer::FindBarWebEngineView::hideFindBar, mSliderContainer, &KPIMTextEdit::SlideContainer::slideOut); mSliderContainer->setContent(mFindBar); #ifndef QT_NO_TREEVIEW mSplitter->setStretchFactor(mSplitter->indexOf(mMimePartTree), 0); #endif } void ViewerPrivate::slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin) { mCSSHelper->setHeaderPlugin(plugin); mHeaderStylePlugin = plugin; update(MimeTreeParser::Force); } void ViewerPrivate::slotStyleUpdated() { update(MimeTreeParser::Force); } void ViewerPrivate::createActions() { KActionCollection *ac = mActionCollection; mHeaderStyleMenuManager = new MessageViewer::HeaderStyleMenuManager(ac, this); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleChanged, this, &ViewerPrivate::slotStyleChanged); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleUpdated, this, &ViewerPrivate::slotStyleUpdated); if (!ac) { return; } mZoomActionMenu = new WebEngineViewer::ZoomActionMenu(this); connect(mZoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, this, &ViewerPrivate::slotZoomChanged); mZoomActionMenu->setActionCollection(ac); mZoomActionMenu->createZoomActions(); // attachment style KActionMenu *attachmentMenu = new KActionMenu(i18nc("View->", "&Attachments"), this); ac->addAction(QStringLiteral("view_attachments"), attachmentMenu); addHelpTextAction(attachmentMenu, i18n("Choose display style of attachments")); QActionGroup *group = new QActionGroup(this); KToggleAction *raction = new KToggleAction(i18nc("View->attachments->", "&As Icons"), this); ac->addAction(QStringLiteral("view_attachments_as_icons"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotIconicAttachments); addHelpTextAction(raction, i18n("Show all attachments as icons. Click to see them.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Smart"), this); ac->addAction(QStringLiteral("view_attachments_smart"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotSmartAttachments); addHelpTextAction(raction, i18n("Show attachments as suggested by sender.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Inline"), this); ac->addAction(QStringLiteral("view_attachments_inline"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotInlineAttachments); addHelpTextAction(raction, i18n("Show all attachments inline (if possible)")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Hide"), this); ac->addAction(QStringLiteral("view_attachments_hide"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotHideAttachments); addHelpTextAction(raction, i18n("Do not show attachments in the message viewer")); group->addAction(raction); attachmentMenu->addAction(raction); mHeaderOnlyAttachmentsAction = new KToggleAction(i18nc("View->attachments->", "In Header Only"), this); ac->addAction(QStringLiteral("view_attachments_headeronly"), mHeaderOnlyAttachmentsAction); connect(mHeaderOnlyAttachmentsAction, &QAction::triggered, this, &ViewerPrivate::slotHeaderOnlyAttachments); addHelpTextAction(mHeaderOnlyAttachmentsAction, i18n("Show Attachments only in the header of the mail")); group->addAction(mHeaderOnlyAttachmentsAction); attachmentMenu->addAction(mHeaderOnlyAttachmentsAction); // Set Encoding submenu mSelectEncodingAction = new KSelectAction(QIcon::fromTheme(QStringLiteral( "character-set")), i18n("&Set Encoding"), this); mSelectEncodingAction->setToolBarMode(KSelectAction::MenuMode); ac->addAction(QStringLiteral("encoding"), mSelectEncodingAction); connect(mSelectEncodingAction, qOverload(&KSelectAction::triggered), this, &ViewerPrivate::slotSetEncoding); QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false); encodings.prepend(i18n("Auto")); mSelectEncodingAction->setItems(encodings); mSelectEncodingAction->setCurrentItem(0); // // Message Menu // // copy selected text to clipboard mCopyAction = ac->addAction(KStandardAction::Copy, QStringLiteral("kmail_copy")); mCopyAction->setText(i18n("Copy Text")); connect(mCopyAction, &QAction::triggered, this, &ViewerPrivate::slotCopySelectedText); connect(mViewer, &MailWebEngineView::selectionChanged, this, &ViewerPrivate::viewerSelectionChanged); viewerSelectionChanged(); // copy all text to clipboard mSelectAllAction = new QAction(i18n("Select All Text"), this); ac->addAction(QStringLiteral("mark_all_text"), mSelectAllAction); connect(mSelectAllAction, &QAction::triggered, this, &ViewerPrivate::selectAll); ac->setDefaultShortcut(mSelectAllAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A)); // copy Email address to clipboard mCopyURLAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link Address"), this); ac->addAction(QStringLiteral("copy_url"), mCopyURLAction); connect(mCopyURLAction, &QAction::triggered, this, &ViewerPrivate::slotUrlCopy); // open URL mUrlOpenAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n( "Open URL"), this); ac->addAction(QStringLiteral("open_url"), mUrlOpenAction); connect(mUrlOpenAction, &QAction::triggered, this, &ViewerPrivate::slotOpenUrl); // use fixed font mToggleFixFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); ac->addAction(QStringLiteral("toggle_fixedfont"), mToggleFixFontAction); connect(mToggleFixFontAction, &QAction::triggered, this, &ViewerPrivate::slotToggleFixedFont); ac->setDefaultShortcut(mToggleFixFontAction, QKeySequence(Qt::Key_X)); // Show message structure viewer mToggleMimePartTreeAction = new KToggleAction(i18n("Show Message Structure"), this); ac->addAction(QStringLiteral("toggle_mimeparttree"), mToggleMimePartTreeAction); connect(mToggleMimePartTreeAction, &QAction::toggled, this, &ViewerPrivate::slotToggleMimePartTree); ac->setDefaultShortcut(mToggleMimePartTreeAction, QKeySequence(Qt::Key_D + Qt::CTRL + Qt::ALT)); mViewSourceAction = new QAction(i18n("&View Source"), this); ac->addAction(QStringLiteral("view_source"), mViewSourceAction); connect(mViewSourceAction, &QAction::triggered, this, &ViewerPrivate::slotShowMessageSource); ac->setDefaultShortcut(mViewSourceAction, QKeySequence(Qt::Key_V)); mSaveMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "&Save message..."), this); ac->addAction(QStringLiteral("save_message"), mSaveMessageAction); connect(mSaveMessageAction, &QAction::triggered, this, &ViewerPrivate::slotSaveMessage); //Laurent: conflict with kmail shortcut //mSaveMessageAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); mSaveMessageDisplayFormat = new QAction(i18n("&Save Display Format"), this); ac->addAction(QStringLiteral("save_message_display_format"), mSaveMessageDisplayFormat); connect(mSaveMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotSaveMessageDisplayFormat); mResetMessageDisplayFormat = new QAction(i18n("&Reset Display Format"), this); ac->addAction(QStringLiteral("reset_message_display_format"), mResetMessageDisplayFormat); connect(mResetMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotResetMessageDisplayFormat); // // Scroll actions // mScrollUpAction = new QAction(i18n("Scroll Message Up"), this); ac->setDefaultShortcut(mScrollUpAction, QKeySequence(Qt::Key_Up)); ac->addAction(QStringLiteral("scroll_up"), mScrollUpAction); connect(mScrollUpAction, &QAction::triggered, q, &Viewer::slotScrollUp); mScrollDownAction = new QAction(i18n("Scroll Message Down"), this); ac->setDefaultShortcut(mScrollDownAction, QKeySequence(Qt::Key_Down)); ac->addAction(QStringLiteral("scroll_down"), mScrollDownAction); connect(mScrollDownAction, &QAction::triggered, q, &Viewer::slotScrollDown); mScrollUpMoreAction = new QAction(i18n("Scroll Message Up (More)"), this); ac->setDefaultShortcut(mScrollUpMoreAction, QKeySequence(Qt::Key_PageUp)); ac->addAction(QStringLiteral("scroll_up_more"), mScrollUpMoreAction); connect(mScrollUpMoreAction, &QAction::triggered, q, &Viewer::slotScrollPrior); mScrollDownMoreAction = new QAction(i18n("Scroll Message Down (More)"), this); ac->setDefaultShortcut(mScrollDownMoreAction, QKeySequence(Qt::Key_PageDown)); ac->addAction(QStringLiteral("scroll_down_more"), mScrollDownMoreAction); connect(mScrollDownMoreAction, &QAction::triggered, q, &Viewer::slotScrollNext); // // Actions not in menu // // Toggle HTML display mode. mToggleDisplayModeAction = new KToggleAction(i18n("Toggle HTML Display Mode"), this); ac->addAction(QStringLiteral("toggle_html_display_mode"), mToggleDisplayModeAction); ac->setDefaultShortcut(mToggleDisplayModeAction, QKeySequence(Qt::SHIFT + Qt::Key_H)); connect(mToggleDisplayModeAction, &QAction::triggered, this, &ViewerPrivate::slotToggleHtmlMode); addHelpTextAction(mToggleDisplayModeAction, i18n("Toggle display mode between HTML and plain text")); // Load external reference QAction *loadExternalReferenceAction = new QAction(i18n("Load external references"), this); ac->addAction(QStringLiteral("load_external_reference"), loadExternalReferenceAction); ac->setDefaultShortcut(loadExternalReferenceAction, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_R)); connect(loadExternalReferenceAction, &QAction::triggered, this, &ViewerPrivate::slotLoadExternalReference); addHelpTextAction(loadExternalReferenceAction, i18n("Load external references from the Internet for this message.")); mSpeakTextAction = new QAction(i18n("Speak Text"), this); mSpeakTextAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); ac->addAction(QStringLiteral("speak_text"), mSpeakTextAction); connect(mSpeakTextAction, &QAction::triggered, this, &ViewerPrivate::slotSpeakText); mCopyImageLocation = new QAction(i18n("Copy Image Location"), this); mCopyImageLocation->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization"))); ac->addAction(QStringLiteral("copy_image_location"), mCopyImageLocation); ac->setShortcutsConfigurable(mCopyImageLocation, false); connect(mCopyImageLocation, &QAction::triggered, this, &ViewerPrivate::slotCopyImageLocation); mFindInMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n( "&Find in Message..."), this); ac->addAction(QStringLiteral("find_in_messages"), mFindInMessageAction); connect(mFindInMessageAction, &QAction::triggered, this, &ViewerPrivate::slotFind); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); mShareServiceUrlMenu = mShareServiceManager->menu(); ac->addAction(QStringLiteral("shareservice_menu"), mShareServiceUrlMenu); connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ViewerPrivate::slotServiceUrlSelected); mDisableEmoticonAction = new KToggleAction(i18n("Disable Emoticon"), this); ac->addAction(QStringLiteral("disable_emoticon"), mDisableEmoticonAction); connect(mDisableEmoticonAction, &QAction::triggered, this, &ViewerPrivate::slotToggleEmoticons); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); } void ViewerPrivate::showContextMenu(KMime::Content *content, const QPoint &pos) { #ifndef QT_NO_TREEVIEW if (!content) { return; } if (content->contentType(false)) { if (content->contentType()->mimeType() == "text/x-moz-deleted") { return; } } const bool isAttachment = !content->contentType()->isMultipart() && !content->isTopLevel(); const bool isRoot = (content == mMessage.data()); const auto hasAttachments = KMime::hasAttachment(mMessage.data()); QMenu popup; if (!isRoot) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save &As..."), this, &ViewerPrivate::slotAttachmentSaveAs); if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open"), this, &ViewerPrivate::slotAttachmentOpen); if (selectedContents().count() == 1) { createOpenWithMenu(&popup, QLatin1String(content->contentType()->mimeType()), false); } else { popup.addAction(i18n("Open With..."), this, &ViewerPrivate::slotAttachmentOpenWith); } popup.addAction(i18nc("to view something", "View"), this, &ViewerPrivate::slotAttachmentView); } } if (hasAttachments) { popup.addAction(i18n("Save All Attachments..."), this, &ViewerPrivate::slotAttachmentSaveAll); } // edit + delete only for attachments if (!isRoot) { if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"), this, &ViewerPrivate::slotAttachmentCopy); } if (!content->isTopLevel()) { popup.addSeparator(); popup.addAction(i18n("Properties"), this, &ViewerPrivate::slotAttachmentProperties); } } popup.exec(mMimePartTree->viewport()->mapToGlobal(pos)); #endif } KToggleAction *ViewerPrivate::actionForAttachmentStrategy( const AttachmentStrategy *as) { if (!mActionCollection) { return nullptr; } QString actionName; if (as == AttachmentStrategy::iconic()) { - actionName = QLatin1String("view_attachments_as_icons"); + actionName = QStringLiteral("view_attachments_as_icons"); } else if (as == AttachmentStrategy::smart()) { - actionName = QLatin1String("view_attachments_smart"); + actionName = QStringLiteral("view_attachments_smart"); } else if (as == AttachmentStrategy::inlined()) { - actionName = QLatin1String("view_attachments_inline"); + actionName = QStringLiteral("view_attachments_inline"); } else if (as == AttachmentStrategy::hidden()) { - actionName = QLatin1String("view_attachments_hide"); + actionName = QStringLiteral("view_attachments_hide"); } else if (as == AttachmentStrategy::headerOnly()) { - actionName = QLatin1String("view_attachments_headeronly"); + actionName = QStringLiteral("view_attachments_headeronly"); } if (actionName.isEmpty()) { return nullptr; } else { return static_cast(mActionCollection->action(actionName)); } } void ViewerPrivate::readGlobalOverrideCodec() { // if the global character encoding wasn't changed then there's nothing to do if (MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding() == mOldGlobalOverrideEncoding) { return; } setOverrideEncoding(MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding()); mOldGlobalOverrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); } const QTextCodec *ViewerPrivate::overrideCodec() const { if (mOverrideEncoding.isEmpty() || mOverrideEncoding == QLatin1String("Auto")) { // Auto return nullptr; } else { return ViewerPrivate::codecForName(mOverrideEncoding.toLatin1()); } } static QColor nextColor(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv((h + 50) % 360, qMax(s, 64), v); } QString ViewerPrivate::renderAttachments(KMime::Content *node, const QColor &bgColor) const { if (!node) { return QString(); } QString html; KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (child) { QString subHtml = renderAttachments(child, nextColor(bgColor)); if (!subHtml.isEmpty()) { QString margin; if (node != mMessage.data() || headerStylePlugin()->hasMargin()) { margin = QStringLiteral("padding:2px; margin:2px; "); } QString align = headerStylePlugin()->alignment(); const QByteArray mediaTypeLower = node->contentType()->mediaType().toLower(); const bool result = (mediaTypeLower == "message" || mediaTypeLower == "multipart" || node == mMessage.data()); if (result) { html += QStringLiteral("
").arg(bgColor.name()). - arg(margin).arg(align); + arg(margin, align); } html += subHtml; if (result) { html += QLatin1String("
"); } } } else { Util::AttachmentDisplayInfo info = Util::attachmentDisplayInfo(node); if (info.displayInHeader) { html += QLatin1String(" "); } } for (KMime::Content *extraNode : mNodeHelper->extraContents(node)) { html += renderAttachments(extraNode, bgColor); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(node); if (next) { html += renderAttachments(next, nextColor(bgColor)); } return html; } KMime::Content *ViewerPrivate::findContentByType(KMime::Content *content, const QByteArray &type) { const auto list = content->contents(); for (KMime::Content *c : list) { if (c->contentType()->mimeType() == type) { return c; } } return nullptr; } //----------------------------------------------------------------------------- const QTextCodec *ViewerPrivate::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } void ViewerPrivate::update(MimeTreeParser::UpdateMode updateMode) { // Avoid flicker, somewhat of a cludge if (updateMode == MimeTreeParser::Force) { // stop the timer to avoid calling updateReaderWin twice mUpdateReaderWinTimer.stop(); saveRelativePosition(); updateReaderWin(); } else if (mUpdateReaderWinTimer.isActive()) { mUpdateReaderWinTimer.setInterval(150); } else { mUpdateReaderWinTimer.start(0); } } void ViewerPrivate::slotOpenUrl() { slotUrlOpen(); } void ViewerPrivate::slotUrlOpen(const QUrl &url) { if (!url.isEmpty()) { mClickedUrl = url; } // First, let's see if the URL handler manager can handle the URL. If not, try KRun for some // known URLs, otherwise fallback to emitting a signal. // That signal is caught by KMail, and in case of mailto URLs, a composer is shown. if (URLHandlerManager::instance()->handleClick(mClickedUrl, this)) { return; } Q_EMIT urlClicked(mMessageItem, mClickedUrl); } void ViewerPrivate::checkPhishingUrl() { if (!PimCommon::NetworkUtil::self()->lowBandwidth() && MessageViewer::MessageViewerSettings::self()->checkPhishingUrl() && (mClickedUrl.scheme() != QLatin1String("mailto"))) { mPhishingDatabase->checkUrl(mClickedUrl); } else { executeRunner(mClickedUrl); } } void ViewerPrivate::executeRunner(const QUrl &url) { if (!MessageViewer::Util::handleUrlWithQDesktopServices(url)) { KRun *runner = new KRun(url, viewer()); // will delete itself runner->setRunExecutables(false); } } void ViewerPrivate::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status) { switch (status) { case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork: KMessageBox::error(mMainWindow, i18n("The network is broken."), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl: KMessageBox::error(mMainWindow, i18n("The URL %1 is not valid.", url.toString()), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::Ok: break; case WebEngineViewer::CheckPhishingUrlUtil::MalWare: if (!urlIsAMalwareButContinue()) { return; } break; case WebEngineViewer::CheckPhishingUrlUtil::Unknown: qCWarning(MESSAGEVIEWER_LOG) << "WebEngineViewer::slotCheckedUrlFinished unknown error "; break; } executeRunner(url); } bool ViewerPrivate::urlIsAMalwareButContinue() { if (KMessageBox::No == KMessageBox::warningYesNo(mMainWindow, i18n( "This web site is a malware, do you want to continue to show it?"), i18n("Malware"))) { return false; } return true; } void ViewerPrivate::slotUrlOn(const QString &link) { // The "link" we get here is not URL-encoded, and therefore there is no way QUrl could // parse it correctly. To workaround that, we use QWebFrame::hitTestContent() on the mouse position // to get the URL before WebKit managed to mangle it. QUrl url(link); const QString protocol = url.scheme(); if (protocol == QLatin1String("kmail") || protocol == QLatin1String("x-kmail") || protocol == QLatin1String("attachment") || (protocol.isEmpty() && url.path().isEmpty())) { mViewer->setAcceptDrops(false); } else { mViewer->setAcceptDrops(true); } mViewer->setLinkHovered(url); if (link.trimmed().isEmpty()) { KPIM::BroadcastStatus::instance()->reset(); Q_EMIT showStatusBarMessage(QString()); return; } QString msg = URLHandlerManager::instance()->statusBarMessage(url, this); if (msg.isEmpty()) { msg = link; } Q_EMIT showStatusBarMessage(msg); } void ViewerPrivate::slotUrlPopup(const WebEngineViewer::WebHitTestResult &result) { if (!mMsgDisplay) { return; } mClickedUrl = result.linkUrl(); mImageUrl = result.imageUrl(); const QPoint aPos = mViewer->mapToGlobal(result.pos()); if (URLHandlerManager::instance()->handleContextMenuRequest(mClickedUrl, aPos, this)) { return; } if (!mActionCollection) { return; } if (mClickedUrl.scheme() == QLatin1String("mailto")) { mCopyURLAction->setText(i18n("Copy Email Address")); } else { mCopyURLAction->setText(i18n("Copy Link Address")); } Q_EMIT displayPopupMenu(mMessageItem, result, aPos); Q_EMIT popupMenu(mMessageItem, mClickedUrl, mImageUrl, aPos); } void ViewerPrivate::slotLoadExternalReference() { if (mColorBar->isNormal() || htmlLoadExtOverride()) { return; } setHtmlLoadExtOverride(true); update(MimeTreeParser::Force); } Viewer::DisplayFormatMessage translateToDisplayFormat(MimeTreeParser::Util::HtmlMode mode) { switch (mode) { case MimeTreeParser::Util::Normal: return Viewer::Unknown; case MimeTreeParser::Util::Html: return Viewer::Html; case MimeTreeParser::Util::MultipartPlain: return Viewer::Text; case MimeTreeParser::Util::MultipartHtml: return Viewer::Html; case MimeTreeParser::Util::MultipartIcal: return Viewer::ICal; } return Viewer::Unknown; } void ViewerPrivate::slotToggleHtmlMode() { const auto availableModes = mColorBar->availableModes(); const int availableModeSize(availableModes.size()); // for (int i = 0; i < availableModeSize; ++i) { // qDebug() << " Mode " << MimeTreeParser::Util::htmlModeToString(availableModes.at(i)); // } // qDebug() << " availableModeSize"<isNormal() || availableModeSize < 2) { return; } mScamDetectionWarning->setVisible(false); const MimeTreeParser::Util::HtmlMode mode = mColorBar->mode(); const int pos = (availableModes.indexOf(mode) + 1) % availableModeSize; setDisplayFormatMessageOverwrite(translateToDisplayFormat(availableModes[pos])); update(MimeTreeParser::Force); // for (int i = 0; i < availableModeSize; ++i) { // qDebug() << "AFTER Mode " << MimeTreeParser::Util::htmlModeToString(availableModes.at(i)); // } // qDebug() << " Assign modes " << availableModes; mColorBar->setAvailableModes(availableModes); } void ViewerPrivate::slotFind() { if (mViewer->hasSelection()) { mFindBar->setText(mViewer->selectedText()); } mSliderContainer->slideIn(); mFindBar->focusAndSetCursor(); } void ViewerPrivate::slotToggleFixedFont() { mUseFixedFont = !mUseFixedFont; update(MimeTreeParser::Force); } void ViewerPrivate::slotToggleMimePartTree() { if (mToggleMimePartTreeAction->isChecked()) { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2( MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always); } else { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2( MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Never); } showHideMimeTree(); } void ViewerPrivate::slotShowMessageSource() { if (!mMessage) { return; } mNodeHelper->messageWithExtraContent(mMessage.data()); QPointer viewer = new MailSourceWebEngineViewer; // deletes itself upon close mListMailSourceViewer.append(viewer); viewer->setWindowTitle(i18n("Message as Plain Text")); const QString rawMessage = QString::fromLatin1(mMessage->encodedContent()); viewer->setRawSource(rawMessage); viewer->setDisplayedSource(mViewer->page()); if (mUseFixedFont) { viewer->setFixedFont(); } viewer->show(); } void ViewerPrivate::updateReaderWin() { if (!mMsgDisplay) { return; } if (mRecursionCountForDisplayMessage + 1 > 1) { // This recursion here can happen because the ObjectTreeParser in parseMsg() can exec() an // eventloop. // This happens in two cases: // 1) The ContactSearchJob started by FancyHeaderStyle::format // 2) Various modal passphrase dialogs for decryption of a message (bug 96498) // // While the exec() eventloop is running, it is possible that a timer calls updateReaderWin(), // and not aborting here would confuse the state terribly. qCWarning(MESSAGEVIEWER_LOG) << "Danger, recursion while displaying a message!"; return; } mRecursionCountForDisplayMessage++; mViewer->setAllowExternalContent(htmlLoadExternal()); htmlWriter()->reset(); //TODO: if the item doesn't have the payload fetched, try to fetch it? Maybe not here, but in setMessageItem. if (mMessage) { mColorBar->show(); displayMessage(); } else if (mMessagePartNode) { setMessagePart(mMessagePartNode); } else { mColorBar->hide(); #ifndef QT_NO_TREEVIEW mMimePartTree->hide(); #endif htmlWriter()->begin(); htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont) + QLatin1String("")); htmlWriter()->end(); } mRecursionCountForDisplayMessage--; } void ViewerPrivate::slotMimePartSelected(const QModelIndex &index) { #ifndef QT_NO_TREEVIEW KMime::Content *content = static_cast(index.internalPointer()); if (!mMimePartTree->mimePartModel()->parent(index).isValid() && index.row() == 0) { update(MimeTreeParser::Force); } else { setMessagePart(content); } #endif } void ViewerPrivate::slotIconicAttachments() { setAttachmentStrategy(AttachmentStrategy::iconic()); } void ViewerPrivate::slotSmartAttachments() { setAttachmentStrategy(AttachmentStrategy::smart()); } void ViewerPrivate::slotInlineAttachments() { setAttachmentStrategy(AttachmentStrategy::inlined()); } void ViewerPrivate::slotHideAttachments() { setAttachmentStrategy(AttachmentStrategy::hidden()); } void ViewerPrivate::slotHeaderOnlyAttachments() { setAttachmentStrategy(AttachmentStrategy::headerOnly()); } void ViewerPrivate::attachmentView(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { atmViewMsg(atmNode->parent()->bodyAsMessage()); } else if ((qstricmp(atmNode->contentType()->mediaType().constData(), "text") == 0) && ((qstricmp(atmNode->contentType()->subType().constData(), "x-vcard") == 0) || (qstricmp(atmNode->contentType()->subType().constData(), "directory") == 0))) { setMessagePart(atmNode); } else { Q_EMIT showReader(atmNode, htmlMail(), overrideEncoding()); } } } void ViewerPrivate::slotDelayedResize() { mSplitter->setGeometry(0, 0, q->width(), q->height()); } void ViewerPrivate::slotPrintPreview() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); if (!mMessage) { return; } //Need to delay QTimer::singleShot(1 * 1000, this, &ViewerPrivate::slotDelayPrintPreview); } void ViewerPrivate::slotDelayPrintPreview() { QPrintPreviewDialog *dialog = new QPrintPreviewDialog(q); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->resize(800, 750); connect(dialog, &QPrintPreviewDialog::paintRequested, this, [=](QPrinter *printing) { QApplication::setOverrideCursor(Qt::WaitCursor); mViewer->execPrintPreviewPage(printing, 10*1000); QApplication::restoreOverrideCursor(); }); dialog->open(this, SIGNAL(printingFinished())); } void ViewerPrivate::slotOpenInBrowser() { WebEngineViewer::WebEngineExportHtmlPageJob *job = new WebEngineViewer::WebEngineExportHtmlPageJob; job->setEngineView(mViewer); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed, this, &ViewerPrivate::slotExportHtmlPageFailed); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::success, this, &ViewerPrivate::slotExportHtmlPageSuccess); job->start(); } void ViewerPrivate::slotExportHtmlPageSuccess(const QString &filename) { const QUrl url(QUrl::fromLocalFile(filename)); KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(url, QStringLiteral("text/html"), q, flags); Q_EMIT printingFinished(); } void ViewerPrivate::slotExportHtmlPageFailed() { qCDebug(MESSAGEVIEWER_LOG) << " Export HTML failed"; Q_EMIT printingFinished(); } void ViewerPrivate::slotPrintMessage() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); if (!mMessage) { return; } if (mCurrentPrinter) { return; } mCurrentPrinter = new QPrinter(); QPointer dialog = new QPrintDialog(mCurrentPrinter, mMainWindow); dialog->setWindowTitle(i18n("Print Document")); if (dialog->exec() != QDialog::Accepted) { slotHandlePagePrinted(false); delete dialog; return; } delete dialog; mViewer->page()->print(mCurrentPrinter, invoke(this, &ViewerPrivate::slotHandlePagePrinted)); } void ViewerPrivate::slotHandlePagePrinted(bool result) { Q_UNUSED(result); delete mCurrentPrinter; mCurrentPrinter = nullptr; Q_EMIT printingFinished(); } void ViewerPrivate::slotSetEncoding() { if (mSelectEncodingAction) { if (mSelectEncodingAction->currentItem() == 0) { // Auto mOverrideEncoding.clear(); } else { mOverrideEncoding = MimeTreeParser::NodeHelper::encodingForName( mSelectEncodingAction->currentText()); } update(MimeTreeParser::Force); } } HeaderStylePlugin *ViewerPrivate::headerStylePlugin() const { return mHeaderStylePlugin; } QString ViewerPrivate::attachmentHtml() const { const QColor background = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); QString html = renderAttachments(mMessage.data(), background); if (!html.isEmpty()) { const bool isFancyTheme = (headerStylePlugin()->name() == QStringLiteral("fancy")); if (isFancyTheme) { html.prepend(QStringLiteral("
%1 
").arg(i18n( "Attachments:"))); } } return html; } void ViewerPrivate::executeCustomScriptsAfterLoading() { disconnect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading); // inject attachments in header view // we have to do that after the otp has run so we also see encrypted parts mViewer->scrollToRelativePosition(mViewer->relativePosition()); mViewer->clearRelativePosition(); } void ViewerPrivate::slotSettingsChanged() { update(MimeTreeParser::Force); } void ViewerPrivate::slotMimeTreeContextMenuRequested(const QPoint &pos) { #ifndef QT_NO_TREEVIEW QModelIndex index = mMimePartTree->indexAt(pos); if (index.isValid()) { KMime::Content *content = static_cast(index.internalPointer()); showContextMenu(content, pos); } #endif } void ViewerPrivate::slotAttachmentOpenWith() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpenWith(content); } #endif } void ViewerPrivate::slotAttachmentOpen() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpen(content); } #endif } void ViewerPrivate::showOpenAttachmentFolderWidget(const QList &urls) { mOpenAttachmentFolderWidget->setUrls(urls); mOpenAttachmentFolderWidget->slotShowWarning(); } bool ViewerPrivate::mimePartTreeIsEmpty() const { #ifndef QT_NO_TREEVIEW return mMimePartTree->model()->rowCount() == 0; #else return false; #endif } void ViewerPrivate::setPluginName(const QString &pluginName) { mHeaderStyleMenuManager->setPluginName(pluginName); } QList ViewerPrivate::viewerPluginActionList( ViewerPluginInterface::SpecificFeatureTypes features) { if (mViewerPluginToolManager) { return mViewerPluginToolManager->viewerPluginActionList(features); } return QList(); } void ViewerPrivate::slotActivatePlugin(ViewerPluginInterface *interface) { interface->setMessage(mMessage); interface->setMessageItem(mMessageItem); interface->setUrl(mClickedUrl); interface->setCurrentCollection(mMessageItem.parentCollection()); const QString text = mViewer->selectedText(); if (!text.isEmpty()) { interface->setText(text); } interface->execute(); } void ViewerPrivate::slotAttachmentSaveAs() { const auto contents = selectedContents(); QList urlList; if (Util::saveAttachments(contents, mMainWindow, urlList)) { showOpenAttachmentFolderWidget(urlList); } } void ViewerPrivate::slotAttachmentSaveAll() { const auto contents = mMessage->attachments(); QList urlList; if (Util::saveAttachments(contents, mMainWindow, urlList)) { showOpenAttachmentFolderWidget(urlList); } } void ViewerPrivate::slotAttachmentView() { const auto contents = selectedContents(); for (KMime::Content *content : contents) { attachmentView(content); } } void ViewerPrivate::slotAttachmentProperties() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } for (KMime::Content *content : contents) { attachmentProperties(content); } } void ViewerPrivate::attachmentProperties(KMime::Content *content) { MessageCore::AttachmentPropertiesDialog *dialog = new MessageCore::AttachmentPropertiesDialog( content, mMainWindow); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void ViewerPrivate::slotAttachmentCopy() { #ifndef QT_NO_CLIPBOARD attachmentCopy(selectedContents()); #endif } void ViewerPrivate::attachmentCopy(const KMime::Content::List &contents) { #ifndef QT_NO_CLIPBOARD if (contents.isEmpty()) { return; } QList urls; for (KMime::Content *content : contents) { auto url = QUrl::fromLocalFile(mNodeHelper->writeNodeToTempFile(content)); if (!url.isValid()) { continue; } urls.append(url); } if (urls.isEmpty()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(urls); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); #endif } void ViewerPrivate::slotLevelQuote(int l) { if (mLevelQuote != l) { mLevelQuote = l; update(MimeTreeParser::Force); } } void ViewerPrivate::slotHandleAttachment(int choice) { if (!mCurrentContent) { return; } switch (choice) { case Viewer::Delete: deleteAttachment(mCurrentContent); break; case Viewer::Properties: attachmentProperties(mCurrentContent); break; case Viewer::Save: { const bool isEncapsulatedMessage = mCurrentContent->parent() && mCurrentContent->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { KMime::Message::Ptr message(new KMime::Message); message->setContent(mCurrentContent->parent()->bodyAsMessage()->encodedContent()); message->parse(); Akonadi::Item item; item.setPayload(message); Akonadi::MessageFlags::copyMessageFlags(*message, item); item.setMimeType(KMime::Message::mimeType()); QUrl url; if (MessageViewer::Util::saveMessageInMboxAndGetUrl(url, Akonadi::Item::List() << item, mMainWindow)) { showOpenAttachmentFolderWidget(QList() << url); } } else { QList urlList; if (Util::saveContents(mMainWindow, KMime::Content::List() << mCurrentContent, urlList)) { showOpenAttachmentFolderWidget(urlList); } } break; } case Viewer::OpenWith: attachmentOpenWith(mCurrentContent); break; case Viewer::Open: attachmentOpen(mCurrentContent); break; case Viewer::View: attachmentView(mCurrentContent); break; case Viewer::Copy: attachmentCopy(KMime::Content::List() << mCurrentContent); break; case Viewer::ScrollTo: scrollToAttachment(mCurrentContent); break; case Viewer::ReplyMessageToAuthor: replyMessageToAuthor(mCurrentContent); break; case Viewer::ReplyMessageToAll: replyMessageToAll(mCurrentContent); break; } } void ViewerPrivate::replyMessageToAuthor(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), false); } } } void ViewerPrivate::replyMessageToAll(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), true); } } } void ViewerPrivate::slotSpeakText() { const QString text = mViewer->selectedText(); if (!text.isEmpty()) { mTextToSpeechWidget->say(text); } } QUrl ViewerPrivate::imageUrl() const { QUrl url; if (mImageUrl.scheme() == QLatin1String("cid")) { url = QUrl(MessageViewer::WebEngineEmbedPart::self()->contentUrl(mImageUrl.path())); } else { url = mImageUrl; } return url; } void ViewerPrivate::slotCopyImageLocation() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setText(imageUrl().url()); #endif } void ViewerPrivate::slotCopySelectedText() { #ifndef QT_NO_CLIPBOARD QString selection = mViewer->selectedText(); selection.replace(QChar::Nbsp, QLatin1Char(' ')); QApplication::clipboard()->setText(selection); #endif } void ViewerPrivate::viewerSelectionChanged() { mActionCollection->action(QStringLiteral("kmail_copy"))->setEnabled( !mViewer->selectedText().isEmpty()); } void ViewerPrivate::selectAll() { mViewer->selectAll(); } void ViewerPrivate::slotUrlCopy() { #ifndef QT_NO_CLIPBOARD QClipboard *clip = QApplication::clipboard(); if (mClickedUrl.scheme() == QLatin1String("mailto")) { // put the url into the mouse selection and the clipboard const QString address = KEmailAddress::decodeMailtoUrl(mClickedUrl); clip->setText(address, QClipboard::Clipboard); clip->setText(address, QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard.")); } else { // put the url into the mouse selection and the clipboard const QString clickedUrl = mClickedUrl.url(); clip->setText(clickedUrl, QClipboard::Clipboard); clip->setText(clickedUrl, QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("URL copied to clipboard.")); } #endif } void ViewerPrivate::slotSaveMessage() { if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } if (!Util::saveMessageInMbox(Akonadi::Item::List() << mMessageItem, mMainWindow)) { qCWarning(MESSAGEVIEWER_LOG) << "Impossible to save as mbox"; } } void ViewerPrivate::saveRelativePosition() { mViewer->saveRelativePosition(); } //TODO(Andras) inline them bool ViewerPrivate::htmlMail() const { if (mDisplayFormatMessageOverwrite == Viewer::UseGlobalSetting) { return mHtmlMailGlobalSetting; } else { return mDisplayFormatMessageOverwrite == Viewer::Html; } } bool ViewerPrivate::htmlLoadExternal() const { if (!mNodeHelper || !mMessage) { return mHtmlLoadExtOverride; } // when displaying an encrypted message, only load external resources on explicit request if (mNodeHelper->overallEncryptionState(mMessage.data()) != MimeTreeParser::KMMsgNotEncrypted) { return mHtmlLoadExtOverride; } return (mHtmlLoadExternalDefaultSetting && !mHtmlLoadExtOverride) || (!mHtmlLoadExternalDefaultSetting && mHtmlLoadExtOverride); } void ViewerPrivate::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format) { mDisplayFormatMessageOverwrite = format; // keep toggle display mode action state in sync. if (mToggleDisplayModeAction) { mToggleDisplayModeAction->setChecked(htmlMail()); } } bool ViewerPrivate::htmlMailGlobalSetting() const { return mHtmlMailGlobalSetting; } Viewer::DisplayFormatMessage ViewerPrivate::displayFormatMessageOverwrite() const { return mDisplayFormatMessageOverwrite; } void ViewerPrivate::setHtmlLoadExtDefault(bool loadExtDefault) { mHtmlLoadExternalDefaultSetting = loadExtDefault; } void ViewerPrivate::setHtmlLoadExtOverride(bool loadExtOverride) { mHtmlLoadExtOverride = loadExtOverride; } bool ViewerPrivate::htmlLoadExtOverride() const { return mHtmlLoadExtOverride; } void ViewerPrivate::setDecryptMessageOverwrite(bool overwrite) { mDecrytMessageOverwrite = overwrite; } bool ViewerPrivate::showSignatureDetails() const { return mShowSignatureDetails; } void ViewerPrivate::setShowSignatureDetails(bool showDetails) { mShowSignatureDetails = showDetails; } void ViewerPrivate::setShowEncryptionDetails(bool showEncDetails) { mShowEncryptionDetails = showEncDetails; } bool ViewerPrivate::showEncryptionDetails() const { return mShowEncryptionDetails; } void ViewerPrivate::scrollToAttachment(KMime::Content *node) { const QString indexStr = node->index().toString(); // The anchors for this are created in ObjectTreeParser::parseObjectTree() mViewer->scrollToAnchor(QLatin1String("attachmentDiv") + indexStr); // Remove any old color markings which might be there const KMime::Content *root = node->topLevel(); const int totalChildCount = Util::allContents(root).size(); for (int i = 0; i < totalChildCount + 1; ++i) { //Not optimal I need to optimize it. But for the moment it removes yellow mark mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv%1").arg(i + 1)); mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv1.%1").arg(i + 1)); mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv2.%1").arg(i + 1)); } // Don't mark hidden nodes, that would just produce a strange yellow line if (mNodeHelper->isNodeDisplayedHidden(node)) { return; } // Now, color the div of the attachment in yellow, so that the user sees what happened. // We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser, // find and modify that now. mViewer->markAttachment(QLatin1String("attachmentDiv") + indexStr, QStringLiteral("border:2px solid %1").arg(cssHelper()->pgpWarnColor(). name())); } void ViewerPrivate::setUseFixedFont(bool useFixedFont) { mUseFixedFont = useFixedFont; if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } } void ViewerPrivate::itemFetchResult(KJob *job) { if (job->error()) { displaySplashPage(i18n("Message loading failed: %1.", job->errorText())); } else { Akonadi::ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { displaySplashPage(i18n("Message not found.")); } else { setMessageItem(fetch->items().constFirst()); } } } void ViewerPrivate::slotItemChanged(const Akonadi::Item &item, const QSet &parts) { if (item.id() != messageItem().id()) { qCDebug(MESSAGEVIEWER_LOG) << "Update for an already forgotten item. Weird."; return; } if (parts.contains("PLD:RFC822")) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &) { // clear the view after the current item has been moved somewhere else (e.g. to trash) if (item.id() == messageItem().id()) { slotClear(); } } void ViewerPrivate::slotClear() { q->clear(MimeTreeParser::Force); Q_EMIT itemRemoved(); } void ViewerPrivate::slotMessageRendered() { if (!mMessageItem.isValid()) { return; } /** * This slot might be called multiple times for the same message if * some asynchronous mementos are involved in rendering. Therefor we * have to make sure we execute the MessageLoadedHandlers only once. */ if (mMessageItem.id() == mPreviouslyViewedItem) { return; } mPreviouslyViewedItem = mMessageItem.id(); for (AbstractMessageLoadedHandler *handler : qAsConst(mMessageLoadedHandlers)) { handler->setItem(mMessageItem); } } void ViewerPrivate::setZoomFactor(qreal zoomFactor) { mZoomActionMenu->setWebViewerZoomFactor(zoomFactor); } void ViewerPrivate::goOnline() { Q_EMIT makeResourceOnline(Viewer::AllResources); } void ViewerPrivate::goResourceOnline() { Q_EMIT makeResourceOnline(Viewer::SelectedResource); } void ViewerPrivate::slotSaveMessageDisplayFormat() { if (mMessageItem.isValid()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageFormat(displayFormatMessageOverwrite()); job->setMessageItem(mMessageItem); job->setRemoteContent(htmlLoadExtOverride()); job->start(); } } void ViewerPrivate::slotResetMessageDisplayFormat() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageItem(mMessageItem); job->setResetFormat(true); job->start(); } } } void ViewerPrivate::slotMessageMayBeAScam() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { const MessageViewer::ScamAttribute *const attr = mMessageItem.attribute(); if (attr && !attr->isAScam()) { return; } } if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); const QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } } } mScamDetectionWarning->slotShowWarning(); } void ViewerPrivate::slotMessageIsNotAScam() { if (mMessageItem.isValid()) { MessageViewer::ScamAttribute *attr = mMessageItem.attribute( Akonadi::Item::AddIfMissing); attr->setIsAScam(false); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession); modify->setIgnorePayload(true); modify->disableRevisionCheck(); connect(modify, &KJob::result, this, &ViewerPrivate::slotModifyItemDone); } } void ViewerPrivate::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(MESSAGEVIEWER_LOG) << " Error trying to change attribute:" << job->errorText(); } } void ViewerPrivate::saveMainFrameScreenshotInFile(const QString &filename) { mViewer->saveMainFrameScreenshotInFile(filename); } void ViewerPrivate::slotAddToWhiteList() { if (mMessageItem.isValid()) { if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } lst << email; MessageViewer::MessageViewerSettings::self()->setScamDetectionWhiteList(lst); MessageViewer::MessageViewerSettings::self()->save(); } } } void ViewerPrivate::slotMailTrackingFound(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &blacklist) { mMailTrackingWarning->addTracker(blacklist); } void ViewerPrivate::slotFormSubmittedForbidden() { mSubmittedFormWarning->showWarning(); } void ViewerPrivate::addHelpTextAction(QAction *act, const QString &text) { act->setStatusTip(text); act->setToolTip(text); act->setWhatsThis(text); } void ViewerPrivate::slotRefreshMessage(const Akonadi::Item &item) { if (item.id() == mMessageItem.id()) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotServiceUrlSelected( PimCommon::ShareServiceUrlManager::ServiceType serviceType) { const QUrl url = mShareServiceManager->generateServiceUrl(mClickedUrl.toString(), QString(), serviceType); mShareServiceManager->openUrl(url); } QList ViewerPrivate::interceptorUrlActions( const WebEngineViewer::WebHitTestResult &result) const { return mViewer->interceptorUrlActions(result); } void ViewerPrivate::setPrintElementBackground(bool printElementBackground) { mViewer->setPrintElementBackground(printElementBackground); } void ViewerPrivate::slotToggleEmoticons() { mForceEmoticons = !mForceEmoticons; //Save value MessageViewer::MessageViewerSettings::self()->setShowEmoticons(mForceEmoticons); headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons); update(MimeTreeParser::Force); } void ViewerPrivate::slotZoomChanged(qreal zoom) { mViewer->slotZoomChanged(zoom); const qreal zoomFactor = zoom * 100; MessageViewer::MessageViewerSettings::self()->setZoomFactor(zoomFactor); Q_EMIT zoomChanged(zoomFactor); } diff --git a/mimetreeparser/autotests/setupenv.cpp b/mimetreeparser/autotests/setupenv.cpp index 4836fe62..5f82b868 100644 --- a/mimetreeparser/autotests/setupenv.cpp +++ b/mimetreeparser/autotests/setupenv.cpp @@ -1,33 +1,33 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 "setupenv.h" #include #include #include void MimeTreeParser::Test::setupEnv() { qputenv("LC_ALL", "C"); - qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1("/.qttest")).constData()); + qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.qttest")).constData()); QStandardPaths::setTestModeEnabled(true); } diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp index 668edd77..e87f673d 100644 --- a/mimetreeparser/src/nodehelper.cpp +++ b/mimetreeparser/src/nodehelper.cpp @@ -1,1034 +1,1034 @@ /* 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 "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()); } 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 = QString::fromLatin1("e%1").arg(i); + indexStr = QStringLiteral("e%1").arg(i); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { - indexStr = QString::fromLatin1("%1:%2").arg(parentIndex, indexStr); + 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/templateparser/autotests/templateparserjobtest.cpp b/templateparser/autotests/templateparserjobtest.cpp index 770b22f2..573685a9 100644 --- a/templateparser/autotests/templateparserjobtest.cpp +++ b/templateparser/autotests/templateparserjobtest.cpp @@ -1,346 +1,346 @@ /* Copyright (C) 2017-2019 Laurent Montel 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 "templateparserjobtest.h" #define private public #include "templateparserjob_p.h" #include "templateparserjob.h" #undef protected #include #include #include #include #include #include #include #include using namespace MimeTreeParser; TemplateParserJobTest::TemplateParserJobTest(QObject *parent) : QObject(parent) { QStandardPaths::setTestModeEnabled(true); } TemplateParserJobTest::~TemplateParserJobTest() { // Workaround QTestLib not flushing deleteLater()s on exit, which leads to WebEngine asserts (view not deleted) QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } void TemplateParserJobTest::test_convertedHtml_data() { QTest::addColumn("mailFileName"); QTest::addColumn("referenceFileName"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); - const auto l = dir.entryList(QStringList(QLatin1String("plain*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); + const auto l = dir.entryList(QStringList(QStringLiteral("plain*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); foreach (const QString &file, l) { QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << QString(dir.path() + QLatin1Char('/') + file + QLatin1String(".html")); } } void TemplateParserJobTest::test_convertedHtml() { QFETCH(QString, mailFileName); QFETCH(QString, referenceFileName); // load input mail QFile mailFile(mailFileName); QVERIFY(mailFile.open(QIODevice::ReadOnly)); const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll()); QVERIFY(!mailData.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mailData); msg->parse(); // load expected result QFile referenceFile(referenceFileName); QVERIFY(referenceFile.open(QIODevice::ReadOnly)); const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll()); const QString referenceData = QString::fromLatin1(referenceRawData); QVERIFY(!referenceData.isEmpty()); QCOMPARE(msg->subject()->as7BitString(false).constData(), "Plain Message Test"); QCOMPARE(msg->contents().size(), 0); TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::NewMessage); KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager; parser->setIdentityManager(identMan); parser->d->mOtp->parseObjectTree(msg.data()); QVERIFY(parser->d->mOtp->htmlContent().isEmpty()); QVERIFY(!parser->d->mOtp->plainTextContent().isEmpty()); QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone); parser->processWithTemplate(QString()); QVERIFY(spy.wait()); const QString convertedHtmlContent = parser->htmlMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed); QVERIFY(!convertedHtmlContent.isEmpty()); QCOMPARE(convertedHtmlContent, referenceData); } void TemplateParserJobTest::test_replyPlain_data() { QTest::addColumn("mailFileName"); QTest::addColumn("referenceFileName"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks); foreach (const QString &file, l) { const QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".plain.reply"); if (!QFile::exists(expectedFile)) { continue; } QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile; } } void TemplateParserJobTest::test_replyPlain() { QFETCH(QString, mailFileName); QFETCH(QString, referenceFileName); // load input mail QFile mailFile(mailFileName); QVERIFY(mailFile.open(QIODevice::ReadOnly)); const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll()); QVERIFY(!mailData.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mailData); msg->parse(); // load expected result QFile referenceFile(referenceFileName); QVERIFY(referenceFile.open(QIODevice::ReadOnly)); const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll()); const QString referenceData = QString::fromLatin1(referenceRawData); // QVERIFY(!referenceData.isEmpty()); // QCOMPARE(msg->subject()->as7BitString(false).constData(), "Plain Message Test"); // QCOMPARE(msg->contents().size(), 0); TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply); //KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager; //parser->setIdentityManager(identMan); parser->d->mOtp->parseObjectTree(msg.data()); parser->d->mOrigMsg = msg; //QVERIFY(parser->mOtp->htmlContent().isEmpty()); //QVERIFY(!parser->mOtp->plainTextContent().isEmpty()); // QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone); // parser->processWithTemplate(QString()); // QVERIFY(spy.wait()); // QBENCHMARK { // const QString convertedHtmlContent = parser->plainMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed); // QCOMPARE(convertedHtmlContent, referenceData); // } } void TemplateParserJobTest::test_processWithTemplatesForBody_data() { QTest::addColumn("command"); QTest::addColumn("text"); QTest::addColumn("expected"); QTest::addColumn("selection"); QTest::newRow("%OTEXT") << "%OTEXT" << "Original text.\nLine two." << "Original text.\nLine two." << ""; QTest::newRow("%OTEXT") << "%OTEXT" << "-----BEGIN PGP MESSAGE-----\nVersion: GnuPG v1.4.12 (GNU/Linux)\n" "\n" "hQEMAwzOQ1qnzNo7AQgA1345CrnOBTGf2eo4ABR6wkOdasI9SELRBKA1fNkFcq+Z\n" "Qg0gWB5RLapU+VFRc5hK1zPOZ1dY6j3+uPHO4RhjfUgfiZ8T7oaWav15yP+07u21\n" "EI9W9sk+eQU9GZSOayURucmZa/mbBz9hrsmePpORxD+C3uNTYa6ePTFlQP6wEZOI\n" "7E53DrtJnF0EzIsCBIVep6CyuYfuSSwQ5gMgyPzfBqiGHNw96w2i/eayErc6lquL\n" "JPFhIcMMq8w9Yo9+vXCAbkns6dtBAzlnAzuV86VFUZ/MnHTlCNk2yHyGLP6BS6hG\n" "kFEUmgdHrGRizdz1sjo1tSmOLu+Gyjlv1Ir/Sqr8etJQAeTq3heKslAfhtotAMMt\n" "R3tk228Su13Q3CAP/rktAyuGMDFtH8klW09zFdsZBDu8svE6d9e2nZ541NGspFVI\n" "6XTZHUMMdlgnTBcu3aPc0ow=\n" "=0xtc\n" "-----END PGP MESSAGE-----" << "Crypted line.\nCrypted line two." << ""; QTest::newRow("%QUOTE") << "%QUOTE" << "Quoted text.\nLine two." << "> Quoted text.\n> Line two." << ""; } void TemplateParserJobTest::test_processWithTemplatesForBody() { QFETCH(QString, command); QFETCH(QString, text); QFETCH(QString, expected); QFETCH(QString, selection); KMime::Message::Ptr msg(new KMime::Message()); msg->setBody(text.toLocal8Bit()); msg->parse(); TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply); parser->setSelection(selection); KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager; parser->setIdentityManager(identMan); parser->setAllowDecryption(true); parser->d->mOrigMsg = msg; QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone); parser->processWithTemplate(command); QVERIFY(spy.wait()); identMan->deleteLater(); QCOMPARE(QString::fromLatin1(msg->encodedBody()), expected); QCOMPARE(spy.count(), 1); } void TemplateParserJobTest::test_processWithTemplatesForContent_data() { QTest::addColumn("command"); QTest::addColumn("mailFileName"); QTest::addColumn("expectedBody"); QTest::addColumn("hasDictionary"); qputenv("TZ", "Europe/Paris"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); const QString file = QStringLiteral("plain-message.mbox"); const QString fileName = QString(dir.path() + QLatin1Char('/') + file); QTest::newRow("%OTIME") << "%OTIME" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::ShortFormat) << false; QTest::newRow("%OTIMELONG") << "%OTIMELONG" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) << false; QTest::newRow("%OTIMELONGEN") << "%OTIMELONGEN" << fileName << QLocale(QLocale::C).toString(QTime(8, 0, 27), QLocale::LongFormat) << false; QTest::newRow("%ODATE") << "%ODATE" << fileName << QLocale().toString(QDate(2011, 8, 7), QLocale::LongFormat) << false; QTest::newRow("%ODATESHORT") << "%ODATESHORT" << fileName << QLocale().toString(QDate(2011, 8, 7), QLocale::ShortFormat) << false; QTest::newRow("%ODATEEN") << "%ODATEEN" << fileName << QLocale::c().toString(QDate(2011, 8, 7), QLocale::LongFormat) << false; QTest::newRow("%OFULLSUBJ") << "%OFULLSUBJ" << fileName << "Plain Message Test" << false; QTest::newRow("%OFULLSUBJECT") << "%OFULLSUBJECT" << fileName << "Plain Message Test" << false; QTest::newRow("%OFROMFNAME") << "%OFROMFNAME" << fileName << "Sudhendu" << false; QTest::newRow("%OFROMLNAME") << "%OFROMLNAME" << fileName << "Kumar" << false; QTest::newRow("%OFROMNAME") << "%OFROMNAME" << fileName << "Sudhendu Kumar" << false; QTest::newRow("%OFROMADDR") << "%OFROMADDR" << fileName << "Sudhendu Kumar " << false; QTest::newRow("%OTOADDR") << "%OTOADDR" << fileName << "kde " << false; QTest::newRow("%OTOFNAME") << "%OTOFNAME" << fileName << "kde" << false; QTest::newRow("%OTONAME") << "%OTONAME" << fileName << "kde" << false; QTest::newRow("%OTOLNAME") << "%OTOLNAME" << fileName << "" << false; QTest::newRow("%OTOLIST") << "%OTOLIST" << fileName << "kde " << false; QTest::newRow("%ODOW") << "%ODOW" << fileName << QLocale().dayName(7, QLocale::LongFormat) << false; QTest::newRow("%BLANK") << "%BLANK" << fileName << "" << false; QTest::newRow("%NOP") << "%NOP" << fileName << "" << false; QTest::newRow("%DICTIONARYLANGUAGE=\"en\"") << "%DICTIONARYLANGUAGE=\"en\"" << fileName << "" << true; QTest::newRow("%DICTIONARYLANGUAGE=\"\"") << "%DICTIONARYLANGUAGE=\"\"" << fileName << "" << false; QTest::newRow("%OTIMELONG %OFULLSUBJECT") << "%OTIMELONG %OFULLSUBJECT" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) + QStringLiteral(" Plain Message Test") << false; QTest::newRow("%OTIMELONG\n%OFULLSUBJECT") << "%OTIMELONG\n%OFULLSUBJECT" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) + QStringLiteral("\nPlain Message Test") << false; QTest::newRow("%REM=\"sdfsfsdsdfsdf\"") << "%REM=\"sdfsfsdsdfsdf\"" << fileName << "" << false; QTest::newRow("%CLEAR") << "%CLEAR" << fileName << "" << false; QTest::newRow("FOO foo") << "FOO foo" << fileName << "FOO foo" << false; const QString insertFileName = QString(dir.path() + QLatin1Char('/') + QLatin1String("insert-file.txt")); QString insertFileNameCommand = QStringLiteral("%INSERT=\"%1\"").arg(insertFileName); QTest::newRow("%INSERT") << insertFileNameCommand << fileName << "test insert file!\n" << false; insertFileNameCommand = QStringLiteral("%PUT=\"%1\"").arg(insertFileName); QTest::newRow("%PUT") << insertFileNameCommand << fileName << "test insert file!\n" << false; QTest::newRow("%MSGID") << "%MSGID" << fileName << "<20150@foo.kde.org>" << false; QTest::newRow("%SYSTEM") << "%SYSTEM=\"echo foo\"" << fileName << "foo\n" << false; QTest::newRow("%DEBUG") << "%DEBUG" << fileName << "" << false; QTest::newRow("%DEBUGOFF") << "%DEBUGOFF" << fileName << "" << false; QTest::newRow("%HEADER=\"Reply-To\"") << "%HEADER=\"Reply-To\"" << fileName << "bla@yoohoo.org" << false; //Header doesn't exist => don't add value QTest::newRow("%OHEADER=\"SSS\"") << "%HEADER=\"SSS\"" << fileName << "" << false; QTest::newRow("%OHEADER=\"To\"") << "%OHEADER=\"To\"" << fileName << "kde " << false; //Unknown command QTest::newRow("unknown command") << "%GGGGG" << fileName << "%GGGGG" << false; //Test bug 308444 const QString file2 = QStringLiteral("plain-message-timezone.mbox"); const QString fileName2 = QString(dir.path() + QLatin1Char('/') + file2); QTest::newRow("bug308444-%OTIMELONG") << "%OTIMELONG" << fileName2 << QLocale::system().toString(QTime(20, 31, 25), QLocale::LongFormat) << false; } void TemplateParserJobTest::test_processWithTemplatesForContent() { QFETCH(QString, command); QFETCH(QString, mailFileName); QFETCH(QString, expectedBody); QFETCH(bool, hasDictionary); QFile mailFile(mailFileName); QVERIFY(mailFile.open(QIODevice::ReadOnly)); const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll()); QVERIFY(!mailData.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mailData); msg->parse(); TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply); KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager; parser->setIdentityManager(identMan); parser->setAllowDecryption(false); parser->d->mOrigMsg = msg; QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone); parser->processWithTemplate(command); QVERIFY(spy.wait()); QCOMPARE(msg->hasHeader("X-KMail-Dictionary"), hasDictionary); identMan->deleteLater(); QCOMPARE(QString::fromUtf8(msg->encodedBody()), expectedBody); QCOMPARE(spy.count(), 1); } void TemplateParserJobTest::test_processWithTemplatesForContentOtherTimeZone_data() { QTest::addColumn("command"); QTest::addColumn("mailFileName"); QTest::addColumn("expectedBody"); QTest::addColumn("hasDictionary"); qputenv("TZ", "America/New_York"); QDir dir(QStringLiteral(MAIL_DATA_DIR)); //Test bug 308444 const QString file2 = QStringLiteral("plain-message-timezone.mbox"); const QString fileName2 = QString(dir.path() + QLatin1Char('/') + file2); QTest::newRow("bug308444-%OTIMELONG") << "%OTIMELONG" << fileName2 << QLocale::system().toString(QTime(14, 31, 25), QLocale::LongFormat) << false; } void TemplateParserJobTest::test_processWithTemplatesForContentOtherTimeZone() { QFETCH(QString, command); QFETCH(QString, mailFileName); QFETCH(QString, expectedBody); QFETCH(bool, hasDictionary); QFile mailFile(mailFileName); QVERIFY(mailFile.open(QIODevice::ReadOnly)); const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll()); QVERIFY(!mailData.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mailData); msg->parse(); TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply); KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager; parser->setIdentityManager(identMan); parser->setAllowDecryption(false); parser->d->mOrigMsg = msg; QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone); parser->processWithTemplate(command); QVERIFY(spy.wait()); QCOMPARE(msg->hasHeader("X-KMail-Dictionary"), hasDictionary); identMan->deleteLater(); QCOMPARE(QString::fromUtf8(msg->encodedBody()), expectedBody); QCOMPARE(spy.count(), 1); } QTEST_MAIN(TemplateParserJobTest) diff --git a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp index fb669702..2d7806da 100644 --- a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp +++ b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp @@ -1,45 +1,45 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "webengineexporthtmlpagejobtest.h" #include "../webengineexporthtmlpagejob.h" #include #include WebEngineExportHtmlPageJobTest::WebEngineExportHtmlPageJobTest(QObject *parent) : QObject(parent) { } WebEngineExportHtmlPageJobTest::~WebEngineExportHtmlPageJobTest() { } void WebEngineExportHtmlPageJobTest::shouldHaveDefaultValue() { WebEngineViewer::WebEngineExportHtmlPageJob job; - QSignalSpy spyFailed(&job, SIGNAL(failed())); - QSignalSpy spySuccess(&job, SIGNAL(success(QString))); + QSignalSpy spyFailed(&job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed); + QSignalSpy spySuccess(&job, &WebEngineViewer::WebEngineExportHtmlPageJob::success); QVERIFY(!job.engineView()); job.start(); QCOMPARE(spyFailed.count(), 1); QCOMPARE(spySuccess.count(), 0); } QTEST_MAIN(WebEngineExportHtmlPageJobTest) diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp index a9e11b50..54b06ee4 100644 --- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp +++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp @@ -1,101 +1,101 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "checkphishingurljobtest.h" #include "../checkphishingurljob.h" #include "../checkphishingurlutil.h" #include #include CheckPhishingUrlJobTest::CheckPhishingUrlJobTest(QObject *parent) : QObject(parent) { } CheckPhishingUrlJobTest::~CheckPhishingUrlJobTest() { } void CheckPhishingUrlJobTest::shouldNotBeAbleToStartWithEmptyUrl() { WebEngineViewer::CheckPhishingUrlJob job; QVERIFY(!job.canStart()); } void CheckPhishingUrlJobTest::shouldCreateRequest_data() { QTest::addColumn("url"); QTest::addColumn("request"); QTest::addColumn("canStart"); QTest::newRow("no url") << QUrl() << QString() << false; QTest::newRow("value") << QUrl(QStringLiteral("http://www.kde.org")) << QStringLiteral( "{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"threatInfo\":{\"platformTypes\":[\"WINDOWS\"],\"threatEntries\":[{\"url\":\"http://www.kde.org\"}],\"threatEntryTypes\":[\"URL\"],\"threatTypes\":[\"MALWARE\"]}}") .arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps()) << true; } void CheckPhishingUrlJobTest::shouldCreateRequest() { QFETCH(QUrl, url); QFETCH(QString, request); QFETCH(bool, canStart); WebEngineViewer::CheckPhishingUrlJob job; job.setUrl(url); QCOMPARE(job.canStart(), canStart); if (canStart) { QCOMPARE(job.jsonRequest(), request.toLatin1()); } } void CheckPhishingUrlJobTest::shouldParseResult_data() { QTest::addColumn("input"); QTest::addColumn("checkedUrl"); QTest::addColumn("verifyCacheAfterThisTime"); QTest::addColumn("urlStatus"); uint val = 0; QTest::newRow("empty") << QByteArray() << QUrl() << val << WebEngineViewer::CheckPhishingUrlUtil::Unknown; QTest::newRow("empty1") << QByteArray() << QUrl(QStringLiteral("http://www.kde.org")) << val << WebEngineViewer::CheckPhishingUrlUtil::Unknown; QTest::newRow("urlOk") << QByteArrayLiteral("{}") << QUrl(QStringLiteral("http://www.kde.org")) << val << WebEngineViewer::CheckPhishingUrlUtil::Ok; val = WebEngineViewer::CheckPhishingUrlUtil::refreshingCacheAfterThisTime(WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(QStringLiteral("300s"))); QTest::newRow("malware") << QByteArrayLiteral( "{\"matches\":[{\"threatType\":\"MALWARE\",\"platformType\":\"WINDOWS\",\"threat\":{\"url\":\"http://malware.testing.google.test/testing/malware/\"},\"cacheDuration\":\"300s\",\"threatEntryType\":\"URL\"}]}") << QUrl(QStringLiteral("http://malware.testing.google.test/testing/malware/")) << val << WebEngineViewer::CheckPhishingUrlUtil::MalWare; } void CheckPhishingUrlJobTest::shouldParseResult() { QFETCH(QByteArray, input); QFETCH(QUrl, checkedUrl); QFETCH(uint, verifyCacheAfterThisTime); QFETCH(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus, urlStatus); WebEngineViewer::CheckPhishingUrlJob job; job.setUrl(checkedUrl); - QSignalSpy spy1(&job, SIGNAL(result(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus,QUrl,uint))); + QSignalSpy spy1(&job, &WebEngineViewer::CheckPhishingUrlJob::result); job.parse(input); QCOMPARE(spy1.count(), 1); QCOMPARE(spy1.at(0).at(0).value(), urlStatus); QCOMPARE(spy1.at(0).at(1).toUrl(), checkedUrl); QCOMPARE(spy1.at(0).at(2).value(), verifyCacheAfterThisTime); } QTEST_MAIN(CheckPhishingUrlJobTest) diff --git a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp index 044e3391..15f525bb 100644 --- a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp +++ b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp @@ -1,441 +1,441 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "createdatabasefilejobtest.h" #include "../createdatabasefilejob.h" #include "../createphishingurldatabasejob.h" #include "../localdatabasefile.h" #include #include #include #include Q_DECLARE_METATYPE(QVector) QByteArray readJsonFile(const QString &jsonFile) { QFile file(QLatin1String(CHECKPHISHINGURL_DATA_DIR) + QLatin1Char('/') + jsonFile); file.open(QIODevice::ReadOnly); Q_ASSERT(file.isOpen()); const QByteArray data = file.readAll(); Q_ASSERT(!data.isEmpty()); return data; } CreateDatabaseFileJobTest::CreateDatabaseFileJobTest(QObject *parent) : QObject(parent) { QStandardPaths::setTestModeEnabled(true); QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl")); } CreateDatabaseFileJobTest::~CreateDatabaseFileJobTest() { } void CreateDatabaseFileJobTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void CreateDatabaseFileJobTest::shouldHaveDefaultValue() { WebEngineViewer::CreateDatabaseFileJob job; QVERIFY(!job.canStart()); } void CreateDatabaseFileJobTest::shouldCreateFile_data() { QTest::addColumn("filename"); QTest::addColumn("numberOfElement"); QTest::addColumn("success"); QTest::newRow("correctdatabase") << QStringLiteral("current.json") << static_cast(580600) << true; QTest::newRow("correctdatabase2") << QStringLiteral("newdatabase2.json") << static_cast(579416) << true; QTest::newRow("incorrectdatabase") << QStringLiteral("incorrectdatabase2.json") << static_cast(0) << false; } void CreateDatabaseFileJobTest::shouldCreateFile() { QFETCH(QString, filename); QFETCH(quint64, numberOfElement); QFETCH(bool, success); const QByteArray ba = readJsonFile(filename); WebEngineViewer::CreatePhishingUrlDataBaseJob job; - QSignalSpy spy1(&job, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult))); + QSignalSpy spy1(&job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished); job.parseResult(ba); QCOMPARE(spy1.count(), 1); const WebEngineViewer::UpdateDataBaseInfo info = spy1.at(0).at(0).value(); WebEngineViewer::CreateDatabaseFileJob databasejob; const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/test.db"); qDebug() << " new filename " << createDataBaseName; databasejob.setFileName(createDataBaseName); databasejob.setUpdateDataBaseInfo(info); - QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished); databasejob.start(); QCOMPARE(spy2.count(), 1); bool successCreateDataBase = spy2.at(0).at(0).toBool(); QCOMPARE(successCreateDataBase, success); WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName); QVERIFY(newFile.isValid()); QCOMPARE(newFile.getUint16(0), static_cast(1)); QCOMPARE(newFile.getUint16(2), static_cast(0)); if (success) { QCOMPARE(newFile.getUint64(4), numberOfElement); } } void CreateDatabaseFileJobTest::shouldRemoveElementInDataBase_data() { QTest::addColumn >("listElementToRemove"); QTest::addColumn >("listElementToAdd"); QTest::addColumn("newssha"); QTest::addColumn("success"); QVector lstAdditions; QList r = { 2, 3, 4}; QTest::newRow("correctdatabase") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true; r = {3, 2, 4}; QTest::newRow("correctdatabaseotherorder") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true; r = {4, 2, 3}; QTest::newRow("correctdatabaseotherorder2") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true; // >>> import hashlib // >>> m = hashlib.sha256() // >>> m.update("111154321abcdabcdebbbbbcdef") // >>> m.digest() // '\x81\xdd9\xe3\xae\x94s\xfd\x16o\\\xcea \xb7\xbc\x1b+R\nN\x05o\xfe\xeeWY\x7f\x8a\xcb\xbeN' // >>> import base64 // >>> encoded = base64.b64encode(m.digest()) // >>> encoded // 'gd05466Uc/0Wb1zOYSC3vBsrUgpOBW/+7ldZf4rLvk4=' // >>> r = {0, 2, 8}; QTest::newRow("correctdatabaseotherorder3") << r << lstAdditions << QByteArrayLiteral("gd05466Uc/0Wb1zOYSC3vBsrUgpOBW/+7ldZf4rLvk4=") << true; r = {0, 2, 8}; WebEngineViewer::Addition c; c.hashString = QByteArray("mnopqrst"); c.prefixSize = 4; c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition b; b.hashString = QByteArray("uvwx"); b.prefixSize = 4; b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; lstAdditions << c << b; // >>> import hashlib // >>> m = hashlib.sha256() // >>> m.update("111154321abcdabcdebbbbbcdefmnopqrstuvwx") // >>> m.digest() // '\n\xae\xe2\xe0!\x8f\xa4\x05N\x89,\xdcJ*\xbe\x85\xa1Q\xc3\x9c\xc8}j\x83*s\xd5L&\xbe\xfbh' // >>> import base64 // >>> encoded = base64.b64encode(m.digest()) // >>> encoded // 'Cq7i4CGPpAVOiSzcSiq+haFRw5zIfWqDKnPVTCa++2g=' //m.update("111154321abcdabcdebbbbbcdefmnopqrstuvwx"); QTest::newRow("correctdatabaseotherorderwithadditions") << r << lstAdditions << QByteArrayLiteral("Cq7i4CGPpAVOiSzcSiq+haFRw5zIfWqDKnPVTCa++2g=") << true; } void CreateDatabaseFileJobTest::shouldRemoveElementInDataBase() { QFETCH(QList, listElementToRemove); QFETCH(QVector, listElementToAdd); QFETCH(QByteArray, newssha); QFETCH(bool, success); // Proof of checksum validity using python: // >>> import hashlib // >>> m = hashlib.sha256() // >>> m.update("----11112222254321abcdabcdebbbbbcdefefgh") // >>> m.digest() // "\xbc\xb3\xedk\xe3x\xd1(\xa9\xedz7]" // "x\x18\xbdn]\xa5\xa8R\xf7\xab\xcf\xc1\xa3\xa3\xc5Z,\xa6o" WebEngineViewer::CreateDatabaseFileJob databasejob; const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/correctBinary.db"); qDebug() << " new filename " << createDataBaseName; databasejob.setFileName(createDataBaseName); WebEngineViewer::UpdateDataBaseInfo info; WebEngineViewer::Addition a; a.hashString = QByteArray("----1111bbbb"); a.prefixSize = 4; a.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition b; b.hashString = QByteArray("abcdefgh"); b.prefixSize = 4; b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition c; c.hashString = QByteArray("54321abcde"); c.prefixSize = 5; c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition d; d.hashString = QByteArray("22222bcdef"); d.prefixSize = 5; d.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; QVector lst; lst << a << b << c << d; info.additionList = lst; info.minimumWaitDuration = QStringLiteral("593.440s"); info.threatType = QStringLiteral("MALWARE"); info.threatEntryType = QStringLiteral("URL"); info.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate; info.platformType = QStringLiteral("WINDOWS"); info.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd"); info.sha256 = QByteArrayLiteral("vLPta+N40Sip7Xo3XXgYvW5dpahS96vPwaOjxVospm8="); databasejob.setUpdateDataBaseInfo(info); - QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished); databasejob.start(); QCOMPARE(spy2.count(), 1); bool successCreateDataBase = spy2.at(0).at(0).toBool(); QVERIFY(successCreateDataBase); WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName); QVERIFY(newFile.isValid()); QCOMPARE(newFile.getUint16(0), static_cast(1)); QCOMPARE(newFile.getUint16(2), static_cast(0)); QCOMPARE(newFile.getUint64(4), static_cast(9)); int index = 4 + sizeof(quint64); QList storageData; storageData << QByteArrayLiteral("----"); storageData << QByteArrayLiteral("1111"); storageData << QByteArrayLiteral("22222"); storageData << QByteArrayLiteral("54321"); storageData << QByteArrayLiteral("abcd"); storageData << QByteArrayLiteral("abcde"); storageData << QByteArrayLiteral("bbbb"); storageData << QByteArrayLiteral("bcdef"); storageData << QByteArrayLiteral("efgh"); for (int i = 0; i < 9; ++i) { quint64 value = newFile.getUint64(index); //qDebug() << "char "<< newFile.getCharStar(value); QCOMPARE(storageData.at(i), QByteArray(newFile.getCharStar(value))); index += sizeof(quint64); } const QVector lstInfo = newFile.extractAllInfo(); QCOMPARE(lstInfo.count(), 9); for (int i = 0; i < 9; i++) { QCOMPARE(lstInfo.at(i).hashString, storageData.at(i)); QCOMPARE(lstInfo.at(i).prefixSize, lstInfo.at(i).hashString.size()); } //Before //storageData << QByteArrayLiteral("----"); //storageData << QByteArrayLiteral("1111"); //storageData << QByteArrayLiteral("22222"); //storageData << QByteArrayLiteral("54321"); //storageData << QByteArrayLiteral("abcd"); //storageData << QByteArrayLiteral("abcde"); //storageData << QByteArrayLiteral("bbbb"); //storageData << QByteArrayLiteral("bcdef"); //storageData << QByteArrayLiteral("efgh"); //TODO remove items. WebEngineViewer::UpdateDataBaseInfo updateinfo; // we will remove QByteArrayLiteral("22222"); QByteArrayLiteral("54321"); QByteArrayLiteral("abcd"); WebEngineViewer::Removal r; r.indexes = listElementToRemove; r.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; // Proof of checksum validity using python: // >>> import hashlib // >>> m = hashlib.sha256() // >>> m.update("----1111abcdebbbbbcdefefgh") // >>> m.digest() // '\xc99\xf2\x8c\x08\x08\x15\xe4\xba\n\xff\x9b\xe0\x82G\x9e\x06\x1bv\xfay\xbb=[\xc7\xd5xz^B\xc9\xe1' // >>> import base64 // >>> encoded = base64.b64encode(m.digest()) // >>> encoded // 'yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=' QVector lstRemovals; lstRemovals << r; updateinfo.additionList = listElementToAdd; updateinfo.removalList = lstRemovals; updateinfo.minimumWaitDuration = QStringLiteral("593.440s"); updateinfo.threatType = QStringLiteral("MALWARE"); updateinfo.threatEntryType = QStringLiteral("URL"); updateinfo.responseType = WebEngineViewer::UpdateDataBaseInfo::PartialUpdate; updateinfo.platformType = QStringLiteral("WINDOWS"); updateinfo.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd"); updateinfo.sha256 = /*QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=")*/ newssha; WebEngineViewer::CreateDatabaseFileJob updateDatabasejob; qDebug() << " new filename " << createDataBaseName; updateDatabasejob.setFileName(createDataBaseName); updateDatabasejob.setUpdateDataBaseInfo(updateinfo); - QSignalSpy spy3(&updateDatabasejob, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy3(&updateDatabasejob, &WebEngineViewer::CreateDatabaseFileJob::finished); updateDatabasejob.start(); QCOMPARE(spy3.count(), 1); successCreateDataBase = spy3.at(0).at(0).toBool(); QCOMPARE(successCreateDataBase, success); } void CreateDatabaseFileJobTest::shouldCreateCorrectBinaryFile() { WebEngineViewer::CreateDatabaseFileJob databasejob; const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/correctBinary.db"); qDebug() << " new filename " << createDataBaseName; databasejob.setFileName(createDataBaseName); WebEngineViewer::UpdateDataBaseInfo info; WebEngineViewer::Addition a; a.hashString = QByteArray("----1111bbbb"); a.prefixSize = 4; a.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition b; b.hashString = QByteArray("abcdefgh"); b.prefixSize = 4; b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition c; c.hashString = QByteArray("54321abcde"); c.prefixSize = 5; c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; WebEngineViewer::Addition d; d.hashString = QByteArray("22222bcdef"); d.prefixSize = 5; d.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression; QVector lst; lst << a << b << c << d; info.additionList = lst; info.minimumWaitDuration = QStringLiteral("593.440s"); info.threatType = QStringLiteral("MALWARE"); info.threatEntryType = QStringLiteral("URL"); info.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate; info.platformType = QStringLiteral("WINDOWS"); info.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd"); info.sha256 = QByteArrayLiteral("vLPta+N40Sip7Xo3XXgYvW5dpahS96vPwaOjxVospm8="); databasejob.setUpdateDataBaseInfo(info); - QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished); databasejob.start(); QCOMPARE(spy2.count(), 1); bool successCreateDataBase = spy2.at(0).at(0).toBool(); QVERIFY(successCreateDataBase); WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName); QVERIFY(newFile.isValid()); QCOMPARE(newFile.getUint16(0), static_cast(1)); QCOMPARE(newFile.getUint16(2), static_cast(0)); QCOMPARE(newFile.getUint64(4), static_cast(9)); int index = 4 + sizeof(quint64); QList storageData; storageData << QByteArrayLiteral("----"); storageData << QByteArrayLiteral("1111"); storageData << QByteArrayLiteral("22222"); storageData << QByteArrayLiteral("54321"); storageData << QByteArrayLiteral("abcd"); storageData << QByteArrayLiteral("abcde"); storageData << QByteArrayLiteral("bbbb"); storageData << QByteArrayLiteral("bcdef"); storageData << QByteArrayLiteral("efgh"); for (int i = 0; i < 9; ++i) { quint64 value = newFile.getUint64(index); //qDebug() << "char "<< newFile.getCharStar(value); QCOMPARE(storageData.at(i), QByteArray(newFile.getCharStar(value))); index += sizeof(quint64); } const QVector lstInfo = newFile.extractAllInfo(); QCOMPARE(lstInfo.count(), 9); for (int i = 0; i < 9; i++) { QCOMPARE(lstInfo.at(i).hashString, storageData.at(i)); QCOMPARE(lstInfo.at(i).prefixSize, lstInfo.at(i).hashString.size()); } } void CreateDatabaseFileJobTest::shouldUpdateDataBase() { QString firstFilename = QStringLiteral("newdatabase2.json"); const QByteArray ba = readJsonFile(firstFilename); WebEngineViewer::CreatePhishingUrlDataBaseJob job; - QSignalSpy spy1(&job, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult))); + QSignalSpy spy1(&job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished); job.parseResult(ba); QCOMPARE(spy1.count(), 1); const WebEngineViewer::UpdateDataBaseInfo info = spy1.at(0).at(0).value(); WebEngineViewer::CreateDatabaseFileJob databasejob; const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/update.db"); //qDebug() << " new filename " << createDataBaseName; databasejob.setFileName(createDataBaseName); databasejob.setUpdateDataBaseInfo(info); - QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished); databasejob.start(); QCOMPARE(spy2.count(), 1); bool successCreateDataBase = spy2.at(0).at(0).toBool(); QVERIFY(successCreateDataBase); WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName); QVERIFY(newFile.isValid()); QCOMPARE(newFile.getUint16(0), static_cast(1)); QCOMPARE(newFile.getUint16(2), static_cast(0)); QCOMPARE(newFile.getUint64(4), static_cast(579416)); newFile.close(); QString updateFilename = QStringLiteral("partial_download3.json"); const QByteArray baUpdate = readJsonFile(updateFilename); WebEngineViewer::CreatePhishingUrlDataBaseJob jobUpdate; - QSignalSpy spy3(&jobUpdate, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult))); + QSignalSpy spy3(&jobUpdate, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished); jobUpdate.parseResult(baUpdate); QCOMPARE(spy3.count(), 1); const WebEngineViewer::UpdateDataBaseInfo infoUpdate = spy3.at(0).at(0).value(); QCOMPARE(infoUpdate.responseType, WebEngineViewer::UpdateDataBaseInfo::PartialUpdate); WebEngineViewer::CreateDatabaseFileJob databasejob2; databasejob2.setFileName(createDataBaseName); databasejob2.setUpdateDataBaseInfo(infoUpdate); - QSignalSpy spy4(&databasejob2, SIGNAL(finished(bool,QString,QString))); + QSignalSpy spy4(&databasejob2, &WebEngineViewer::CreateDatabaseFileJob::finished); databasejob2.start(); QCOMPARE(spy4.count(), 1); successCreateDataBase = spy4.at(0).at(0).toBool(); QEXPECT_FAIL("successCreateDataBase", "Expected a success but not", Continue); //QVERIFY(successCreateDataBase); } QTEST_MAIN(CreateDatabaseFileJobTest) diff --git a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp index dd2c14bc..73cf2a4d 100644 --- a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp +++ b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp @@ -1,123 +1,123 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "webengineaccesskeyanchorfromhtmltest.h" #include "../webengineaccesskeyutils.h" #include #include #include #include #include template struct InvokeWrapper { R *receiver; void (C::*memberFunction)(Arg); void operator()(Arg result) { (receiver->*memberFunction)(result); } }; template InvokeWrapper invoke(R *receiver, void (C::*memberFunction)(Arg)) { InvokeWrapper wrapper = {receiver, memberFunction}; return wrapper; } TestWebEngineAccessKey::TestWebEngineAccessKey(QWidget *parent) : QWidget(parent) { QHBoxLayout *hbox = new QHBoxLayout(this); mEngineView = new QWebEngineView(this); connect(mEngineView, &QWebEngineView::loadFinished, this, &TestWebEngineAccessKey::loadFinished); hbox->addWidget(mEngineView); } TestWebEngineAccessKey::~TestWebEngineAccessKey() { } void TestWebEngineAccessKey::setHtml(const QString &html) { mEngineView->setHtml(html); } void TestWebEngineAccessKey::handleSearchAccessKey(const QVariant &var) { const QVariantList lst = var.toList(); QVector anchorList; anchorList.reserve(lst.count()); for (const QVariant &anchor : lst) { anchorList << WebEngineViewer::WebEngineAccessKeyAnchor(anchor); } Q_EMIT accessKeySearchFinished(anchorList); } void TestWebEngineAccessKey::loadFinished(bool b) { Q_UNUSED(b); mEngineView->page()->runJavaScript(WebEngineViewer::WebEngineAccessKeyUtils::script(), WebEngineViewer::WebEngineManageScript::scriptWordId(), invoke(this, &TestWebEngineAccessKey::handleSearchAccessKey)); } Q_DECLARE_METATYPE(QVector) WebEngineAccessKeyAnchorFromHtmlTest::WebEngineAccessKeyAnchorFromHtmlTest(QObject *parent) : QObject(parent) { qRegisterMetaType >(); } void WebEngineAccessKeyAnchorFromHtmlTest::shouldNotShowAccessKeyWhenHtmlAsNotAnchor() { TestWebEngineAccessKey w; - QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector))); + QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished); w.setHtml(QStringLiteral("foo")); QVERIFY(accessKeySpy.wait()); QCOMPARE(accessKeySpy.count(), 1); const QVector resultLst = accessKeySpy.at(0).at(0).value >(); QCOMPARE(resultLst.count(), 0); } void WebEngineAccessKeyAnchorFromHtmlTest::shouldReturnOneAnchor() { TestWebEngineAccessKey w; - QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector))); + QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished); w.setHtml(QStringLiteral("foofoo")); QVERIFY(accessKeySpy.wait()); QCOMPARE(accessKeySpy.count(), 1); const QVector resultLst = accessKeySpy.at(0).at(0).value >(); QCOMPARE(resultLst.count(), 1); } void WebEngineAccessKeyAnchorFromHtmlTest::shouldReturnTwoAnchor() { TestWebEngineAccessKey w; - QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector))); + QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished); w.setHtml(QStringLiteral("foofoofoo")); QVERIFY(accessKeySpy.wait()); QCOMPARE(accessKeySpy.count(), 1); const QVector resultLst = accessKeySpy.at(0).at(0).value >(); QCOMPARE(resultLst.count(), 2); } QTEST_MAIN(WebEngineAccessKeyAnchorFromHtmlTest) diff --git a/webengineviewer/src/webenginescript.cpp b/webengineviewer/src/webenginescript.cpp index 62a78e32..dd267ac5 100644 --- a/webengineviewer/src/webenginescript.cpp +++ b/webengineviewer/src/webenginescript.cpp @@ -1,191 +1,191 @@ /* Copyright (C) 2016-2019 Laurent Montel 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 "webenginescript.h" using namespace WebEngineViewer; QString WebEngineScript::findAllImages() { const QString source = QStringLiteral("(function() {" "var out = [];" "var imgs = document.getElementsByTagName('img');" "for (var i = 0; i < imgs.length; ++i) {" " var e = imgs[i];" " out.push({" " src: e.src" " });" "}" "return out;" "})()"); return source; } QString WebEngineScript::findAllScripts() { const QString source = QStringLiteral("(function() {" "var out = [];" "var scripts = document.getElementsByTagName('script');" "for (var i = 0; i < scripts.length; ++i) {" " var e = scripts[i];" " out.push({" " src: e.src" " });" "}" "return out;" "})()"); return source; } QString WebEngineScript::findAllAnchors() { const QString source = QStringLiteral("(function() {" "var out = [];" "var anchors = document.getElementsByTagName('a');" "for (var i = 0; i < anchors.length; ++i) {" " var r = anchors[i].getBoundingClientRect();" " out.push({" " src: anchors[i].href," " title: anchors[i].title," " boudingRect: [r.top, r.left, r.width, r.height]" " });" "}" "return out;" "})()"); return source; } QString WebEngineScript::findAllAnchorsAndForms() { const QString source = QStringLiteral("(function() {" "var res = [];" "var out = [];" "var anchors = document.getElementsByTagName('a');" "for (var i = 0; i < anchors.length; ++i) {" " out.push({" " src: anchors[i].href," " title: anchors[i].title," " text: anchors[i].innerText" " });" "}" "var forms = document.getElementsByTagName('form');" "res.push({" " anchors: out," " forms: forms.length" " });" "return res;" "})()"); return source; } QString WebEngineScript::setElementByIdVisible(const QString &elementStr, bool visibility) { if (visibility) { const QString source = QStringLiteral("var element = document.getElementById('%1'); " "if (element) { " " element.style.removeProperty( 'display' );" "}").arg(elementStr); return source; } else { const QString source = QStringLiteral("var element = document.getElementById('%1'); " "if (element) { " " element.style.display = \"none\";" "}").arg(elementStr); return source; } } QString WebEngineScript::searchElementPosition(const QString &elementStr) { const QString source = QStringLiteral("var element = document.getElementById('%1'); " "if (element) { " " var geometry = element.getBoundingClientRect(); " " [(geometry.left + window.scrollX), (geometry.top + window.scrollY)]; " "}").arg(elementStr); return source; } static QString scrollTop() { return QStringLiteral("document.documentElement.scrollTop"); } QString WebEngineScript::scrollPercentage(int percent) { const QString source = QStringLiteral("var current = ") + scrollTop() + QStringLiteral(";" "var docElement = document.documentElement;" "var height = docElement.clientHeight;" "var newPosition = current + height * %1 /100;" "window.scrollTo(window.scrollX, newPosition);").arg(percent); return source; } QString WebEngineScript::scrollUp(int pixel) { - const QString source = QString::fromLatin1("window.scrollBy(0, %1);").arg(-pixel); + const QString source = QStringLiteral("window.scrollBy(0, %1);").arg(-pixel); return source; } QString WebEngineScript::scrollDown(int pixel) { const QString source = QStringLiteral("window.scrollBy(0, %1);").arg(pixel); return source; } QString WebEngineScript::scrollToPosition(const QPoint &pos) { const QString source = QStringLiteral("window.scrollTo(%1, %2); [window.scrollX, window.scrollY];").arg(pos.x()).arg(pos.y()); return source; } QString WebEngineScript::removeStyleToElement(const QString &elementStr) { const QString source = QStringLiteral("var element = document.getElementById('%1'); " "if (element) { " " element.removeAttribute(\"style\");" "}").arg(elementStr); return source; } QString WebEngineScript::setStyleToElement(const QString &elementStr, const QString &style) { const QString source = QStringLiteral("var element = document.getElementById('%1'); " "if (element) { " " element.style = '%2';" "}").arg(elementStr, style); return source; } QString WebEngineScript::scrollToRelativePosition(qreal pos) { const QString source = QStringLiteral("window.scrollTo(window.scrollX, %1); [window.scrollX, window.scrollY];").arg(pos); return source; } QString WebEngineScript::isScrolledToBottom() { return QStringLiteral("(function() { " "var docElement = document.documentElement;" "var viewportHeight = docElement.clientHeight;" "var isAtBottom = ") + scrollTop() + QStringLiteral(" + viewportHeight >= document.body.scrollHeight;" "return Boolean(isAtBottom); " "}());"); }