diff --git a/messagecomposer/autotests/messagefactoryngtest.cpp b/messagecomposer/autotests/messagefactoryngtest.cpp index ab4f4a3a..45267f9f 100644 --- a/messagecomposer/autotests/messagefactoryngtest.cpp +++ b/messagecomposer/autotests/messagefactoryngtest.cpp @@ -1,975 +1,935 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2019 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagefactoryngtest.h" #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include "setupenv.h" #include #include #include #include #include #include #include #include #include #include #include #include "globalsettings_templateparser.h" #include #include #include #include #include #include using namespace MessageComposer; 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; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: foo1 \n" "X-KMail-Identity: %1\n" "Date: %2\n" - "Cc: blo , bli , blu , bly , Bla \n" - "To: Bla \n" + "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; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" - "Cc: blo , bli , blu , bly \n" - "To: Bla \n" + "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; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender"); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" - "Cc: blo , bli , blu , bly \n" - "To: Bla \n" + "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; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); QString replyTo = reply.msg->inReplyTo()->asUnicodeString(); QString reference = reply.msg->references()->asUnicodeString(); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "X-KMail-Transport: 0\n" "To: me@me.me\n" "References: %3\n" "In-Reply-To: %2\n" "Subject: Re: Test Email Subject\n" "X-KMail-CursorPos: %5\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: reply\n\n" "%4") .arg(dateStr).arg(replyTo).arg(reference).arg(replyStr).arg(replyStr.length() - 1); QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1()); } void MessageFactoryTest::testCreateReplyAllWithMultiEmailsAsync() { KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); factory.setReplyStrategy(ReplyAll); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QString replyTo = reply.msg->inReplyTo()->asUnicodeString(); QString reference = reply.msg->references()->asUnicodeString(); QString dateStr = reply.msg->date()->asUnicodeString(); QString ba = QString::fromLatin1("From: another \n" "Date: %1\n" "X-KMail-Transport: 0\n" - "Cc: you@you.you, you2@you.you, cc@cc.cc, cc2@cc.cc\n" - "To: me@me.me\n" + "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; - //qDebug() << reply.msg->body(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n"))); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject")); QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1()); } void MessageFactoryTest::testCreateReplyHtmlAsync() { KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("html_utf8_encoded.mbox")); //qDebug() << "html message:" << msg->encodedContent(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.setIdentityManager(mIdentMan); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; - //qDebug() << "html reply" << reply.msg->encodedContent(); QDateTime date = msg->date()->dateTime().toLocalTime(); QString datetime = QLocale().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> encoded?\n\n"))); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please")); QCOMPARE(reply.msg->contents().count(), 2); QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1()); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(false); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 2); reply = spy.at(1).at(0).value(); reply.replyAll = true; datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("text/plain")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please")); QCOMPARE(reply.msg->contents().count(), 0); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); } void MessageFactoryTest::testCreateReplyUTF16Base64Async() { KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("plain_utf16.mbox")); TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true); -// qDebug() << "plain base64 msg message:" << msg->encodedContent(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone); factory.setIdentityManager(mIdentMan); factory.createReplyAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value(); reply.replyAll = true; -// qDebug() << "html reply" << reply.msg->encodedContent(); QDateTime date = msg->date()->dateTime().toLocalTime(); QString datetime = QLocale().toString(date.date(), QLocale::LongFormat); datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat); QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> quote me please.\n\n"))); QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative")); QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: asking for reply")); QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1()); } void MessageFactoryTest::testCreateForwardMultiEmailsAsync() { KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone); factory.createForwardAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); KMime::Message::Ptr fw = spy.at(0).at(0).value(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat); QString fwdMsg = QString::fromLatin1( "From: another \n" "Date: %2\n" "X-KMail-Transport: 0\n" "MIME-Version: 1.0\n" "Subject: Fwd: Test Email Subject\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: forward\n" "\n" "---------- Forwarded Message ----------\n" "\n" "Subject: Test Email Subject\n" "Date: %1\n" "From: me@me.me\n" "To: you@you.you, you2@you.you\n" "CC: cc@cc.cc, cc2@cc.cc\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way.\n" "-----------------------------------------"); fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString()); -// qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1(); QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject")); QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1()); } void MessageFactoryTest::testCreateForwardAsync() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone); factory.createForwardAsync(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); KMime::Message::Ptr fw = spy.at(0).at(0).value(); QDateTime date = msg->date()->dateTime(); QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat); datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat); QString fwdMsg = QString::fromLatin1( "From: another \n" "Date: %2\n" "X-KMail-Transport: 0\n" "MIME-Version: 1.0\n" "Subject: Fwd: Test Email Subject\n" "Content-Type: text/plain; charset=\"US-ASCII\"\n" "Content-Transfer-Encoding: 8Bit\n" "X-KMail-Link-Message: 0\n" "X-KMail-Link-Type: forward\n" "\n" "---------- Forwarded Message ----------\n" "\n" "Subject: Test Email Subject\n" "Date: %1\n" "From: me@me.me\n" "To: you@you.you\n" "CC: cc@cc.cc\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way.\n" "-----------------------------------------"); fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString()); -// qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1(); QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject")); QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1()); } void MessageFactoryTest::testCreateRedirectToAndCCAndBCC() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com"); QString redirectBcc = QStringLiteral("redirbcc@redirbcc.com, redirbcc2@redirbcc.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc, redirectBcc); QString datetime = rdir->date()->asUnicodeString(); -// qDebug() << rdir->encodedContent(); - QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); - //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n" "Resent-Bcc: redirbcc@redirbcc.com, redirbcc2@redirbcc.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); -// qDebug() << baseline.toLatin1(); -// qDebug() << "instead:" << rdir->encodedContent(); - -// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateRedirectToAndCC() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc); QString datetime = rdir->date()->asUnicodeString(); -// qDebug() << rdir->encodedContent(); - QString msgId = MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), QString()); QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); -// qDebug() << baseline.toLatin1(); -// qDebug() << "instead:" << rdir->encodedContent(); - -// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateRedirect() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); QString redirectTo = QStringLiteral("redir@redir.com"); KMime::Message::Ptr rdir = factory.createRedirect(redirectTo); QString datetime = rdir->date()->asUnicodeString(); -// qDebug() << rdir->encodedContent(); - QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); - //qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")"; QString baseline = QString::fromLatin1("From: me@me.me\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %1\n" "X-KMail-Transport: 0\n" "Message-ID: %2\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "Resent-Message-ID: %3\n" "Resent-Date: %4\n" "Resent-From: %5\n" "To: you@you.you\n" "Resent-To: redir@redir.com\n" "X-KMail-Redirect-From: me@me.me (by way of another )\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another ")); -// qDebug() << baseline.toLatin1(); -// qDebug() << "instead:" << rdir->encodedContent(); - -// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateResend() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); KMime::Message::Ptr rdir = factory.createResend(); QString datetime = rdir->date()->asUnicodeString(); -// qDebug() << msg->encodedContent(); - QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)")); rx.indexIn(QString::fromLatin1(rdir->head())); QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)")); rxmessageid.indexIn(QString::fromLatin1(rdir->head())); QString baseline = QString::fromLatin1("From: me@me.me\n" "To: %1\n" "Cc: cc@cc.cc\n" "Bcc: bcc@bcc.bcc\n" "Subject: Test Email Subject\n" "Date: %2\n" "X-KMail-Transport: 0\n" "Message-ID: %3\n" "Disposition-Notification-To: me@me.me\n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 7Bit\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "\n" "All happy families are alike; each unhappy family is unhappy in its own way."); baseline = baseline.arg(msg->to()->asUnicodeString()).arg(datetime).arg(rxmessageid.cap(1)); - //qDebug() << baseline.toLatin1(); - //qDebug() << "instead:" << rdir->encodedContent(); - -// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" ); QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject")); QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1()); } void MessageFactoryTest::testCreateMDN() { KMime::Message::Ptr msg = createPlainTestMessage(); MessageFactoryNG factory(msg, 0); factory.setIdentityManager(mIdentMan); KMime::Message::Ptr mdn = factory.createMDN(KMime::MDN::AutomaticAction, KMime::MDN::Displayed, KMime::MDN::SentAutomatically); QVERIFY(mdn.data()); - //qDebug() << "mdn" << mdn->encodedContent(); QString mdnContent = QString::fromLatin1("The message sent on %1 to %2 with subject \"%3\" has been displayed. " "This is no guarantee that the message has been read or understood."); mdnContent = mdnContent.arg(KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch())) .arg(msg->to()->asUnicodeString()).arg(msg->subject()->asUnicodeString()); - //qDebug() << "comparing with:" << mdnContent; - QCOMPARE_OR_DIFF(Util::findTypeInMessage(mdn.data(), "multipart", "report")->contents().at(0)->body(), mdnContent.toLatin1()); } KMime::Message::Ptr MessageFactoryTest::createPlainTestMessage() { Composer *composer = new Composer; composer->globalPart()->setFallbackCharsetEnabled(true); composer->infoPart()->setFrom(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/autotests/replystrategytest.cpp b/messagecomposer/autotests/replystrategytest.cpp index 445253dc..1c003539 100644 --- a/messagecomposer/autotests/replystrategytest.cpp +++ b/messagecomposer/autotests/replystrategytest.cpp @@ -1,377 +1,420 @@ /* Copyright (C) 2019 Glen Ditchfield 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 "replystrategytest.h" #include #include #include #include #include #include #include #include #include #include const auto defaultAddress {QStringLiteral("default@example.org")}; const auto nondefaultAddress {QStringLiteral("nondefault@example.com")}; const auto friend1Address {QStringLiteral("friend1@example.net")}; const auto friend2Address {QStringLiteral("friend2@example.net")}; const auto replyAddress {QStringLiteral("reply@example.com")}; const auto followupAddress {QStringLiteral("followup@example.org")}; const auto listAddress {QStringLiteral("list@example.com")}; const auto mailReplyAddress {QStringLiteral("mailreply@example.com")}; const QStringList nobody {}; static inline const QStringList only(const QString &address) { return QStringList {address}; } static inline const QStringList both(const QString &address1, const QString &address2) { return QStringList {address1, address2}; } using namespace MessageComposer; static KMime::Message::Ptr basicMessage(const QString &fromAddress, const QStringList &toAddresses) { Composer composer; composer.infoPart()->setFrom(fromAddress); composer.infoPart()->setTo(toAddresses); composer.infoPart()->setSubject(QStringLiteral("Test Email Subject")); composer.textPart()->setWrappedPlainText(QStringLiteral("Test email body.")); composer.exec(); return composer.resultMessages().first(); } #define COMPARE_ADDRESSES(actual, expected) \ if (!compareAddresses(actual, expected)) { \ QFAIL(qPrintable(QStringLiteral("%1 is \"%2\"") \ .arg(QString::fromLatin1(#actual), actual->displayString()))); \ return; \ } template bool compareAddresses(const T *actual, const QStringList &expected) { auto addresses {actual->addresses()}; if (addresses.length() != expected.length()) { return false; } for (const auto &e : expected) { if (!addresses.contains(e.toLatin1())) { return false; } } return true; } ReplyStrategyTest::ReplyStrategyTest(QObject *parent) : QObject(parent) { QStandardPaths::setTestModeEnabled(true); } ReplyStrategyTest::~ReplyStrategyTest() { // Workaround QTestLib not flushing deleteLater()s on exit, which // leads to WebEngine asserts (view not deleted) QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); } void ReplyStrategyTest::initTestCase() { QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QDir::separator() + QStringLiteral("emailidentities")); QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QDir::separator() + QStringLiteral("emaildefaults")); mIdentityManager = new KIdentityManagement::IdentityManager; auto homeIdentity = mIdentityManager->newFromExisting(KIdentityManagement::Identity { QStringLiteral("Home Identity"), QStringLiteral("Full Home Name"), defaultAddress}); QVERIFY(mIdentityManager->setAsDefault(homeIdentity.uoid())); auto workIdentity = mIdentityManager->newFromExisting(KIdentityManagement::Identity { QStringLiteral("Work Identity"), QStringLiteral("Full Work Name"), nondefaultAddress}); mIdentityManager->commit(); } void ReplyStrategyTest::cleanupTestCase() { delete mIdentityManager; } KMime::Message::Ptr ReplyStrategyTest::makeReply(const KMime::Message::Ptr &original, const ReplyStrategy strategy) { MessageFactoryNG factory {original, 0}; factory.setReplyStrategy(strategy); factory.setIdentityManager(mIdentityManager); QSignalSpy spy {&factory, &MessageFactoryNG::createReplyDone}; factory.createReplyAsync(); KMime::Message::Ptr result {nullptr}; [&]{ QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); result = spy.at(0).at(0).value().msg; } (); return result; } void ReplyStrategyTest::testReply_data() { QTest::addColumn("oFrom"); // Original message's From address. QTest::addColumn("oTo"); // Original message's To addresses. QTest::addColumn("oCc"); // Original message's CC addresses. QTest::addColumn("oRT"); // Original message's Reply-To addresses. QTest::addColumn("oMFT"); // Original message's Mail-Followup-To addresses. QTest::addColumn("oLP"); // Original message's List-Post address. QTest::addColumn("oMRT"); // Original message's Mail-Reply-To addresses. QTest::addColumn("strategy"); // ReplyStrategy (passed as an int). QTest::addColumn("rFrom"); // Reply's expected From address. QTest::addColumn("rTo"); // Reply's expected To addresses. QTest::addColumn("rCc"); // Reply's expected CC addresses. // Smart Replies // ------------- // Smart Reply does not set CC headers. (Compare ReplyAll.) // ReplySmart uses Mail-Reply-To, Reply-To, or From (in that order) // for the original's author's address, if List-Post is absent. QTest::newRow("ReplySmart, from someone to default identity") << friend1Address << only(defaultAddress) << only(friend2Address) << nobody << nobody << QString() << nobody << (int)ReplySmart << defaultAddress << only(friend1Address) << nobody; QTest::newRow("ReplySmart, from someone to non-default identity") << friend1Address << both(friend2Address, nondefaultAddress) << only(defaultAddress) << nobody << nobody << QString() << nobody << (int)ReplySmart << nondefaultAddress << only(friend1Address) << nobody; QTest::newRow("ReplySmart, from someone with Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << both(replyAddress, friend2Address) << nobody << QString() << nobody << (int)ReplySmart << defaultAddress << both(friend2Address, replyAddress) << nobody; QTest::newRow("ReplySmart, from someone with Mail-Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress) << (int)ReplySmart << defaultAddress << only(mailReplyAddress) << nobody; // If the original message was _from_ the user _to_ another person (the // reverse of the usual direction), a smart reply goes to the other person. // Therefore Mail-Reply-To and Reply-To are ignored. // The reply is assumed to add to the original message. QTest::newRow("ReplySmart, from default identity to someone") << defaultAddress << only(friend1Address) << only(friend2Address) << nobody << nobody << QString() << nobody << (int)ReplySmart << defaultAddress << only(friend1Address) << nobody; QTest::newRow("ReplySmart, from default identity with Reply-To to someone") << defaultAddress << only(friend1Address) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress) << (int)ReplySmart << defaultAddress << only(friend1Address) << nobody; // If the original message was from one of the user's identities to another // identity (i.e., between two of the user's mail accounts), a smart reply // goes back to the sending identity. QTest::newRow("ReplySmart, between identities") << defaultAddress << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << QString() << nobody << (int)ReplySmart << nondefaultAddress << only(defaultAddress) << nobody; // If the original message appears to be from a mailing list, smart replies // go to the Mail-Followup-To, Reply-To, or List-Post addresses, in that // order of preference. QTest::newRow("ReplySmart, from list with Mail-Followup-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress << nobody << (int)ReplySmart << defaultAddress << only(followupAddress) << nobody; QTest::newRow("ReplySmart, from list with Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << listAddress << nobody << (int)ReplySmart << defaultAddress << only(replyAddress) << nobody; QTest::newRow("ReplySmart, from list with List-Post") << friend1Address << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << listAddress << only(mailReplyAddress) << (int)ReplySmart << nondefaultAddress << only(listAddress) << nobody; // Replies to Mailing Lists // ------------------------ // If the original message has a Mail-Followup-To header, replies to the list // go to the followup address, in preference to List-Post and Reply-To. // Cc and Mail-Reply-To are ignored. QTest::newRow("ReplyList, from list with Mail-Followup-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress << only(mailReplyAddress) << (int)ReplyList << defaultAddress << only(followupAddress) << nobody; // If the original message has a List-Post header, replies to the list // go to that address, in preference to Reply-To. QTest::newRow("ReplyList, from list with List-Post") << friend1Address << only(defaultAddress) << nobody << only(replyAddress) << nobody << listAddress << nobody << (int)ReplyList << defaultAddress << only(listAddress) << nobody; // If the original message has just a Reply-To header, assume the list // [munges Reply-To](https://www.gnu.org/software/mailman/mailman-admin/node11.html) /// and send the reply to that address. QTest::newRow("ReplyList, from list with Reply-To") << friend1Address << only(defaultAddress) << nobody << only(replyAddress) << nobody << QString() << nobody << (int)ReplyList << defaultAddress << only(replyAddress) << nobody; // If the original message has neither Mail-Followup-To, List-Post, nor // Reply-To headers, replies to the list do not choose a To address. QTest::newRow("ReplyList, from list with no headers") << friend1Address << only(defaultAddress) << nobody << nobody << nobody << QString() << nobody << (int)ReplyList << defaultAddress << nobody << nobody; // Replies to All // -------------- // ReplyAll adds CC addresses to the reply for the original's recipients, // except for the user's identities. QTest::newRow("ReplyAll, with Cc in original") << friend1Address << only(defaultAddress) << both(friend2Address, nondefaultAddress) << nobody << nobody << QString() << nobody << (int)ReplyAll << defaultAddress << only(friend1Address) << only(friend2Address); QTest::newRow("ReplyAll, with multiple To addresses in original") << friend1Address << both(friend2Address, nondefaultAddress) << only(defaultAddress) << nobody << nobody << QString() << nobody - << (int)ReplyAll << nondefaultAddress << only(friend1Address) << only(friend2Address); + << (int)ReplyAll << nondefaultAddress << both(friend1Address, friend2Address) << nobody; QTest::newRow("ReplyAll, with Reply-To in original") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << nobody << (int)ReplyAll << defaultAddress << only(replyAddress) << only(friend2Address); + QTest::newRow("ReplyAll, with Mail-Reply-To in original") + << friend1Address << only(defaultAddress) << only(friend2Address) + << only(replyAddress) << nobody << QString() << only(mailReplyAddress) + << (int)ReplyAll << defaultAddress << only(mailReplyAddress) << only(friend2Address); + + // If the original message was _from_ the user _to_ another person (the + // reverse of the usual direction), reply to all goes to the other person. + // Therefore Mail-Reply-To and Reply-To are ignored. + // The reply is assumed to add to the original message. + QTest::newRow("ReplyAll, from default identity to someone") + << defaultAddress << only(friend1Address) << only(friend2Address) + << only(replyAddress) << nobody << QString() << only(mailReplyAddress) + << (int)ReplyAll << defaultAddress << only(friend1Address) << only(friend2Address); + + // If the original message was from one of the user's identities to another + // identity (i.e., between two of the user's mail accounts), reply to all + // goes back to the sending identity. + QTest::newRow("ReplyAll, between identities") + << defaultAddress << only(nondefaultAddress) << only(friend2Address) + << nobody << nobody << QString() << nobody + << (int)ReplyAll << nondefaultAddress << only(defaultAddress) << only(friend2Address); // If the original passed through a mailing list, ReplyAll replies to the - // list, preferring Mail-Followup-To over List-Post as the list address. + // list. // It CCs the author, using Mail-Reply-To, Reply-To, or From (in that order). QTest::newRow("ReplyAll, from list with List-Post") << friend1Address << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << listAddress << nobody << (int)ReplyAll << nondefaultAddress << only(listAddress) << both(friend1Address, friend2Address); + QTest::newRow("ReplyAll, from list with Reply-To") + << friend1Address << only(defaultAddress) << only(friend2Address) + << only(replyAddress) << nobody << listAddress << nobody + << (int)ReplyAll << defaultAddress << only(listAddress) << both(replyAddress, friend2Address); + QTest::newRow("ReplyAll, from list with Mail-Reply-To") + << friend1Address << only(defaultAddress) << only(friend2Address) + << only(replyAddress) << nobody << listAddress << only(mailReplyAddress) + << (int)ReplyAll << defaultAddress << only(listAddress) << both(mailReplyAddress, friend2Address); // If Reply-To is the same as List-Post, ReplyAll ignores it and uses - // From, because the mailing list munged Reply-To. + // From for the author's address, because the mailing list munged Reply-To. QTest::newRow("ReplyAll, from list that munges Reply-To") << friend1Address << only(defaultAddress) << nobody << only(listAddress) << nobody << listAddress << nobody << (int)ReplyAll << defaultAddress << only(listAddress) << only(friend1Address); + // If Reply-To contains List-Post, ReplyAll uses the other reply + // addresses, because the mailing list didn't completely munge Reply-To. + QTest::newRow("ReplyAll, from list that lightly munges Reply-To") + << friend1Address << only(defaultAddress) << nobody + << both(replyAddress, listAddress) << nobody << listAddress << nobody + << (int)ReplyAll << defaultAddress << only(listAddress) << only(replyAddress); + + // If Mail-Followup-To header is present, use it for To and ignore other + // headers. Cc is empty. + QTest::newRow("ReplyAll, from list with Reply-To and Mail-Followup-To") + << friend1Address << only(defaultAddress) << only(friend2Address) + << only(replyAddress) << only(followupAddress) << listAddress << only(mailReplyAddress) + << (int)ReplyAll << defaultAddress << only(followupAddress) << nobody; + // Reply to Author // --------------- // ReplyAuthor ignores Cc, and replies to the Mail-Reply-To, Reply-To, or // From addresses, in that order of preference, if List-Post is absent. QTest::newRow("ReplyAuthor, no special headers") << friend1Address << only(defaultAddress) << only(friend2Address) << nobody << nobody << QString() << nobody << (int)ReplyAuthor << defaultAddress << only(friend1Address) << nobody; QTest::newRow("ReplyAuthor, from someone with Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << nobody << (int)ReplyAuthor << defaultAddress << only(replyAddress) << nobody; QTest::newRow("ReplyAuthor, from someone with Mail-Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress) << (int)ReplyAuthor << defaultAddress << only(mailReplyAddress) << nobody; // If Reply-To is the same as List-Post, ReplyAuthor ignores it and uses // From, because the mailing list munged Reply-To. QTest::newRow("ReplyAuthor, from list that munges Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(listAddress) << nobody << listAddress << nobody << (int)ReplyAuthor << defaultAddress << only(friend1Address) << nobody; // If Reply-To contains List-Post, ReplyAuthor uses the other reply // addresses, because the mailing list didn't completely munge Reply-To. QTest::newRow("ReplyAuthor, from list that lightly munges Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << both(listAddress, replyAddress) << nobody << listAddress << nobody << (int)ReplyAuthor << defaultAddress << only(replyAddress) << nobody; // Reply to None // ------------- // ReplyNone ignores all possible headers and does not choose a To address. QTest::newRow("ReplyNone") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress << only(mailReplyAddress) << (int)ReplyNone << defaultAddress << nobody << nobody; } void ReplyStrategyTest::testReply() { QFETCH(const QString, oFrom); QFETCH(const QStringList, oTo); QFETCH(const QStringList, oCc); QFETCH(const QStringList, oRT); QFETCH(const QStringList, oMFT); QFETCH(const QString, oLP); QFETCH(const QStringList, oMRT); QFETCH(const int, strategy); QFETCH(const QString, rFrom); QFETCH(const QStringList, rTo); QFETCH(const QStringList, rCc); auto original {basicMessage(oFrom, oTo)}; if (!oCc.isEmpty()) { auto cc {new KMime::Headers::Cc}; for (const auto &a : oCc) { cc->addAddress(a.toLatin1()); } original->setHeader(cc); } if (!oRT.isEmpty()) { auto replyTo {new KMime::Headers::ReplyTo}; for (const auto &a : oRT) { replyTo->addAddress(a.toLatin1()); } original->setHeader(replyTo); } if (!oMFT.isEmpty()) { auto mailFollowupTo = new KMime::Headers::Generic("Mail-Followup-To"); mailFollowupTo->from7BitString(oMFT.join(QLatin1Char(',')).toLatin1()); original->setHeader(mailFollowupTo); } if (!oLP.isEmpty()) { auto listPost = new KMime::Headers::Generic("List-Post"); listPost->from7BitString(""); original->setHeader(listPost); } if (!oMRT.isEmpty()) { auto mailReplyTo = new KMime::Headers::Generic("Mail-Reply-To"); mailReplyTo->from7BitString(oMRT.join(QLatin1Char(',')).toLatin1()); original->setHeader(mailReplyTo); } if (auto reply = makeReply(original, (ReplyStrategy)strategy)) { COMPARE_ADDRESSES(reply->from(), only(rFrom)); COMPARE_ADDRESSES(reply->to(), rTo); COMPARE_ADDRESSES(reply->cc(), rCc); } } QTEST_MAIN(ReplyStrategyTest) diff --git a/messagecomposer/src/helper/messagefactoryng.cpp b/messagecomposer/src/helper/messagefactoryng.cpp index 1bb2305a..1cea5642 100644 --- a/messagecomposer/src/helper/messagefactoryng.cpp +++ b/messagecomposer/src/helper/messagefactoryng.cpp @@ -1,1071 +1,1014 @@ /* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2019 Laurent Montel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "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) , m_identityManager(nullptr) , m_origMsg(origMsg) , m_folderId(0) , m_parentFolderId(0) , m_collection(col) , m_replyStrategy(MessageComposer::ReplySmart) , m_quote(true) , m_id(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, 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, m_id, Akonadi::MessageStatus::statusReplied()); if (m_parentFolderId > 0) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc"); header->fromUnicodeString(QString::number(m_parentFolderId), "utf-8"); msg->setHeader(header); } if (auto hrd = m_origMsg->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(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity); replyToList = m_origMsg->replyTo()->mailboxes(); msg->contentType()->setCharset("utf-8"); if (auto hdr = m_origMsg->headerByType("List-Post")) { const QString hdrListPost = hdr->asUnicodeString(); if (hdrListPost.contains(QLatin1String("mailto:"), Qt::CaseInsensitive)) { QRegExp rx(QStringLiteral("]+)@([^>]+)>"), Qt::CaseInsensitive); if (rx.indexIn(hdrListPost, 0) != -1) { // matched KMime::Types::Mailbox mailbox; mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2)); m_mailingListAddresses << mailbox; } } } switch (m_replyStrategy) { case MessageComposer::ReplySmart: { if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) { toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else if (!m_mailingListAddresses.isEmpty()) { if (replyToList.isEmpty()) { toList = (KMime::Types::Mailbox::List() << m_mailingListAddresses.at(0)); } else { toList = replyToList; } } else { // Doesn't seem to be a mailing list. auto originalFromList = m_origMsg->from()->mailboxes(); auto originalToList = m_origMsg->to()->mailboxes(); if (m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) && !m_identityManager->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(m_origMsg, m_mailingListAddresses); } replyAll = false; } // strip all my addresses from the list of recipients const KMime::Types::Mailbox::List recipients = toList; toList = stripMyAddressesFromAddressList(recipients, m_identityManager); // ... 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 = m_origMsg->headerByType("Mail-Followup-To")) { KMime::Types::Mailbox mailbox; mailbox.from7BitString(hdr->as7BitString(false)); toList << mailbox; } else if (!m_mailingListAddresses.isEmpty()) { toList << m_mailingListAddresses[ 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, m_identityManager); break; } case MessageComposer::ReplyAll: - { - KMime::Types::Mailbox::List recipients; - KMime::Types::Mailbox::List ccRecipients; - - QString sender; - if (auto hrd = m_origMsg->sender(false)) { - sender = hrd->asUnicodeString(); - } - // add addresses from the Reply-To header to the list of recipients - if (!replyToList.isEmpty()) { - recipients = replyToList; - - // strip all possible mailing list addresses from the list of Reply-To addresses - for (const KMime::Types::Mailbox &mailbox : qAsConst(m_mailingListAddresses)) { - foreach (const KMime::Types::Mailbox &recipient, recipients) { //Don't use for(...:...) - if (mailbox == recipient) { - recipients.removeAll(recipient); - } - } - } - } - bool stripMyAddresses = true; - if (!m_mailingListAddresses.isEmpty()) { - // this is a mailing list message - if (recipients.isEmpty() && !m_origMsg->from()->asUnicodeString().isEmpty()) { - // The sender didn't set a Reply-to address, so we add the From - // address to the list of CC recipients. - ccRecipients += m_origMsg->from()->mailboxes(); - qCDebug(MESSAGECOMPOSER_LOG) << "Added" << m_origMsg->from()->asUnicodeString() << "to the list of CC recipients"; - } - - // if it is a mailing list, add the posting address - recipients.prepend(m_mailingListAddresses[ 0 ]); + if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) { + toList = KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else { - const QString fromAddress = m_origMsg->from()->asUnicodeString(); - if (!fromAddress.isEmpty()) { - if (!sender.isEmpty() && m_identityManager->thatIsMe(fromAddress)) { - // strip all my addresses from the list of recipients - toList = recipients; - toList += m_origMsg->from()->mailboxes(); - stripMyAddresses = false; - } else { - // this is a normal message - if (recipients.isEmpty()) { - // in case of replying to a normal message only then add the From - // address to the list of recipients if there was no Reply-to address - recipients += m_origMsg->from()->mailboxes(); - qCDebug(MESSAGECOMPOSER_LOG) << "Added" << m_origMsg->from()->asUnicodeString() << "to the list of recipients"; + auto ccList = stripMyAddressesFromAddressList(m_origMsg->cc(false)->mailboxes(), m_identityManager); + + if (!m_mailingListAddresses.isEmpty()) { + toList = stripMyAddressesFromAddressList(m_origMsg->to()->mailboxes(), m_identityManager); + bool addMailingList = true; + for (const KMime::Types::Mailbox &m : m_mailingListAddresses) { + if (toList.contains(m)) { + addMailingList = false; + break; } } - } - } - if (stripMyAddresses) { - // strip all my addresses from the list of recipients - toList = stripMyAddressesFromAddressList(recipients, m_identityManager); - } - - // merge To header and CC header into a list of CC recipients - if (!m_origMsg->cc()->asUnicodeString().isEmpty() || !m_origMsg->to()->asUnicodeString().isEmpty()) { - KMime::Types::Mailbox::List list; - if (!m_origMsg->to()->asUnicodeString().isEmpty()) { - list += m_origMsg->to()->mailboxes(); - } - if (!m_origMsg->cc()->asUnicodeString().isEmpty()) { - list += m_origMsg->cc()->mailboxes(); - } - - for (const KMime::Types::Mailbox &mailbox : qAsConst(list)) { - if (!recipients.contains(mailbox) - && !ccRecipients.contains(mailbox)) { - ccRecipients += mailbox; - qCDebug(MESSAGECOMPOSER_LOG) << "Added" << mailbox.prettyAddress() << "to the list of CC recipients"; + if (addMailingList) { + toList += m_mailingListAddresses.front(); } - } - } - - if (!ccRecipients.isEmpty()) { - // strip all my addresses from the list of CC recipients - if (stripMyAddresses) { - ccRecipients = stripMyAddressesFromAddressList(ccRecipients, m_identityManager); - } - // in case of a reply to self, toList might be empty. if that's the case - // then propagate a cc recipient to To: (if there is any). - if (toList.isEmpty() && !ccRecipients.isEmpty()) { - toList << ccRecipients.at(0); - ccRecipients.pop_front(); + ccList += authorMailboxes(m_origMsg, m_mailingListAddresses); + } else { + // Doesn't seem to be a mailing list. + auto originalFromList = m_origMsg->from()->mailboxes(); + auto originalToList = m_origMsg->to()->mailboxes(); + + if (m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) + && !m_identityManager->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(m_origMsg->to()->mailboxes(), m_identityManager); + toList += authorMailboxes(m_origMsg, m_mailingListAddresses); + } } - for (const KMime::Types::Mailbox &mailbox : qAsConst(ccRecipients)) { + for (const KMime::Types::Mailbox &mailbox : ccList) { msg->cc()->addAddress(mailbox); } } - - if (toList.isEmpty() && !recipients.isEmpty()) { - // reply to self without other recipients - toList << recipients.at(0); - } break; - } case MessageComposer::ReplyAuthor: toList = authorMailboxes(m_origMsg, m_mailingListAddresses); 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(m_origMsg); if (!refStr.isEmpty()) { msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); } //In-Reply-To = original msg-id msg->inReplyTo()->from7BitString(m_origMsg->messageID()->as7BitString(false)); msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(m_origMsg.data()), "utf-8"); // If the reply shouldn't be blank, apply the template to the message if (m_quote) { MessageFactoryReplyJob *job = new MessageFactoryReplyJob; connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone); job->setMsg(msg); job->setReplyAll(replyAll); job->setIdentityManager(m_identityManager); job->setSelection(m_selection); job->setTemplate(m_template); job->setOrigMsg(m_origMsg); job->setCollection(m_collection); job->start(); } else { slotCreateReplyDone(msg, replyAll); } } void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg) { applyCharset(msg); MessageComposer::Util::addLinkInformation(msg, m_id, 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 (!m_origMsg->contentType()->isMultipart() && (!m_origMsg->contentType()->isText() || (m_origMsg->contentType()->isText() && m_origMsg->contentType()->subType() != "html" && m_origMsg->contentType()->subType() != "plain"))) { const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, 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(m_origMsg->contentType()->mimeType()); secondPart->setBody(m_origMsg->body()); // use the headers of the original mail secondPart->setHead(m_origMsg->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(m_origMsg->head()); msg->setBody(m_origMsg->body()); QString oldContentType = msg->contentType()->asUnicodeString(); const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, 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(m_origMsg.data()), "utf-8"); MessageFactoryForwardJob *job = new MessageFactoryForwardJob; connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone); job->setIdentityManager(m_identityManager); job->setMsg(msg); job->setSelection(m_selection); job->setTemplate(m_template); job->setOrigMsg(m_origMsg); job->setCollection(m_collection); job->start(); } QPair< KMime::Message::Ptr, QList< 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); QList< 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, m_identityManager, 0); } else if (numberOfItems == 1) { KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first()); const uint originalIdentity = identityUoid(firstMsg); MessageHelper::initFromMessage(msg, firstMsg, m_identityManager, 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(m_origMsg); MessageComposer::Util::addLinkInformation(msg, m_id, 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, QList< KMime::Content * > >(msg, QList< 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; } KMime::Message::Ptr MessageFactoryNG::createResend() { KMime::Message::Ptr msg(new KMime::Message); msg->setContent(m_origMsg->encodedContent()); msg->parse(); msg->removeHeader(); uint originalIdentity = identityUoid(m_origMsg); // 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(m_origMsg->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 (!m_origMsg) { return KMime::Message::Ptr(); } // copy the message 1:1 KMime::Message::Ptr msg(new KMime::Message); msg->setContent(m_origMsg->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 = m_identityManager->identityForUoidOrDefault(id); // X-KMail-Redirect-From: content const QString strByWayOf = QString::fromLocal8Bit("%1 (by way of %2 <%3>)") .arg(m_origMsg->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(m_origMsg->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, m_id, Akonadi::MessageStatus::statusForwarded()); return msg; } KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt() { QString receiptTo; if (auto hrd = m_origMsg->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(m_origMsg); MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, originalIdentity); receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1()); receipt->subject()->fromUnicodeString(i18n("Receipt: ") + m_origMsg->subject()->asUnicodeString(), "utf-8"); QString str = QStringLiteral("Your message was successfully delivered."); str += QLatin1String("\n\n---------- Message header follows ----------\n"); str += QString::fromLatin1(m_origMsg->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 = m_origMsg->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 = m_identityManager->identityForUoidOrDefault(identityUoid(m_origMsg)).fullEmailAddr(); // // Generate message: // KMime::Message::Ptr receipt(new KMime::Message()); const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, 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(m_origMsg, KMime::MDN::descriptionFor(d, m)); // text/plain part: KMime::Content *firstMsgPart = new KMime::Content(m_origMsg.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(m_origMsg.data()); secondMsgPart->contentType()->setMimeType("message/disposition-notification"); secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray originalRecipient = ""; if (auto hrd = m_origMsg->headerByType("Original-Recipient")) { originalRecipient = hrd->as7BitString(false); } secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent( finalRecipient, originalRecipient, m_origMsg->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(m_origMsg.data()); switch (mdnQuoteOriginal) { case 1: thirdMsgPart->contentType()->setMimeType("message/rfc822"); thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(m_origMsg)); receipt->addContent(thirdMsgPart); break; case 2: thirdMsgPart->contentType()->setMimeType("text/rfc822-headers"); thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(m_origMsg)); 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(m_origMsg->messageID()->asUnicodeString(), "utf-8"); receipt->setHeader(header); receipt->references()->from7BitString(getRefStr(m_origMsg)); 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 = m_folderId; MessageHelper::initHeader(msg, m_identityManager, 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) { m_identityManager = ident; } void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy) { m_replyStrategy = replyStrategy; } void MessageFactoryNG::setSelection(const QString &selection) { m_selection = selection; } void MessageFactoryNG::setQuote(bool quote) { m_quote = quote; } void MessageFactoryNG::setTemplate(const QString &templ) { m_template = templ; } void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses) { m_mailingListAddresses << listAddresses; } void MessageFactoryNG::setFolderIdentity(Akonadi::Collection::Id folderIdentityId) { m_folderId = folderIdentityId; } void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId) { m_parentFolderId = 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 = m_identityManager->identityForAddress(msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid(); } if (id == 0 && m_folderId > 0) { id = m_folderId; } return id; } QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s) { QString result = s; QRegExp rx(QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), Qt::CaseInsensitive); Q_ASSERT(rx.isValid()); QRegExp rxDate(QStringLiteral("\\$\\{date\\}")); Q_ASSERT(rxDate.isValid()); const QString sDate = KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch()); qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate; int idx = 0; if ((idx = rxDate.indexIn(result, idx)) != -1) { result.replace(idx, rxDate.matchedLength(), sDate); } idx = 0; while ((idx = rx.indexIn(result, idx)) != -1) { const QByteArray ba = rx.cap(1).toLatin1(); QString replacement; if (auto hrd = msg->headerByType(ba.constData())) { replacement = hrd->asUnicodeString(); } result.replace(idx, rx.matchedLength(), replacement); idx += replacement.length(); } 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(m_origMsg->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(); QList 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; }