diff --git a/CMakeLists.txt b/CMakeLists.txt index d55b2885..024a1787 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,130 +1,130 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.14.44") +set(PIM_VERSION "5.14.45") if (POLICY CMP0053) cmake_policy(SET CMP0053 NEW) endif() project(Messagelib VERSION ${PIM_VERSION}) option(KDEPIM_ENTERPRISE_BUILD "Enable features specific to the enterprise branch, which are normally disabled. Also, it disables many components not needed for Kontact such as the Kolab client." FALSE) option(KDEPIM_RUN_AKONADI_TEST "Enable autotest based on Akonadi." TRUE) option(MESSAGEVIEWER_EXPERIMENTAL_CONVERSATIONVIEW "Experimental conversationview (in progress)" FALSE) set(KF5_MIN_VERSION "5.70.0") set(MESSAGELIB_LIB_VERSION ${PIM_VERSION}) set(AKONADIMIME_LIB_VERSION "5.14.42") set(QT_REQUIRED_VERSION "5.13.0") set(AKONADI_VERSION "5.14.40") set(GRANTLEETHEME_LIB_VERSION "5.14.40") set(GRAVATAR_LIB_VERSION "5.14.41") set(IDENTITYMANAGEMENT_LIB_VERSION "5.14.40") set(KDEPIM_APPS_LIB_VERSION "5.14.40") set(KLDAP_LIB_VERSION "5.14.40") set(KMAILTRANSPORT_LIB_VERSION "5.14.40") set(KMBOX_LIB_VERSION "5.14.40") set(KMIME_LIB_VERSION "5.14.40") set(KPIMTEXTEDIT_LIB_VERSION "5.14.45") set(LIBKDEPIM_LIB_VERSION "5.14.41") set(LIBKLEO_LIB_VERSION "5.14.40") set(PIMCOMMON_LIB_VERSION "5.14.40") set(GPGME_LIB_VERSION "1.11.1") set(AKONADI_CONTACT_VERSION "5.14.40") set(ECM_VERSION ${KF5_MIN_VERSION}) find_package(ECM ${ECM_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${Messagelib_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Gui Test) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5NewStuff ${KMIME_LIB_VERSION} CONFIG REQUIRED) find_package(QGpgme ${GPGME_LIB_VERSION} CONFIG REQUIRED) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Network PrintSupport WebEngine WebEngineWidgets) find_package(KF5Archive ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Completion ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemViews ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5JobWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Service ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Sonnet ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5TextWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5SyntaxHighlighting ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5DBusAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(Grantlee5 "5.2" CONFIG REQUIRED) #Use KF5_VERSION in the future find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Contacts ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiContact ${AKONADI_CONTACT_VERSION} CONFIG REQUIRED) find_package(KF5GrantleeTheme ${GRANTLEETHEME_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Gravatar ${GRAVATAR_LIB_VERSION} CONFIG REQUIRED) find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5KaddressbookGrantlee ${KDEPIM_APPS_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Ldap ${KLDAP_LIB_VERSION} CONFIG REQUIRED) find_package(KF5LibkdepimAkonadi ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Libkleo ${LIBKLEO_LIB_VERSION} CONFIG REQUIRED) find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimCommonAkonadi ${PIMCOMMON_LIB_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5AkonadiSearch "5.14.40" CONFIG REQUIRED) set_package_properties(KF5AkonadiSearch PROPERTIES DESCRIPTION "The Akonadi Search libraries" URL "https://kde.org/" TYPE REQUIRED PURPOSE "Provides search capabilities in KMail and Akonadi") set(CMAKE_CXX_STANDARD 14) if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050e00) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054600) endif() if(BUILD_TESTING) add_definitions(-DBUILD_TESTING) endif() add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS) add_definitions(-DQT_NO_EMIT) add_subdirectory(mimetreeparser) add_subdirectory(messageviewer) add_subdirectory(templateparser) add_subdirectory(messagecomposer) add_subdirectory(messagecore) add_subdirectory(messagelist) add_subdirectory(webengineviewer) ecm_qt_install_logging_categories( EXPORT MESSAGELIB FILE messagelib.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/messagecomposer/autotests/messagefactoryngtest.cpp b/messagecomposer/autotests/messagefactoryngtest.cpp index fa79dddb..7a08499a 100644 --- a/messagecomposer/autotests/messagefactoryngtest.cpp +++ b/messagecomposer/autotests/messagefactoryngtest.cpp @@ -1,945 +1,950 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2020 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 #include #include using namespace MessageComposer; MessageFactoryTest::MessageFactoryTest(QObject *parent) : QObject(parent) { QStandardPaths::setTestModeEnabled(true); } MessageFactoryTest::~MessageFactoryTest() { // Workaround QTestLib not flushing deleteLater()s on exit, which // leads to WebEngine asserts (view not deleted) QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } #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() { qRegisterMetaType(); mIdentMan = new KIdentityManagement::IdentityManager; KIdentityManagement::Identity &ident = mIdentMan->modifyIdentityForUoid(mIdentMan->defaultIdentity().uoid()); ident.setFullName(QStringLiteral("another")); ident.setPrimaryEmailAddress(QStringLiteral("another@another.com")); mIdentMan->newFromScratch(QStringLiteral("test1")); mIdentMan->newFromScratch(QStringLiteral("test2")); mIdentMan->newFromScratch(QStringLiteral("test3")); 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; 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: blu , bly \n" "To: blo , bli \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; 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: blu , bly \n" "To: blo , bli \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; 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: blu , bly \n" "To: blo , bli , 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; 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; 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: cc@cc.cc, cc2@cc.cc\n" "To: you@you.you, you2@you.you, 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; 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); + TemplateParser::TemplateParserSettings::self()->setReplyUsingVisualFormat(true); + factory.setReplyAsHtml(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; 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); + TemplateParser::TemplateParserSettings::self()->setReplyUsingVisualFormat(false); + factory.setReplyAsHtml(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); + TemplateParser::TemplateParserSettings::self()->setReplyUsingVisualFormat(true); + factory.setReplyAsHtml(true); } void MessageFactoryTest::testCreateReplyUTF16Base64Async() { KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("plain_utf16.mbox")); - - TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); - MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); + TemplateParser::TemplateParserSettings::self()->setReplyUsingVisualFormat(true); + factory.setReplyAsHtml(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; 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()); 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()); 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(); const QRegularExpression rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); const QRegularExpressionMatch rxMatch = rx.match(QString::fromLatin1(rdir->head())); QVERIFY(rxMatch.hasMatch()); const QRegularExpression rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); const QRegularExpressionMatch rxmessageidMatch = rxmessageid.match(QString::fromLatin1(rdir->head())); QVERIFY(rxmessageidMatch.hasMatch()); 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(rxmessageidMatch.captured(1)).arg(rxMatch.captured(1)).arg(datetime).arg(QStringLiteral("another ")); 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(); const QRegularExpression rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); const QRegularExpressionMatch rxMatch = rx.match(QString::fromLatin1(rdir->head())); QVERIFY(rxMatch.hasMatch()); const QRegularExpression rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); const QRegularExpressionMatch rxmessageidMatch = rxmessageid.match(QString::fromLatin1(rdir->head())); QVERIFY(rxmessageidMatch.hasMatch()); //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(rxmessageidMatch.captured(1)).arg(rxMatch.captured(1)).arg(datetime).arg(QStringLiteral("another ")); 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(); const QRegularExpression rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); const QRegularExpressionMatch rxMatch = rx.match(QString::fromLatin1(rdir->head())); QVERIFY(rxMatch.hasMatch()); const QRegularExpression rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); const QRegularExpressionMatch rxmessageidMatch = rxmessageid.match(QString::fromLatin1(rdir->head())); QVERIFY(rxmessageidMatch.hasMatch()); 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(rxmessageidMatch.captured(1)).arg(rxMatch.captured(1)).arg(datetime).arg(QStringLiteral("another ")); 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(); const QRegularExpression rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); const QRegularExpressionMatch rxMatch = rx.match(QString::fromLatin1(rdir->head())); QVERIFY(!rxMatch.hasMatch()); const QRegularExpression rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); const QRegularExpressionMatch rxmessageidMatch = rxmessageid.match(QString::fromLatin1(rdir->head())); QVERIFY(rxmessageidMatch.hasMatch()); 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(rxmessageidMatch.captured(1)); 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()); 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()); 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(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(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(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(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/src/helper/messagefactoryng.cpp b/messagecomposer/src/helper/messagefactoryng.cpp index ea778f6a..57f24f9a 100644 --- a/messagecomposer/src/helper/messagefactoryng.cpp +++ b/messagecomposer/src/helper/messagefactoryng.cpp @@ -1,1021 +1,1022 @@ /* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2020 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 "messagefactoryng.h" #include "settings/messagecomposersettings.h" #include "messagefactoryforwardjob.h" #include "messagefactoryreplyjob.h" #include "MessageComposer/Util" #include #ifndef QT_NO_CURSOR #include #endif #include #include #include #include #include #include #include #include "helper/messagehelper.h" #include #include "messagecomposer_debug.h" #include #include #include using namespace MessageComposer; namespace KMime { namespace Types { static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right) { return left.addrSpec().asString() == right.addrSpec().asString(); } } } /** * Strips all the user's addresses from an address list. This is used * when replying. */ static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KIdentityManagement::IdentityManager *manager) { KMime::Types::Mailbox::List addresses(list); for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) { if (manager->thatIsMe(it->prettyAddress())) { it = addresses.erase(it); } else { ++it; } } return addresses; } MessageFactoryNG::MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col, QObject *parent) : QObject(parent) , mOrigMsg(origMsg) , mFolderId(0) , mParentFolderId(0) , mCollection(col) , mReplyStrategy(MessageComposer::ReplySmart) , mId(id) { } MessageFactoryNG::~MessageFactoryNG() { } // Return the addresses to use when replying to the author of msg. // See . static KMime::Types::Mailbox::List authorMailboxes( const KMime::Message::Ptr &msg, const KMime::Types::Mailbox::List &mailingLists) { if (auto mrt = msg->headerByType("Mail-Reply-To")) { return KMime::Types::Mailbox::listFrom7BitString(mrt->as7BitString(false)); } if (auto rt = msg->replyTo(false)) { // Did a mailing list munge Reply-To? auto mboxes = rt->mailboxes(); for (const auto &list : mailingLists) { mboxes.removeAll(list); } if (!mboxes.isEmpty()) { return mboxes; } } return msg->from(true)->mailboxes(); } void MessageFactoryNG::slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll) { applyCharset(msg); MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusReplied()); if (mParentFolderId > 0) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc"); header->fromUnicodeString(QString::number(mParentFolderId), "utf-8"); msg->setHeader(header); } if (auto hrd = mOrigMsg->headerByType("X-KMail-EncryptActionEnabled")) { if (hrd->as7BitString(false).contains("true")) { auto header = new KMime::Headers::Generic("X-KMail-EncryptActionEnabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); msg->setHeader(header); } } msg->assemble(); MessageReply reply; reply.msg = msg; reply.replyAll = replyAll; Q_EMIT createReplyDone(reply); } void MessageFactoryNG::createReplyAsync() { KMime::Message::Ptr msg(new KMime::Message); QByteArray refStr; bool replyAll = true; KMime::Types::Mailbox::List toList; KMime::Types::Mailbox::List replyToList; const uint originalIdentity = identityUoid(mOrigMsg); MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity); replyToList = mOrigMsg->replyTo()->mailboxes(); msg->contentType()->setCharset("utf-8"); if (auto hdr = mOrigMsg->headerByType("List-Post")) { static const QRegularExpression rx { QStringLiteral("<\\s*mailto\\s*:([^@>]+@[^>]+)>"), QRegularExpression::CaseInsensitiveOption }; const auto match = rx.match(hdr->asUnicodeString()); if (match.hasMatch()) { KMime::Types::Mailbox mailbox; mailbox.fromUnicodeString(match.captured(1)); mMailingListAddresses << mailbox; } } switch (mReplyStrategy) { case MessageComposer::ReplySmart: { if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) { toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else if (!mMailingListAddresses.isEmpty()) { if (replyToList.isEmpty()) { toList = (KMime::Types::Mailbox::List() << mMailingListAddresses.at(0)); } else { toList = replyToList; } } else { // Doesn't seem to be a mailing list. auto originalFromList = mOrigMsg->from()->mailboxes(); auto originalToList = mOrigMsg->to()->mailboxes(); if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList)) ) { // Sender seems to be one of our own identities and recipient is not, // so we assume that this is a reply to a "sent" mail where the user // wants to add additional information for the recipient. toList = originalToList; } else { // "Normal" case: reply to sender. toList = authorMailboxes(mOrigMsg, mMailingListAddresses); } replyAll = false; } // strip all my addresses from the list of recipients const KMime::Types::Mailbox::List recipients = toList; toList = stripMyAddressesFromAddressList(recipients, mIdentityManager); // ... unless the list contains only my addresses (reply to self) if (toList.isEmpty() && !recipients.isEmpty()) { toList << recipients.first(); } break; } case MessageComposer::ReplyList: { if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) { KMime::Types::Mailbox mailbox; mailbox.from7BitString(hdr->as7BitString(false)); toList << mailbox; } else if (!mMailingListAddresses.isEmpty()) { toList << mMailingListAddresses[ 0 ]; } else if (!replyToList.isEmpty()) { // assume a Reply-To header mangling mailing list toList = replyToList; } // strip all my addresses from the list of recipients const KMime::Types::Mailbox::List recipients = toList; toList = stripMyAddressesFromAddressList(recipients, mIdentityManager); break; } case MessageComposer::ReplyAll: if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) { toList = KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else { auto ccList = stripMyAddressesFromAddressList(mOrigMsg->cc()->mailboxes(), mIdentityManager); if (!mMailingListAddresses.isEmpty()) { toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager); bool addMailingList = true; for (const KMime::Types::Mailbox &m : qAsConst(mMailingListAddresses)) { if (toList.contains(m)) { addMailingList = false; break; } } if (addMailingList) { toList += mMailingListAddresses.front(); } ccList += authorMailboxes(mOrigMsg, mMailingListAddresses); } else { // Doesn't seem to be a mailing list. auto originalFromList = mOrigMsg->from()->mailboxes(); auto originalToList = mOrigMsg->to()->mailboxes(); if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList)) ) { // Sender seems to be one of our own identities and recipient is not, // so we assume that this is a reply to a "sent" mail where the user // wants to add additional information for the recipient. toList = originalToList; } else { // "Normal" case: reply to sender. toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager); toList += authorMailboxes(mOrigMsg, mMailingListAddresses); } } for (const KMime::Types::Mailbox &mailbox : qAsConst(ccList)) { msg->cc()->addAddress(mailbox); } } break; case MessageComposer::ReplyAuthor: toList = authorMailboxes(mOrigMsg, mMailingListAddresses); replyAll = false; break; case MessageComposer::ReplyNone: // the addressees will be set by the caller break; default: Q_UNREACHABLE(); } for (const KMime::Types::Mailbox &mailbox : qAsConst(toList)) { msg->to()->addAddress(mailbox); } refStr = getRefStr(mOrigMsg); if (!refStr.isEmpty()) { msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); } //In-Reply-To = original msg-id msg->inReplyTo()->from7BitString(mOrigMsg->messageID()->as7BitString(false)); msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(mOrigMsg.data()), "utf-8"); // If the reply shouldn't be blank, apply the template to the message if (mQuote) { MessageFactoryReplyJob *job = new MessageFactoryReplyJob; connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone); job->setMsg(msg); job->setReplyAll(replyAll); job->setIdentityManager(mIdentityManager); job->setSelection(mSelection); job->setTemplate(mTemplate); job->setOrigMsg(mOrigMsg); job->setCollection(mCollection); + job->setReplyAsHtml(mReplyAsHtml); job->start(); } else { slotCreateReplyDone(msg, replyAll); } } void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg) { applyCharset(msg); MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded()); msg->assemble(); Q_EMIT createForwardDone(msg); } void MessageFactoryNG::createForwardAsync() { KMime::Message::Ptr msg(new KMime::Message); // This is a non-multipart, non-text mail (e.g. text/calendar). Construct // a multipart/mixed mail and add the original body as an attachment. if (!mOrigMsg->contentType()->isMultipart() && (!mOrigMsg->contentType()->isText() || (mOrigMsg->contentType()->isText() && mOrigMsg->contentType()->subType() != "html" && mOrigMsg->contentType()->subType() != "plain"))) { const uint originalIdentity = identityUoid(mOrigMsg); MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity); msg->removeHeader(); msg->removeHeader(); msg->contentType()->setMimeType("multipart/mixed"); //TODO: Andras: somebody should check if this is correct. :) // empty text part KMime::Content *msgPart = new KMime::Content; msgPart->contentType()->setMimeType("text/plain"); msg->addContent(msgPart); // the old contents of the mail KMime::Content *secondPart = new KMime::Content; secondPart->contentType()->setMimeType(mOrigMsg->contentType()->mimeType()); secondPart->setBody(mOrigMsg->body()); // use the headers of the original mail secondPart->setHead(mOrigMsg->head()); msg->addContent(secondPart); msg->assemble(); } // Normal message (multipart or text/plain|html) // Just copy the message, the template parser will do the hard work of // replacing the body text in TemplateParser::addProcessedBodyToMessage() else { //TODO Check if this is ok msg->setHead(mOrigMsg->head()); msg->setBody(mOrigMsg->body()); QString oldContentType = msg->contentType()->asUnicodeString(); const uint originalIdentity = identityUoid(mOrigMsg); MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity); // restore the content type, MessageHelper::initFromMessage() sets the contents type to // text/plain, via initHeader(), for unclear reasons msg->contentType()->fromUnicodeString(oldContentType, "utf-8"); msg->assemble(); } msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(mOrigMsg.data()), "utf-8"); MessageFactoryForwardJob *job = new MessageFactoryForwardJob; connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone); job->setIdentityManager(mIdentityManager); job->setMsg(msg); job->setSelection(mSelection); job->setTemplate(mTemplate); job->setOrigMsg(mOrigMsg); job->setCollection(mCollection); job->start(); } QPair< KMime::Message::Ptr, QVector< KMime::Content * > > MessageFactoryNG::createAttachedForward(const Akonadi::Item::List &items) { // create forwarded message with original message as attachment // remove headers that shouldn't be forwarded KMime::Message::Ptr msg(new KMime::Message); QVector< KMime::Content * > attachments; const int numberOfItems(items.count()); if (numberOfItems >= 2) { // don't respect X-KMail-Identity headers because they might differ for // the selected mails MessageHelper::initHeader(msg, mIdentityManager, 0); } else if (numberOfItems == 1) { KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first()); const uint originalIdentity = identityUoid(firstMsg); MessageHelper::initFromMessage(msg, firstMsg, mIdentityManager, originalIdentity); msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(firstMsg.data()), "utf-8"); } MessageHelper::setAutomaticFields(msg, true); #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif if (numberOfItems == 0) { attachments << createForwardAttachmentMessage(mOrigMsg); MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded()); } else { // iterate through all the messages to be forwarded attachments.reserve(items.count()); for (const Akonadi::Item &item : qAsConst(items)) { attachments << createForwardAttachmentMessage(MessageComposer::Util::message(item)); MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded()); } } applyCharset(msg); //msg->assemble(); return QPair< KMime::Message::Ptr, QVector< KMime::Content * > >(msg, QVector< KMime::Content * >() << attachments); } KMime::Content *MessageFactoryNG::createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg) { // remove headers that shouldn't be forwarded MessageCore::StringUtil::removePrivateHeaderFields(fwdMsg); fwdMsg->removeHeader(); fwdMsg->assemble(); // set the part KMime::Content *msgPart = new KMime::Content(fwdMsg.data()); msgPart->contentType()->setMimeType("message/rfc822"); msgPart->contentDisposition()->setParameter(QStringLiteral("filename"), i18n("forwarded message")); msgPart->contentDisposition()->setDisposition(KMime::Headers::CDinline); msgPart->contentDescription()->fromUnicodeString(fwdMsg->from()->asUnicodeString() + QLatin1String(": ") + fwdMsg->subject()->asUnicodeString(), "utf-8"); msgPart->setBody(fwdMsg->encodedContent()); msgPart->assemble(); MessageComposer::Util::addLinkInformation(fwdMsg, 0, Akonadi::MessageStatus::statusForwarded()); return msgPart; } bool MessageFactoryNG::replyAsHtml() const { return mReplyAsHtml; } void MessageFactoryNG::setReplyAsHtml(bool replyAsHtml) { mReplyAsHtml = replyAsHtml; } KMime::Message::Ptr MessageFactoryNG::createResend() { KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mOrigMsg->encodedContent()); msg->parse(); msg->removeHeader(); uint originalIdentity = identityUoid(mOrigMsg); // Set the identity from above KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Identity"); header->fromUnicodeString(QString::number(originalIdentity), "utf-8"); msg->setHeader(header); // Restore the original bcc field as this is overwritten in applyIdentity msg->bcc(mOrigMsg->bcc()); return msg; } KMime::Message::Ptr MessageFactoryNG::createRedirect(const QString &toStr, const QString &ccStr, const QString &bccStr, int transportId, const QString &fcc, int identity) { if (!mOrigMsg) { return KMime::Message::Ptr(); } // copy the message 1:1 KMime::Message::Ptr msg(new KMime::Message); msg->setContent(mOrigMsg->encodedContent()); msg->parse(); uint id = identity; if (identity == -1) { if (auto hrd = msg->headerByType("X-KMail-Identity")) { const QString strId = hrd->asUnicodeString().trimmed(); if (!strId.isEmpty()) { id = strId.toUInt(); } } } const KIdentityManagement::Identity &ident = mIdentityManager->identityForUoidOrDefault(id); // X-KMail-Redirect-From: content const QString strByWayOf = QString::fromLocal8Bit("%1 (by way of %2 <%3>)") .arg(mOrigMsg->from()->asUnicodeString(), ident.fullName(), ident.primaryEmailAddress()); // Resent-From: content const QString strFrom = QString::fromLocal8Bit("%1 <%2>") .arg(ident.fullName(), ident.primaryEmailAddress()); // format the current date to be used in Resent-Date: // FIXME: generate datetime the same way as KMime, otherwise we get inconsistency // in unit-tests. Unfortunatelly RFC2822Date is not enough for us, we need the // composition hack below const QDateTime dt = QDateTime::currentDateTime(); const QString newDate = QLocale::c().toString(dt, QStringLiteral("ddd, ")) +dt.toString(Qt::RFC2822Date); // Clean up any resent headers msg->removeHeader("Resent-Cc"); msg->removeHeader("Resent-Bcc"); msg->removeHeader("Resent-Sender"); // date, from to and id will be set anyway // prepend Resent-*: headers (c.f. RFC2822 3.6.6) QString msgIdSuffix; if (MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()) { msgIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix(); } KMime::Headers::Generic *header = new KMime::Headers::Generic("Resent-Message-ID"); header->fromUnicodeString(MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), msgIdSuffix), "utf-8"); msg->setHeader(header); header = new KMime::Headers::Generic("Resent-Date"); header->fromUnicodeString(newDate, "utf-8"); msg->setHeader(header); header = new KMime::Headers::Generic("Resent-From"); header->fromUnicodeString(strFrom, "utf-8"); msg->setHeader(header); if (msg->to(false)) { KMime::Headers::To *headerT = new KMime::Headers::To; headerT->fromUnicodeString(mOrigMsg->to()->asUnicodeString(), "utf-8"); msg->setHeader(headerT); } header = new KMime::Headers::Generic("Resent-To"); header->fromUnicodeString(toStr, "utf-8"); msg->setHeader(header); if (!ccStr.isEmpty()) { header = new KMime::Headers::Generic("Resent-Cc"); header->fromUnicodeString(ccStr, "utf-8"); msg->setHeader(header); } if (!bccStr.isEmpty()) { header = new KMime::Headers::Generic("Resent-Bcc"); header->fromUnicodeString(bccStr, "utf-8"); msg->setHeader(header); } header = new KMime::Headers::Generic("X-KMail-Redirect-From"); header->fromUnicodeString(strByWayOf, "utf-8"); msg->setHeader(header); if (transportId != -1) { header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transportId), "utf-8"); msg->setHeader(header); } if (!fcc.isEmpty()) { header = new KMime::Headers::Generic("X-KMail-Fcc"); header->fromUnicodeString(fcc, "utf-8"); msg->setHeader(header); } const bool fccIsDisabled = ident.disabledFcc(); if (fccIsDisabled) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); msg->setHeader(header); } else { msg->removeHeader("X-KMail-FccDisabled"); } msg->assemble(); MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded()); return msg; } KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt() { QString receiptTo; if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return KMime::Message::Ptr(); } receiptTo.remove(QChar::fromLatin1('\n')); KMime::Message::Ptr receipt(new KMime::Message); const uint originalIdentity = identityUoid(mOrigMsg); MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity); receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1()); receipt->subject()->fromUnicodeString(i18n("Receipt: ") + mOrigMsg->subject()->asUnicodeString(), "utf-8"); QString str = QStringLiteral("Your message was successfully delivered."); str += QLatin1String("\n\n---------- Message header follows ----------\n"); str += QString::fromLatin1(mOrigMsg->head()); str += QLatin1String("--------------------------------------------\n"); // Conversion to toLatin1 is correct here as Mail headers should contain // ascii only receipt->setBody(str.toLatin1()); MessageHelper::setAutomaticFields(receipt); receipt->assemble(); return receipt; } KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal, const QVector &m) { // extract where to send to: QString receiptTo; if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return KMime::Message::Ptr(new KMime::Message); } receiptTo.remove(QChar::fromLatin1('\n')); QString special; // fill in case of error, warning or failure // extract where to send from: QString finalRecipient = mIdentityManager->identityForUoidOrDefault(identityUoid(mOrigMsg)).fullEmailAddr(); // // Generate message: // KMime::Message::Ptr receipt(new KMime::Message()); const uint originalIdentity = identityUoid(mOrigMsg); MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity); receipt->contentType()->from7BitString("multipart/report"); receipt->contentType()->setBoundary(KMime::multiPartBoundary()); receipt->contentType()->setCharset("us-ascii"); receipt->removeHeader(); // Modify the ContentType directly (replaces setAutomaticFields(true)) receipt->contentType()->setParameter(QStringLiteral("report-type"), QStringLiteral("disposition-notification")); QString description = replaceHeadersInString(mOrigMsg, KMime::MDN::descriptionFor(d, m)); // text/plain part: KMime::Content *firstMsgPart = new KMime::Content(mOrigMsg.data()); firstMsgPart->contentType()->setMimeType("text/plain"); firstMsgPart->contentType()->setCharset("utf-8"); firstMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); firstMsgPart->setBody(description.toUtf8()); receipt->addContent(firstMsgPart); // message/disposition-notification part: KMime::Content *secondMsgPart = new KMime::Content(mOrigMsg.data()); secondMsgPart->contentType()->setMimeType("message/disposition-notification"); secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray originalRecipient = ""; if (auto hrd = mOrigMsg->headerByType("Original-Recipient")) { originalRecipient = hrd->as7BitString(false); } secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent( finalRecipient, originalRecipient, mOrigMsg->messageID()->as7BitString(false), /* Message-ID */ d, a, s, m, special)); receipt->addContent(secondMsgPart); if (mdnQuoteOriginal < 0 || mdnQuoteOriginal > 2) { mdnQuoteOriginal = 0; } /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/ KMime::Content *thirdMsgPart = new KMime::Content(mOrigMsg.data()); switch (mdnQuoteOriginal) { case 1: thirdMsgPart->contentType()->setMimeType("message/rfc822"); thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(mOrigMsg)); receipt->addContent(thirdMsgPart); break; case 2: thirdMsgPart->contentType()->setMimeType("text/rfc822-headers"); thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(mOrigMsg)); receipt->addContent(thirdMsgPart); break; case 0: default: delete thirdMsgPart; break; } receipt->to()->fromUnicodeString(receiptTo, "utf-8"); //Laurent: We don't translate subject ? receipt->subject()->from7BitString("Message Disposition Notification"); KMime::Headers::InReplyTo *header = new KMime::Headers::InReplyTo; header->fromUnicodeString(mOrigMsg->messageID()->asUnicodeString(), "utf-8"); receipt->setHeader(header); receipt->references()->from7BitString(getRefStr(mOrigMsg)); receipt->assemble(); qCDebug(MESSAGECOMPOSER_LOG) << "final message:" + receipt->encodedContent(); receipt->assemble(); return receipt; } QPair< KMime::Message::Ptr, KMime::Content * > MessageFactoryNG::createForwardDigestMIME(const Akonadi::Item::List &items) { KMime::Message::Ptr msg(new KMime::Message); KMime::Content *digest = new KMime::Content(msg.data()); QString mainPartText = i18n("\nThis is a MIME digest forward. The content of the" " message is contained in the attachment(s).\n\n\n"); digest->contentType()->setMimeType("multipart/digest"); digest->contentType()->setBoundary(KMime::multiPartBoundary()); digest->contentDescription()->fromUnicodeString(QStringLiteral("Digest of %1 messages.").arg(items.count()), "utf8"); digest->contentDisposition()->setFilename(QStringLiteral("digest")); digest->fromUnicodeString(mainPartText); int id = 0; for (const Akonadi::Item &item : qAsConst(items)) { KMime::Message::Ptr fMsg = MessageComposer::Util::message(item); if (id == 0) { if (auto hrd = fMsg->headerByType("X-KMail-Identity")) { id = hrd->asUnicodeString().toInt(); } } MessageCore::StringUtil::removePrivateHeaderFields(fMsg); fMsg->removeHeader(); fMsg->assemble(); KMime::Content *part = new KMime::Content(digest); part->contentType()->setMimeType("message/rfc822"); part->contentType()->setCharset(fMsg->contentType()->charset()); part->contentID()->setIdentifier(fMsg->contentID()->identifier()); part->contentDescription()->fromUnicodeString(fMsg->contentDescription()->asUnicodeString(), "utf8"); part->contentDisposition()->setParameter(QStringLiteral("name"), i18n("forwarded message")); part->fromUnicodeString(QString::fromLatin1(fMsg->encodedContent())); part->assemble(); MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded()); digest->addContent(part); } digest->assemble(); id = mFolderId; MessageHelper::initHeader(msg, mIdentityManager, id); // qCDebug(MESSAGECOMPOSER_LOG) << "digest:" << digest->contents().size() << digest->encodedContent(); return QPair< KMime::Message::Ptr, KMime::Content * >(msg, digest); } void MessageFactoryNG::setIdentityManager(KIdentityManagement::IdentityManager *ident) { mIdentityManager = ident; } void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy) { mReplyStrategy = replyStrategy; } void MessageFactoryNG::setSelection(const QString &selection) { mSelection = selection; } void MessageFactoryNG::setQuote(bool quote) { mQuote = quote; } void MessageFactoryNG::setTemplate(const QString &templ) { mTemplate = templ; } void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses) { mMailingListAddresses << listAddresses; } void MessageFactoryNG::setFolderIdentity(Akonadi::Collection::Id folderIdentityId) { mFolderId = folderIdentityId; } void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId) { mParentFolderId = parentColId; } bool MessageFactoryNG::MDNRequested(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); return !receiptTo.isEmpty(); } bool MessageFactoryNG::MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no // MDN sent) ] if there is more than one distinct address in the // Disposition-Notification-To header. qCDebug(MESSAGECOMPOSER_LOG) << "KEmailAddress::splitAddressList(receiptTo):" << KEmailAddress::splitAddressList(receiptTo).join(QLatin1Char('\n')); return KEmailAddress::splitAddressList(receiptTo).count() > 1; } bool MessageFactoryNG::MDNReturnPathEmpty(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in // the Disposition-Notification-To header differs from the address // in the Return-Path header. [...] Confirmation from the user // SHOULD be obtained (or no MDN sent) if there is no Return-Path // header in the message [...] KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, "Return-Path"); QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain; qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath; return returnPath.isEmpty(); } bool MessageFactoryNG::MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in // the Disposition-Notification-To header differs from the address // in the Return-Path header. [...] Confirmation from the user // SHOULD be obtained (or no MDN sent) if there is no Return-Path // header in the message [...] KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, QStringLiteral("Return-Path").toLatin1()); QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain; qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath; return !receiptTo.contains(returnPath, Qt::CaseSensitive); } bool MessageFactoryNG::MDNMDNUnknownOption(const KMime::Message::Ptr &msg) { // RFC 2298: An importance of "required" indicates that // interpretation of the parameter is necessary for proper // generation of an MDN in response to this request. If a UA does // not understand the meaning of the parameter, it MUST NOT generate // an MDN with any disposition type other than "failed" in response // to the request. QString notificationOptions; if (auto hrd = msg->headerByType("Disposition-Notification-Options")) { notificationOptions = hrd->asUnicodeString(); } if (notificationOptions.contains(QLatin1String("required"), Qt::CaseSensitive)) { // ### hacky; should parse... // There is a required option that we don't understand. We need to // ask the user what we should do: return true; } return false; } uint MessageFactoryNG::identityUoid(const KMime::Message::Ptr &msg) { QString idString; if (auto hdr = msg->headerByType("X-KMail-Identity")) { idString = hdr->asUnicodeString().trimmed(); } bool ok = false; uint id = idString.toUInt(&ok); if (!ok || id == 0) { id = mIdentityManager->identityForAddress(msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid(); } if (id == 0 && mFolderId > 0) { id = mFolderId; } return id; } QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s) { QString result = s; static QRegularExpression rx { QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), QRegularExpression::CaseInsensitiveOption }; const QString sDate = KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch()); qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate; result.replace(QStringLiteral("${date}"), sDate); int idx = 0; for (auto match = rx.match(result); match.hasMatch(); match = rx.match(result, idx)) { idx = match.capturedStart(1); const QByteArray ba = match.captured(1).toLatin1(); if (auto hdr = msg->headerByType(ba.constData())) { const auto replacement = hdr->asUnicodeString(); result.replace(idx, match.capturedLength(1), replacement); idx += replacement.length(); } else { result.remove(idx, match.capturedLength(1)); } } return result; } void MessageFactoryNG::applyCharset(const KMime::Message::Ptr msg) { if (MessageComposer::MessageComposerSettings::forceReplyCharset()) { // first convert the body from its current encoding to unicode representation QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); if (!bodyCodec) { bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8")); } const QString body = bodyCodec->toUnicode(msg->body()); // then apply the encoding of the original message msg->contentType()->setCharset(mOrigMsg->contentType()->charset()); QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); if (!codec) { qCCritical(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << msg->contentType()->charset(); } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred const QStringList charsets = MessageComposer::MessageComposerSettings::preferredCharsets(); QVector chars; chars.reserve(charsets.count()); for (const QString &charset : charsets) { chars << charset.toLatin1(); } QByteArray fallbackCharset = MessageComposer::Util::selectCharset(chars, body); if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through fallbackCharset = "UTF-8"; } codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset)); msg->setBody(codec->fromUnicode(body)); } else { msg->setBody(codec->fromUnicode(body)); } } } QByteArray MessageFactoryNG::getRefStr(const KMime::Message::Ptr &msg) { QByteArray firstRef, lastRef, refStr, retRefStr; int i, j; if (auto hdr = msg->references(false)) { refStr = hdr->as7BitString(false).trimmed(); } if (refStr.isEmpty()) { return msg->messageID()->as7BitString(false); } i = refStr.indexOf('<'); j = refStr.indexOf('>'); firstRef = refStr.mid(i, j - i + 1); if (!firstRef.isEmpty()) { retRefStr = firstRef + ' '; } i = refStr.lastIndexOf('<'); j = refStr.lastIndexOf('>'); lastRef = refStr.mid(i, j - i + 1); if (!lastRef.isEmpty() && lastRef != firstRef) { retRefStr += lastRef + ' '; } retRefStr += msg->messageID()->as7BitString(false); return retRefStr; } diff --git a/messagecomposer/src/helper/messagefactoryreplyjob.cpp b/messagecomposer/src/helper/messagefactoryreplyjob.cpp index 3e1faf5e..8bfac4ed 100644 --- a/messagecomposer/src/helper/messagefactoryreplyjob.cpp +++ b/messagecomposer/src/helper/messagefactoryreplyjob.cpp @@ -1,98 +1,99 @@ /* Copyright (C) 2017-2020 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 "messagefactoryreplyjob.h" #include "settings/messagecomposersettings.h" #include #include using namespace MessageComposer; MessageFactoryReplyJob::MessageFactoryReplyJob(QObject *parent) : QObject(parent) { } MessageFactoryReplyJob::~MessageFactoryReplyJob() { } void MessageFactoryReplyJob::start() { TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(mMsg, (mReplyAll ? TemplateParser::TemplateParserJob::ReplyAll : TemplateParser::TemplateParserJob::Reply), this); connect(parser, &TemplateParser::TemplateParserJob::parsingDone, this, &MessageFactoryReplyJob::slotReplyDone); parser->setIdentityManager(mIdentityManager); parser->setCharsets(MessageComposerSettings::self()->preferredCharsets()); parser->setWordWrap(MessageComposerSettings::wordWrap(), MessageComposerSettings::lineWrapWidth()); + parser->setReplyAsHtml(mReplyAsHtml); if (MessageComposer::MessageComposerSettings::quoteSelectionOnly()) { parser->setSelection(mSelection); } parser->setAllowDecryption(true); if (!mTemplate.isEmpty()) { parser->process(mTemplate, mOrigMsg); } else { parser->process(mOrigMsg, mCollection.id()); } } void MessageFactoryReplyJob::slotReplyDone() { Q_EMIT replyDone(mMsg, mReplyAll); deleteLater(); } void MessageFactoryReplyJob::setCollection(const Akonadi::Collection &collection) { mCollection = collection; } void MessageFactoryReplyJob::setReplyAsHtml(bool replyAsHtml) { mReplyAsHtml = replyAsHtml; } void MessageFactoryReplyJob::setReplyAll(bool replyAll) { mReplyAll = replyAll; } void MessageFactoryReplyJob::setMsg(const KMime::Message::Ptr &msg) { mMsg = msg; } void MessageFactoryReplyJob::setTemplate(const QString &tmpl) { mTemplate = tmpl; } void MessageFactoryReplyJob::setSelection(const QString &selection) { mSelection = selection; } void MessageFactoryReplyJob::setOrigMsg(const KMime::Message::Ptr &origMsg) { mOrigMsg = origMsg; } void MessageFactoryReplyJob::setIdentityManager(KIdentityManagement::IdentityManager *identityManager) { mIdentityManager = identityManager; } diff --git a/templateparser/src/settings/templateparser.kcfg b/templateparser/src/settings/templateparser.kcfg index edb725d3..95015581 100644 --- a/templateparser/src/settings/templateparser.kcfg +++ b/templateparser/src/settings/templateparser.kcfg @@ -1,48 +1,48 @@ true true - - + + false DefaultTemplates::defaultNewMessage() DefaultTemplates::defaultReply() DefaultTemplates::defaultReplyAll() DefaultTemplates::defaultForward() DefaultTemplates::defaultQuoteString() diff --git a/templateparser/src/templateparserjob.cpp b/templateparser/src/templateparserjob.cpp index a2a39aab..ee2209f0 100644 --- a/templateparser/src/templateparserjob.cpp +++ b/templateparser/src/templateparserjob.cpp @@ -1,1568 +1,1569 @@ /* Copyright (C) 2017-2020 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 "templateparserjob.h" #include "templateparserjob_p.h" #include "templateparserextracthtmlinfo.h" #include "globalsettings_templateparser.h" #include "customtemplates_kfg.h" #include "templatesconfiguration_kfg.h" #include "templatesconfiguration.h" #include "templatesutil.h" #include "templatesutil_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "templateparser_debug.h" #include #include #include #include #include #include namespace { Q_DECL_CONSTEXPR inline int pipeTimeout() { return 15 * 1000; } static QTextCodec *selectCharset(const QStringList &charsets, const QString &text) { for (const QString &name : charsets) { // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because // the former knows us-ascii is latin1. bool ok = true; QTextCodec *codec = nullptr; if (name == QLatin1String("locale")) { codec = QTextCodec::codecForLocale(); } else { codec = KCharsets::charsets()->codecForName(name, ok); } if (!ok || !codec) { qCWarning(TEMPLATEPARSER_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 == QLatin1String("us-ascii") && !KMime::isUsAscii(text)) { continue; } qCDebug(TEMPLATEPARSER_LOG) << "Chosen charset" << name << codec->name(); return codec; } } qCDebug(TEMPLATEPARSER_LOG) << "No appropriate charset found."; return KCharsets::charsets()->codecForName(QStringLiteral("utf-8")); } } using namespace TemplateParser; TemplateParserJobPrivate::TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode) : mMsg(amsg) , mMode(amode) { mEmptySource = new MimeTreeParser::SimpleObjectTreeSource; mEmptySource->setDecryptMessage(mAllowDecryption); mOtp = new MimeTreeParser::ObjectTreeParser(mEmptySource); mOtp->setAllowAsync(false); } TemplateParserJobPrivate::~TemplateParserJobPrivate() { delete mEmptySource; delete mOtp; } void TemplateParserJobPrivate::setAllowDecryption(const bool allowDecryption) { mAllowDecryption = allowDecryption; mEmptySource->setDecryptMessage(mAllowDecryption); } TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent) : QObject(parent) , d(new TemplateParserJobPrivate(amsg, amode)) { } TemplateParserJob::~TemplateParserJob() = default; void TemplateParserJob::setSelection(const QString &selection) { d->mSelection = selection; } void TemplateParserJob::setAllowDecryption(const bool allowDecryption) { d->setAllowDecryption(allowDecryption); } bool TemplateParserJob::shouldStripSignature() const { // Only strip the signature when replying, it should be preserved when forwarding return (d->mMode == Reply || d->mMode == ReplyAll) && TemplateParserSettings::self()->stripSignature(); } void TemplateParserJob::setIdentityManager(KIdentityManagement::IdentityManager *ident) { d->m_identityManager = ident; } void TemplateParserJob::setCharsets(const QStringList &charsets) { d->mCharsets = charsets; } int TemplateParserJob::parseQuotes(const QString &prefix, const QString &str, QString "e) { int pos = prefix.length(); int len; const int str_len = str.length(); // Also allow the german lower double-quote sign as quote separator, not only // the standard ASCII quote ("). This fixes bug 166728. const QList< QChar > quoteChars = {QLatin1Char('"'), 0x201C}; QChar prev(QChar::Null); pos++; len = pos; while (pos < str_len) { const QChar c = str[pos]; pos++; len++; if (!prev.isNull()) { quote.append(c); prev = QChar::Null; } else { if (c == QLatin1Char('\\')) { prev = c; } else if (quoteChars.contains(c)) { break; } else { quote.append(c); } } } return len; } void TemplateParserJob::process(const KMime::Message::Ptr &aorig_msg, qint64 afolder) { if (aorig_msg == nullptr) { qCDebug(TEMPLATEPARSER_LOG) << "aorig_msg == 0!"; Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); return; } d->mOrigMsg = aorig_msg; d->mFolder = afolder; const QString tmpl = findTemplate(); if (tmpl.isEmpty()) { Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); return; } processWithTemplate(tmpl); } void TemplateParserJob::process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder) { d->mForceCursorPosition = false; d->mOrigMsg = aorig_msg; d->mFolder = afolder; const QString tmpl = findCustomTemplate(tmplName); processWithTemplate(tmpl); } void TemplateParserJob::processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder) { d->mIdentity = uoid; process(aorig_msg, afolder); } MimeTreeParser::MessagePart::Ptr toplevelTextNode(MimeTreeParser::MessagePart::Ptr messageTree) { foreach (const auto &mp, messageTree->subParts()) { auto text = mp.dynamicCast(); const auto attach = mp.dynamicCast(); if (text && !attach) { // TextMessagePart can have several subparts cause of PGP inline, we search for the first part with content foreach (const auto &sub, mp->subParts()) { if (!sub->text().trimmed().isEmpty()) { return sub; } } return text; } else if (const auto html = mp.dynamicCast()) { return html; } else if (const auto alternative = mp.dynamicCast()) { return alternative; } else { auto ret = toplevelTextNode(mp); if (ret) { return ret; } } } return MimeTreeParser::MessagePart::Ptr(); } void TemplateParserJob::processWithTemplate(const QString &tmpl) { d->mOtp->parseObjectTree(d->mOrigMsg.data()); const auto mp = toplevelTextNode(d->mOtp->parsedPart()); QString plainText = mp->plaintextContent(); QString htmlElement; if (mp->isHtml()) { htmlElement = d->mOtp->htmlContent(); if (plainText.isEmpty()) { //HTML-only mails plainText = htmlElement; } } else { //plain mails only QString htmlReplace = plainText.toHtmlEscaped(); htmlReplace.replace(QLatin1Char('\n'), QStringLiteral("
")); htmlElement = QStringLiteral("%1\n").arg(htmlReplace); } TemplateParserExtractHtmlInfo *job = new TemplateParserExtractHtmlInfo(this); connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone); job->setHtmlForExtractingTextPlain(plainText); job->setTemplate(tmpl); job->setHtmlForExtractionHeaderAndBody(htmlElement); job->start(); } void TemplateParserJob::setReplyAsHtml(bool replyAsHtml) { d->mReplyAsHtml = replyAsHtml; } void TemplateParserJob::slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result) { d->mExtractHtmlInfoResult = result; const QString tmpl = d->mExtractHtmlInfoResult.mTemplate; const int tmpl_len = tmpl.length(); QString plainBody; QString htmlBody; bool dnl = false; auto definedLocale = QLocale(); for (int i = 0; i < tmpl_len; ++i) { QChar c = tmpl[i]; // qCDebug(TEMPLATEPARSER_LOG) << "Next char: " << c; if (c == QLatin1Char('%')) { const QString cmd = tmpl.mid(i + 1); if (cmd.startsWith(QLatin1Char('-'))) { // dnl qCDebug(TEMPLATEPARSER_LOG) << "Command: -"; dnl = true; i += 1; } else if (cmd.startsWith(QLatin1String("REM="))) { // comments qCDebug(TEMPLATEPARSER_LOG) << "Command: REM="; QString q; const int len = parseQuotes(QStringLiteral("REM="), cmd, q); i += len; } else if (cmd.startsWith(QLatin1String("LANGUAGE="))) { QString q; const int len = parseQuotes(QStringLiteral("LANGUAGE="), cmd, q); i += len; if (!q.isEmpty()) { definedLocale = QLocale(q); } } else if (cmd.startsWith(QLatin1String("DICTIONARYLANGUAGE="))) { QString q; const int len = parseQuotes(QStringLiteral("DICTIONARYLANGUAGE="), cmd, q); i += len; if (!q.isEmpty()) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Dictionary"); header->fromUnicodeString(q, "utf-8"); d->mMsg->setHeader(header); } } else if (cmd.startsWith(QLatin1String("INSERT=")) || cmd.startsWith(QLatin1String("PUT="))) { QString q; int len = 0; if (cmd.startsWith(QLatin1String("INSERT="))) { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: INSERT="; len = parseQuotes(QStringLiteral("INSERT="), cmd, q); } else { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: PUT="; len = parseQuotes(QStringLiteral("PUT="), cmd, q); } i += len; QString path = KShell::tildeExpand(q); QFileInfo finfo(path); if (finfo.isRelative()) { path = QDir::homePath() + QLatin1Char('/') + q; } QFile file(path); if (file.open(QIODevice::ReadOnly)) { const QByteArray content = file.readAll(); const QString str = QString::fromLocal8Bit(content.constData(), content.size()); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (d->mDebug) { KMessageBox::error( nullptr, i18nc("@info", "Cannot insert content from file %1: %2", path, file.errorString())); } } else if (cmd.startsWith(QLatin1String("SYSTEM="))) { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: SYSTEM="; QString q; const int len = parseQuotes(QStringLiteral("SYSTEM="), cmd, q); i += len; const QString pipe_cmd = q; const QString str = pipe(pipe_cmd, QString()); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("QUOTEPIPE="))) { // pipe message body through command and insert it as quotation qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTEPIPE="; QString q; const int len = parseQuotes(QStringLiteral("QUOTEPIPE="), cmd, q); i += len; const QString pipe_cmd = q; if (d->mOrigMsg) { const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed)); QString plainQuote = quotedPlainText(plainStr); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed)); const QString htmlQuote = quotedHtmlText(htmlStr); htmlBody.append(htmlQuote); } } else if (cmd.startsWith(QLatin1String("QUOTE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTE"; i += strlen("QUOTE"); if (d->mOrigMsg) { QString plainQuote = quotedPlainText(plainMessageText(shouldStripSignature(), SelectionAllowed)); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlQuote = quotedHtmlText(htmlMessageText(shouldStripSignature(), SelectionAllowed)); htmlBody.append(htmlQuote); } } else if (cmd.startsWith(QLatin1String("FORCEDPLAIN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDPLAIN"; d->mQuotes = ReplyAsPlain; i += strlen("FORCEDPLAIN"); } else if (cmd.startsWith(QLatin1String("FORCEDHTML"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDHTML"; d->mQuotes = ReplyAsHtml; i += strlen("FORCEDHTML"); } else if (cmd.startsWith(QLatin1String("QHEADERS"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: QHEADERS"; i += strlen("QHEADERS"); if (d->mOrigMsg) { const QString headerStr = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg)); QString plainQuote = quotedPlainText(headerStr); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlQuote = quotedHtmlText(headerStr); const QString str = plainTextToHtml(htmlQuote); htmlBody.append(str); } } else if (cmd.startsWith(QLatin1String("HEADERS"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADERS"; i += strlen("HEADERS"); if (d->mOrigMsg) { const QString str = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg)); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("TEXTPIPE="))) { // pipe message body through command and insert it as is qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXTPIPE="; QString q; const int len = parseQuotes(QStringLiteral("TEXTPIPE="), cmd, q); i += len; const QString pipe_cmd = q; if (d->mOrigMsg) { const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed)); plainBody.append(plainStr); const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed)); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("MSGPIPE="))) { // pipe full message through command and insert result as is qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGPIPE="; QString q; const int len = parseQuotes(QStringLiteral("MSGPIPE="), cmd, q); i += len; if (d->mOrigMsg) { QString pipe_cmd = q; const QString str = pipe(pipe_cmd, QString::fromLatin1(d->mOrigMsg->encodedContent())); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("BODYPIPE="))) { // pipe message body generated so far through command and insert result as is qCDebug(TEMPLATEPARSER_LOG) << "Command: BODYPIPE="; QString q; const int len = parseQuotes(QStringLiteral("BODYPIPE="), cmd, q); i += len; const QString pipe_cmd = q; const QString plainStr = pipe(pipe_cmd, plainBody); plainBody.append(plainStr); const QString htmlStr = pipe(pipe_cmd, htmlBody); const QString body = plainTextToHtml(htmlStr); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CLEARPIPE="))) { // pipe message body generated so far through command and // insert result as is replacing current body qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEARPIPE="; QString q; const int len = parseQuotes(QStringLiteral("CLEARPIPE="), cmd, q); i += len; const QString pipe_cmd = q; const QString plainStr = pipe(pipe_cmd, plainBody); plainBody = plainStr; const QString htmlStr = pipe(pipe_cmd, htmlBody); htmlBody = htmlStr; KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(0), "utf-8"); d->mMsg->setHeader(header); } else if (cmd.startsWith(QLatin1String("TEXT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXT"; i += strlen("TEXT"); if (d->mOrigMsg) { const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed); plainBody.append(plainStr); const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("OTEXTSIZE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXTSIZE"; i += strlen("OTEXTSIZE"); if (d->mOrigMsg) { const QString str = QStringLiteral("%1").arg(d->mOrigMsg->body().length()); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTEXT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXT"; i += strlen("OTEXT"); if (d->mOrigMsg) { const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed); plainBody.append(plainStr); const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("OADDRESSEESADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OADDRESSEESADDR"; i += strlen("OADDRESSEESADDR"); if (d->mOrigMsg) { const QString to = d->mOrigMsg->to()->asUnicodeString(); const QString cc = d->mOrigMsg->cc()->asUnicodeString(); if (!to.isEmpty()) { const QString toLine = i18nc("@item:intext email To", "To:") + QLatin1Char(' ') + to; plainBody.append(toLine); const QString body = plainTextToHtml(toLine); htmlBody.append(body); } if (!to.isEmpty() && !cc.isEmpty()) { plainBody.append(QLatin1Char('\n')); const QString str = plainTextToHtml(QString(QLatin1Char('\n'))); htmlBody.append(str); } if (!cc.isEmpty()) { const QString ccLine = i18nc("@item:intext email CC", "CC:") + QLatin1Char(' ') + cc; plainBody.append(ccLine); const QString str = plainTextToHtml(ccLine); htmlBody.append(str); } } } else if (cmd.startsWith(QLatin1String("CCADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCADDR"; i += strlen("CCADDR"); const QString str = d->mMsg->cc()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCNAME"; i += strlen("CCNAME"); const QString str = d->mMsg->cc()->displayString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCFNAME"; i += strlen("CCFNAME"); const QString str = d->mMsg->cc()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCLNAME"; i += strlen("CCLNAME"); const QString str = d->mMsg->cc()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOADDR"; i += strlen("TOADDR"); const QString str = d->mMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TONAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TONAME"; i += strlen("TONAME"); const QString str = (d->mMsg->to()->displayString()); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOFNAME"; i += strlen("TOFNAME"); const QString str = d->mMsg->to()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLNAME"; i += strlen("TOLNAME"); const QString str = d->mMsg->to()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOLIST"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLIST"; i += strlen("TOLIST"); const QString str = d->mMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMADDR"; i += strlen("FROMADDR"); const QString str = d->mMsg->from()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMNAME"; i += strlen("FROMNAME"); const QString str = d->mMsg->from()->displayString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMFNAME"; i += strlen("FROMFNAME"); const QString str = d->mMsg->from()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMLNAME"; i += strlen("FROMLNAME"); const QString str = d->mMsg->from()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FULLSUBJECT")) || cmd.startsWith(QLatin1String("FULLSUBJ"))) { if (cmd.startsWith(QLatin1String("FULLSUBJ"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJ"; i += strlen("FULLSUBJ"); } else { qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJECT"; i += strlen("FULLSUBJECT"); } const QString str = d->mMsg->subject()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("MSGID"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGID"; i += strlen("MSGID"); const QString str = d->mMsg->messageID()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("OHEADER="))) { // insert specified content of header from original message qCDebug(TEMPLATEPARSER_LOG) << "Command: OHEADER="; QString q; const int len = parseQuotes(QStringLiteral("OHEADER="), cmd, q); i += len; if (d->mOrigMsg) { const QString hdr = q; QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("HEADER="))) { // insert specified content of header from current message qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER="; QString q; const int len = parseQuotes(QStringLiteral("HEADER="), cmd, q); i += len; const QString hdr = q; QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("HEADER( "))) { // insert specified content of header from current message qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER("; QRegularExpressionMatch match; const int res = cmd.indexOf(QRegularExpression(QStringLiteral("^HEADER\\((.+)\\)")), 0, &match); if (res != 0) { // something wrong i += strlen("HEADER( "); } else { i += match.capturedLength(0); //length of HEADER( + ) const QString hdr = match.captured(1).trimmed(); QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCADDR"; i += strlen("OCCADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCNAME"; i += strlen("OCCNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCFNAME"; i += strlen("OCCFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCLNAME"; i += strlen("OCCLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOADDR"; i += strlen("OTOADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTONAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTONAME"; i += strlen("OTONAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOFNAME"; i += strlen("OTOFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLNAME"; i += strlen("OTOLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOLIST"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLIST"; i += strlen("OTOLIST"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTO"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTO"; i += strlen("OTO"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMADDR"; i += strlen("OFROMADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMNAME"; i += strlen("OFROMNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMFNAME"; i += strlen("OFROMFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainTextToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMLNAME"; i += strlen("OFROMLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFULLSUBJECT")) || cmd.startsWith(QLatin1String("OFULLSUBJ"))) { if (cmd.startsWith(QLatin1String("OFULLSUBJECT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJECT"; i += strlen("OFULLSUBJECT"); } else { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJ"; i += strlen("OFULLSUBJ"); } if (d->mOrigMsg) { const QString str = d->mOrigMsg->subject()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OMSGID"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OMSGID"; i += strlen("OMSGID"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->messageID()->asUnicodeString(); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("DATEEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATEEN"; i += strlen("DATEEN"); const QDateTime date = QDateTime::currentDateTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DATESHORT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATESHORT"; i += strlen("DATESHORT"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DATE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATE"; i += strlen("DATE"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DOW"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DOW"; i += strlen("DOW"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIMELONGEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONGEN"; i += strlen("TIMELONGEN"); const QDateTime date = QDateTime::currentDateTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIMELONG"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONG"; i += strlen("TIMELONG"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIME"; i += strlen("TIME"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("ODATEEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATEEN"; i += strlen("ODATEEN"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = QLocale::c().toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODATESHORT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATESHORT"; i += strlen("ODATESHORT"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODATE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATE"; i += strlen("ODATE"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODOW"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODOW"; i += strlen("ODOW"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIMELONGEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONGEN"; i += strlen("OTIMELONGEN"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIMELONG"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONG"; i += strlen("OTIMELONG"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIME"; i += strlen("OTIME"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainTextToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("BLANK"))) { // do nothing qCDebug(TEMPLATEPARSER_LOG) << "Command: BLANK"; i += strlen("BLANK"); } else if (cmd.startsWith(QLatin1String("NOP"))) { // do nothing qCDebug(TEMPLATEPARSER_LOG) << "Command: NOP"; i += strlen("NOP"); } else if (cmd.startsWith(QLatin1String("CLEAR"))) { // clear body buffer; not too useful yet qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEAR"; i += strlen("CLEAR"); plainBody.clear(); htmlBody.clear(); KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(0), "utf-8"); d->mMsg->setHeader(header); } else if (cmd.startsWith(QLatin1String("DEBUGOFF"))) { // turn off debug qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUGOFF"; i += strlen("DEBUGOFF"); d->mDebug = false; } else if (cmd.startsWith(QLatin1String("DEBUG"))) { // turn on debug qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUG"; i += strlen("DEBUG"); d->mDebug = true; } else if (cmd.startsWith(QLatin1String("CURSOR"))) { // turn on debug qCDebug(TEMPLATEPARSER_LOG) << "Command: CURSOR"; int oldI = i; i += strlen("CURSOR"); KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(plainBody.length()), "utf-8"); /* if template is: * FOOBAR * %CURSOR * * Make sure there is an empty line for the cursor otherwise it will be placed at the end of FOOBAR */ if (oldI > 0 && tmpl[ oldI - 1 ] == QLatin1Char('\n') && i == tmpl_len - 1) { plainBody.append(QLatin1Char('\n')); } d->mMsg->setHeader(header); d->mForceCursorPosition = true; //FIXME HTML part for header remaining } else if (cmd.startsWith(QLatin1String("SIGNATURE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: SIGNATURE"; i += strlen("SIGNATURE"); plainBody.append(getPlainSignature()); htmlBody.append(getHtmlSignature()); } else { // wrong command, do nothing plainBody.append(c); htmlBody.append(c); } } else if (dnl && (c == QLatin1Char('\n') || c == QLatin1Char('\r'))) { // skip if ((tmpl.size() > i + 1) && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) { // skip one more i += 1; } dnl = false; } else { plainBody.append(c); if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { htmlBody.append(QLatin1String("
")); htmlBody.append(c); if (tmpl.size() > i + 1 && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) { htmlBody.append(tmpl[i + 1]); plainBody.append(tmpl[i + 1]); i += 1; } } else { htmlBody.append(c); } } } // Clear the HTML body if FORCEDPLAIN has set ReplyAsPlain, OR if, // there is no use of FORCED command but a configure setting has ReplyUsingHtml disabled, // OR the original mail has no HTML part. const KMime::Content *content = d->mOrigMsg->mainBodyPart("text/html"); + qDebug() << " d->mReplyAsHtml" << d->mReplyAsHtml << " TemplateParserSettings::self()->replyUsingVisualFormat()" << TemplateParserSettings::self()->replyUsingVisualFormat(); if (d->mQuotes == ReplyAsPlain - || (d->mQuotes != ReplyAsHtml && !TemplateParserSettings::self()->replyUsingHtml()) - /*|| (!d->mReplyAsHtml && ... TODO add config settings for it ) */ + || (d->mQuotes != ReplyAsHtml) + || (!d->mReplyAsHtml && TemplateParserSettings::self()->replyUsingVisualFormat()) || (!content || !content->hasContent())) { htmlBody.clear(); } else { makeValidHtml(htmlBody); } if (d->mMode == NewMessage && plainBody.isEmpty() && !d->mExtractHtmlInfoResult.mPlainText.isEmpty()) { plainBody = d->mExtractHtmlInfoResult.mPlainText; } addProcessedBodyToMessage(plainBody, htmlBody); Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); } QString TemplateParserJob::getPlainSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return QString(); } KIdentityManagement::Signature signature = const_cast(identity).signature(); if (signature.type() == KIdentityManagement::Signature::Inlined && signature.isInlinedHtml()) { return signature.toPlainText(); } else { return signature.rawText(); } } // TODO If %SIGNATURE command is on, then override it with signature from // "KMail configure->General->identity->signature". // There should be no two signatures. QString TemplateParserJob::getHtmlSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return QString(); } KIdentityManagement::Signature signature = const_cast(identity).signature(); if (!signature.isInlinedHtml()) { signature = signature.rawText().toHtmlEscaped(); return signature.rawText().replace(QLatin1Char('\n'), QStringLiteral("
")); } return signature.rawText(); } void TemplateParserJob::addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const { MessageCore::ImageCollector ic; ic.collectImagesFrom(d->mOrigMsg.data()); // Now, delete the old content and set the new content, which // is either only the new text or the new text with some attachments. const auto parts = d->mMsg->contents(); for (KMime::Content *content : parts) { d->mMsg->removeContent(content, true /*delete*/); } // Set To and CC from the template if (!d->mTo.isEmpty()) { d->mMsg->to()->fromUnicodeString(d->mMsg->to()->asUnicodeString() + QLatin1Char(',') + d->mTo, "utf-8"); } if (!d->mCC.isEmpty()) { d->mMsg->cc()->fromUnicodeString(d->mMsg->cc()->asUnicodeString() + QLatin1Char(',') + d->mCC, "utf-8"); } d->mMsg->contentType()->clear(); // to get rid of old boundary //const QByteArray boundary = KMime::multiPartBoundary(); KMime::Content *const mainTextPart = htmlBody.isEmpty() ? createPlainPartContent(plainBody) : createMultipartAlternativeContent(plainBody, htmlBody); mainTextPart->assemble(); KMime::Content *textPart = mainTextPart; if (!ic.images().empty()) { textPart = createMultipartRelated(ic, mainTextPart); textPart->assemble(); } // If we have some attachments, create a multipart/mixed mail and // add the normal body as well as the attachments KMime::Content *mainPart = textPart; if (d->mMode == Forward) { auto attachments = d->mOrigMsg->attachments(); attachments += d->mOtp->nodeHelper()->attachmentsOfExtraContents(); if (!attachments.isEmpty()) { mainPart = createMultipartMixed(attachments, textPart); mainPart->assemble(); } } d->mMsg->setBody(mainPart->encodedBody()); d->mMsg->setHeader(mainPart->contentType()); d->mMsg->setHeader(mainPart->contentTransferEncoding()); d->mMsg->assemble(); d->mMsg->parse(); } KMime::Content *TemplateParserJob::createMultipartMixed(const QVector &attachments, KMime::Content *textPart) const { KMime::Content *mixedPart = new KMime::Content(d->mMsg.data()); const QByteArray boundary = KMime::multiPartBoundary(); mixedPart->contentType()->setMimeType("multipart/mixed"); mixedPart->contentType()->setBoundary(boundary); mixedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); mixedPart->addContent(textPart); int attachmentNumber = 1; for (KMime::Content *attachment : attachments) { mixedPart->addContent(attachment); // If the content type has no name or filename parameter, add one, since otherwise the name // would be empty in the attachment view of the composer, which looks confusing if (attachment->contentType(false)) { if (!attachment->contentType()->hasParameter(QStringLiteral("name")) && !attachment->contentType()->hasParameter(QStringLiteral("filename"))) { attachment->contentType()->setParameter( QStringLiteral("name"), i18nc("@item:intext", "Attachment %1", attachmentNumber)); } } ++attachmentNumber; } return mixedPart; } KMime::Content *TemplateParserJob::createMultipartRelated(const MessageCore::ImageCollector &ic, KMime::Content *mainTextPart) const { KMime::Content *relatedPart = new KMime::Content(d->mMsg.data()); const QByteArray boundary = KMime::multiPartBoundary(); relatedPart->contentType()->setMimeType("multipart/related"); relatedPart->contentType()->setBoundary(boundary); relatedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); relatedPart->addContent(mainTextPart); for (KMime::Content *image : ic.images()) { qCWarning(TEMPLATEPARSER_LOG) << "Adding" << image->contentID() << "as an embedded image"; relatedPart->addContent(image); } return relatedPart; } KMime::Content *TemplateParserJob::createPlainPartContent(const QString &plainBody) const { KMime::Content *textPart = new KMime::Content(d->mMsg.data()); textPart->contentType()->setMimeType("text/plain"); QTextCodec *charset = selectCharset(d->mCharsets, plainBody); textPart->contentType()->setCharset(charset->name()); textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); textPart->fromUnicodeString(plainBody); return textPart; } KMime::Content *TemplateParserJob::createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody) const { KMime::Content *multipartAlternative = new KMime::Content(d->mMsg.data()); multipartAlternative->contentType()->setMimeType("multipart/alternative"); const QByteArray boundary = KMime::multiPartBoundary(); multipartAlternative->contentType()->setBoundary(boundary); KMime::Content *textPart = createPlainPartContent(plainBody); multipartAlternative->addContent(textPart); KMime::Content *htmlPart = new KMime::Content(d->mMsg.data()); htmlPart->contentType()->setMimeType("text/html"); QTextCodec *charset = selectCharset(d->mCharsets, htmlBody); htmlPart->contentType()->setCharset(charset->name()); htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); htmlPart->fromUnicodeString(htmlBody); multipartAlternative->addContent(htmlPart); return multipartAlternative; } QString TemplateParserJob::findCustomTemplate(const QString &tmplName) { CTemplates t(tmplName); d->mTo = t.to(); d->mCC = t.cC(); const QString content = t.content(); if (!content.isEmpty()) { return content; } else { return findTemplate(); } } QString TemplateParserJob::findTemplate() { // qCDebug(TEMPLATEPARSER_LOG) << "Trying to find template for mode" << mode; QString tmpl; qCDebug(TEMPLATEPARSER_LOG) << "Folder identify found:" << d->mFolder; if (d->mFolder >= 0) { // only if a folder was found QString fid = QString::number(d->mFolder); Templates fconf(fid); if (fconf.useCustomTemplates()) { // does folder use custom templates? switch (d->mMode) { case NewMessage: tmpl = fconf.templateNewMessage(); break; case Reply: tmpl = fconf.templateReply(); break; case ReplyAll: tmpl = fconf.templateReplyAll(); break; case Forward: tmpl = fconf.templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = fconf.quoteString(); if (!tmpl.isEmpty()) { return tmpl; // use folder-specific template } } } if (!d->mIdentity) { // find identity message belongs to d->mIdentity = identityUoid(d->mMsg); if (!d->mIdentity && d->mOrigMsg) { d->mIdentity = identityUoid(d->mOrigMsg); } d->mIdentity = d->m_identityManager->identityForUoidOrDefault(d->mIdentity).uoid(); if (!d->mIdentity) { qCDebug(TEMPLATEPARSER_LOG) << "Oops! No identity for message"; } } qCDebug(TEMPLATEPARSER_LOG) << "Identity found:" << d->mIdentity; QString iid; if (d->mIdentity) { iid = TemplatesConfiguration::configIdString(d->mIdentity); // templates ID for that identity } else { iid = QStringLiteral("IDENTITY_NO_IDENTITY"); // templates ID for no identity } Templates iconf(iid); if (iconf.useCustomTemplates()) { // does identity use custom templates? switch (d->mMode) { case NewMessage: tmpl = iconf.templateNewMessage(); break; case Reply: tmpl = iconf.templateReply(); break; case ReplyAll: tmpl = iconf.templateReplyAll(); break; case Forward: tmpl = iconf.templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = iconf.quoteString(); if (!tmpl.isEmpty()) { return tmpl; // use identity-specific template } } switch (d->mMode) { // use the global template case NewMessage: tmpl = TemplateParserSettings::self()->templateNewMessage(); break; case Reply: tmpl = TemplateParserSettings::self()->templateReply(); break; case ReplyAll: tmpl = TemplateParserSettings::self()->templateReplyAll(); break; case Forward: tmpl = TemplateParserSettings::self()->templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = TemplateParserSettings::self()->quoteString(); return tmpl; } QString TemplateParserJob::pipe(const QString &cmd, const QString &buf) { KProcess process; bool success; process.setOutputChannelMode(KProcess::SeparateChannels); process.setShellCommand(cmd); process.start(); if (process.waitForStarted(pipeTimeout())) { bool finished = false; if (!buf.isEmpty()) { process.write(buf.toLatin1()); } if (buf.isEmpty() || process.waitForBytesWritten(pipeTimeout())) { if (!buf.isEmpty()) { process.closeWriteChannel(); } if (process.waitForFinished(pipeTimeout())) { success = (process.exitStatus() == QProcess::NormalExit); finished = true; } else { finished = false; success = false; } } else { success = false; finished = false; } // The process has started, but did not finish in time. Kill it. if (!finished) { process.kill(); } } else { success = false; } if (!success && d->mDebug) { KMessageBox::error( nullptr, xi18nc("@info", "Pipe command %1 failed.", cmd)); } if (success) { return QTextCodec::codecForLocale()->toUnicode(process.readAllStandardOutput()); } else { return QString(); } } void TemplateParserJob::setWordWrap(bool wrap, int wrapColWidth) { d->mWrap = wrap; d->mColWrap = wrapColWidth; } QString TemplateParserJob::plainMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) const { if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) { return d->mSelection; } if (!d->mOrigMsg) { return QString(); } const auto mp = toplevelTextNode(d->mOtp->parsedPart()); QString result = mp->plaintextContent(); if (result.isEmpty()) { result = d->mExtractHtmlInfoResult.mPlainText; } if (aStripSignature) { result = MessageCore::StringUtil::stripSignature(result); } return result; } QString TemplateParserJob::htmlMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) { if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) { //TODO implement d->mSelection for HTML return d->mSelection; } d->mHeadElement = d->mExtractHtmlInfoResult.mHeaderElement; const QString bodyElement = d->mExtractHtmlInfoResult.mBodyElement; if (!bodyElement.isEmpty()) { if (aStripSignature) { //FIXME strip signature works partially for HTML mails return MessageCore::StringUtil::stripSignature(bodyElement); } return bodyElement; } if (aStripSignature) { //FIXME strip signature works partially for HTML mails return MessageCore::StringUtil::stripSignature(d->mExtractHtmlInfoResult.mHtmlElement); } return d->mExtractHtmlInfoResult.mHtmlElement; } QString TemplateParserJob::quotedPlainText(const QString &selection) const { QString content = TemplateParser::Util::removeSpaceAtBegin(selection); const QString indentStr = MessageCore::StringUtil::formatQuotePrefix(d->mQuoteString, d->mOrigMsg->from()->displayString()); if (TemplateParserSettings::self()->smartQuote() && d->mWrap) { content = MessageCore::StringUtil::smartQuote(content, d->mColWrap - indentStr.length()); } content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr); content.prepend(indentStr); content += QLatin1Char('\n'); return content; } QString TemplateParserJob::quotedHtmlText(const QString &selection) const { QString content = selection; //TODO 1) look for all the variations of
and remove the blank lines //2) implement vertical bar for quoted HTML mail. //3) After vertical bar is implemented, If a user wants to edit quoted message, // then the
tags below should open and close as when required. //Add blockquote tag, so that quoted message can be differentiated from normal message content = QLatin1String("
") + content + QLatin1String("
"); return content; } uint TemplateParserJob::identityUoid(const KMime::Message::Ptr &msg) const { QString idString; if (auto hrd = msg->headerByType("X-KMail-Identity")) { idString = hrd->asUnicodeString().trimmed(); } bool ok = false; unsigned int id = idString.toUInt(&ok); if (!ok || id == 0) { id = d->m_identityManager->identityForAddress( msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid(); } return id; } bool TemplateParserJob::isHtmlSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return false; } const KIdentityManagement::Signature signature = const_cast(identity).signature(); return signature.isInlinedHtml(); } QString TemplateParserJob::plainTextToHtml(const QString &body) { QString str = body; str = str.toHtmlEscaped(); str.replace(QLatin1Char('\n'), QStringLiteral("
\n")); return str; } void TemplateParserJob::makeValidHtml(QString &body) { QRegExp regEx; regEx.setMinimal(true); regEx.setPattern(QStringLiteral("")); if (!body.isEmpty() && !body.contains(regEx)) { regEx.setPattern(QStringLiteral("")); if (!body.contains(regEx)) { body = QLatin1String("") + body + QLatin1String("
"); } regEx.setPattern(QStringLiteral("")); if (!body.contains(regEx)) { body = QLatin1String("") + d->mHeadElement + QLatin1String("") + body; } body = QLatin1String("") + body + QLatin1String(""); } }