diff --git a/messagecomposer/autotests/data/non-protected_headers.mbox b/messagecomposer/autotests/data/non-protected_headers.mbox new file mode 100644 index 00000000..2bd73f34 --- /dev/null +++ b/messagecomposer/autotests/data/non-protected_headers.mbox @@ -0,0 +1,3 @@ +Content-Type: text/plain + +one flew over the cuckoo's nest diff --git a/messagecomposer/autotests/data/protected_headers-non-obvoscate.mbox b/messagecomposer/autotests/data/protected_headers-non-obvoscate.mbox new file mode 100644 index 00000000..84120cf3 --- /dev/null +++ b/messagecomposer/autotests/data/protected_headers-non-obvoscate.mbox @@ -0,0 +1,6 @@ +Content-Type: text/plain; protected-headers="v1" +To: to@test.de, to2@test.de +Cc: cc@test.de, cc2@test.de +Subject: =?UTF-8?B?YXNkZmdoamtsw7Y=?= + +one flew over the cuckoo's nest diff --git a/messagecomposer/autotests/data/protected_headers-obvoscate.mbox b/messagecomposer/autotests/data/protected_headers-obvoscate.mbox new file mode 100644 index 00000000..26d55fb5 --- /dev/null +++ b/messagecomposer/autotests/data/protected_headers-obvoscate.mbox @@ -0,0 +1,16 @@ +Content-Type: multipart/mixed; boundary="123456789"; protected-headers="v1" +To: to@test.de, to2@test.de +Cc: cc@test.de, cc2@test.de +Subject: =?UTF-8?B?YXNkZmdoamtsw7Y=?= + +--123456789 +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset="UTF-8"; protected-headers="v1" + +Subject: asdfghjkl=C3=B6 +--123456789 +Content-Type: text/plain + +one flew over the cuckoo's nest +--123456789-- diff --git a/messagecomposer/autotests/encryptjobtest.cpp b/messagecomposer/autotests/encryptjobtest.cpp index ab578cac..35d9d80b 100644 --- a/messagecomposer/autotests/encryptjobtest.cpp +++ b/messagecomposer/autotests/encryptjobtest.cpp @@ -1,163 +1,291 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "encryptjobtest.h" #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include "setupenv.h" #include #include #include #include +#include #include #include -#include +#include +#include #include #include +#include +#include + +#include +#include + #include #include #include #include +#include QTEST_MAIN(EncryptJobTest) +using namespace MessageComposer; + void EncryptJobTest::initTestCase() { MessageComposer::Test::setupEnv(); } void EncryptJobTest::testContentDirect() { MessageComposer::Composer *composer = new MessageComposer::Composer; QVector charsets; charsets << "us-ascii"; composer->globalPart()->setCharsets(charsets); MessageComposer::TextPart *part = new MessageComposer::TextPart(this); part->setWordWrappingEnabled(false); part->setCleanPlainText(QStringLiteral("one flew over the cuckoo's nest")); MessageComposer::MainTextJob *mainTextJob = new MessageComposer::MainTextJob(part, composer); QVERIFY(composer); QVERIFY(mainTextJob); VERIFYEXEC(mainTextJob); std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(composer); QVERIFY(eJob); QStringList recipients; recipients << QString::fromLocal8Bit("test@kolab.org"); eJob->setContent(mainTextJob->content()); eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); eJob->setRecipients(recipients); eJob->setEncryptionKeys(keys); checkEncryption(eJob); } void EncryptJobTest::testContentChained() { MessageComposer::Composer *composer = new MessageComposer::Composer; QVector charsets; charsets << "us-ascii"; composer->globalPart()->setCharsets(charsets); MessageComposer::TextPart *part = new MessageComposer::TextPart(this); part->setWordWrappingEnabled(false); part->setCleanPlainText(QStringLiteral("one flew over the cuckoo's nest")); MessageComposer::MainTextJob *mainTextJob = new MessageComposer::MainTextJob(part, composer); QVERIFY(composer); QVERIFY(mainTextJob); VERIFYEXEC(mainTextJob); std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); - qDebug() << "done getting keys"; MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(composer); QStringList recipients; recipients << QString::fromLocal8Bit("test@kolab.org"); eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); eJob->setRecipients(recipients); eJob->setEncryptionKeys(keys); eJob->setContent(mainTextJob->content()); checkEncryption(eJob); } +void EncryptJobTest::testContentSubjobChained() +{ + std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); + + QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); + KMime::Message skeletonMessage; + + KMime::Content *content = new KMime::Content; + content->contentType(true)->setMimeType("text/plain"); + content->setBody(data); + + auto tJob = new TransparentJob; + tJob->setContent(content); + + QStringList recipients; + recipients << QString::fromLocal8Bit("test@kolab.org"); + + Composer composer; + auto eJob = new MessageComposer::EncryptJob(&composer); + + eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + eJob->setRecipients(recipients); + eJob->setEncryptionKeys(keys); + eJob->setSkeletonMessage(&skeletonMessage); + eJob->appendSubjob(tJob); + + checkEncryption(eJob); +} + void EncryptJobTest::testHeaders() { std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(this); QVERIFY(eJob); QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); KMime::Content *content = new KMime::Content; content->setBody(data); QStringList recipients; recipients << QString::fromLocal8Bit("test@kolab.org"); eJob->setContent(content); eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); eJob->setRecipients(recipients); eJob->setEncryptionKeys(keys); VERIFYEXEC(eJob); QByteArray mimeType("multipart/encrypted"); QByteArray charset("ISO-8859-1"); KMime::Content *result = eJob->content(); result->assemble(); QVERIFY(result->contentType(false)); QCOMPARE(result->contentType()->mimeType(), mimeType); QCOMPARE(result->contentType()->charset(), charset); QCOMPARE(result->contentType()->parameter(QString::fromLocal8Bit("protocol")), QString::fromLocal8Bit("application/pgp-encrypted")); QCOMPARE(result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit); } +void EncryptJobTest::testProtectedHeaders_data() +{ + QTest::addColumn("protectedHeaders"); + QTest::addColumn("protectedHeadersObvoscate"); + QTest::addColumn("referenceFile"); + + QTest::newRow("simple-obvoscate") << true << true << QStringLiteral("protected_headers-obvoscate.mbox"); + QTest::newRow("simple-non-obvoscate") << true << false << QStringLiteral("protected_headers-non-obvoscate.mbox"); + QTest::newRow("non-protected_headers") << false << false << QStringLiteral("non-protected_headers.mbox"); +} + +void EncryptJobTest::testProtectedHeaders() +{ + QFETCH(bool,protectedHeaders); + QFETCH(bool, protectedHeadersObvoscate); + QFETCH(QString, referenceFile); + + std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); + + MessageComposer::Composer composer; + MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(&composer); + + QVERIFY(eJob); + + const QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); + const QString subject(QStringLiteral("asdfghjklö")); + + KMime::Content *content = new KMime::Content; + content->contentType(true)->setMimeType("text/plain"); + content->setBody(data); + + KMime::Message skeletonMessage; + skeletonMessage.contentType(true)->setMimeType("foo/bla"); + skeletonMessage.to(true)->from7BitString("to@test.de, to2@test.de"); + skeletonMessage.cc(true)->from7BitString("cc@test.de, cc2@test.de"); + skeletonMessage.bcc(true)->from7BitString("bcc@test.de, bcc2@test.de"); + skeletonMessage.subject(true)->fromUnicodeString(subject, "utf-8"); + + QStringList recipients; + recipients << QString::fromLocal8Bit("test@kolab.org"); + + eJob->setContent(content); + eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + eJob->setRecipients(recipients); + eJob->setEncryptionKeys(keys); + eJob->setSkeletonMessage(&skeletonMessage); + eJob->setProtectedHeaders(protectedHeaders); + eJob->setProtectedHeadersObvoscate(protectedHeadersObvoscate); + + VERIFYEXEC(eJob); + + if (protectedHeadersObvoscate) { + QCOMPARE(skeletonMessage.subject()->as7BitString(false), "..."); + } else { + QCOMPARE(skeletonMessage.subject()->asUnicodeString(), subject); + } + + KMime::Content *result = eJob->content(); + result->assemble(); + + KMime::Content *encPart = MessageComposer::Util::findTypeInMessage(result, "application", "octet-stream"); + KMime::Content tempNode; + { + QByteArray plainText; + auto job = QGpgME::openpgp()->decryptVerifyJob(); + job->exec(encPart->encodedBody(), plainText); + + tempNode.setContent(KMime::CRLFtoLF(plainText.constData())); + tempNode.parse(); + } + if (protectedHeadersObvoscate) { + tempNode.contentType(false)->setBoundary("123456789"); + tempNode.assemble(); + } + + delete result; + + QFile f(referenceFile); + QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate)); + const QByteArray encodedContent(tempNode.encodedContent()); + f.write(encodedContent); + if (!encodedContent.endsWith('\n')) { + f.write("\n"); + } + f.close(); + + Test::compareFile(referenceFile, QStringLiteral(MAIL_DATA_DIR "/")+referenceFile); +} + void EncryptJobTest::checkEncryption(MessageComposer::EncryptJob *eJob) { VERIFYEXEC(eJob); KMime::Content *result = eJob->content(); Q_ASSERT(result); result->assemble(); ComposerTestUtil::verifyEncryption(result, QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8(), Kleo::OpenPGPMIMEFormat); } diff --git a/messagecomposer/autotests/encryptjobtest.h b/messagecomposer/autotests/encryptjobtest.h index 9c8fd114..48ca63ec 100644 --- a/messagecomposer/autotests/encryptjobtest.h +++ b/messagecomposer/autotests/encryptjobtest.h @@ -1,49 +1,53 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ENCRYPTJOBTEST_H #define ENCRYPTJOBTEST_H #include #include #include namespace MessageComposer { class EncryptJob; } class EncryptJobTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); private Q_SLOTS: void testContentDirect(); void testContentChained(); + void testContentSubjobChained(); void testHeaders(); + void testProtectedHeaders_data(); + void testProtectedHeaders(); + private: void checkEncryption(MessageComposer::EncryptJob *eJob); }; #endif diff --git a/messagecomposer/autotests/setupenv.cpp b/messagecomposer/autotests/setupenv.cpp index 06cc5191..1671339e 100644 --- a/messagecomposer/autotests/setupenv.cpp +++ b/messagecomposer/autotests/setupenv.cpp @@ -1,77 +1,110 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "setupenv.h" #include #include #include -#include #include +#include +#include #include +#include using namespace MessageComposer; void Test::setupEnv() { qputenv("LC_ALL", "C"); qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.qttest")).constData()); QStandardPaths::setTestModeEnabled(true); } std::vector< GpgME::Key, std::allocator< GpgME::Key > > Test::getKeys(bool smime) { QGpgME::KeyListJob *job = nullptr; if (smime) { const QGpgME::Protocol *const backend = QGpgME::smime(); Q_ASSERT(backend); job = backend->keyListJob(false); } else { const QGpgME::Protocol *const backend = QGpgME::openpgp(); Q_ASSERT(backend); job = backend->keyListJob(false); } Q_ASSERT(job); std::vector< GpgME::Key > keys; GpgME::KeyListResult res = job->exec(QStringList(), true, keys); if (!smime) { Q_ASSERT(keys.size() == 3); } Q_ASSERT(!res.error()); /* qDebug() << "got private keys:" << keys.size(); for (std::vector< GpgME::Key >::iterator i = keys.begin(); i != keys.end(); ++i) { qDebug() << "key isnull:" << i->isNull() << "isexpired:" << i->isExpired(); qDebug() << "key numuserIds:" << i->numUserIDs(); for (uint k = 0; k < i->numUserIDs(); ++k) { qDebug() << "userIDs:" << i->userID(k).email(); } } */ return keys; } + +KMime::Message::Ptr Test::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 Test::compareFile(const QString &outFile, const QString &referenceFile) +{ + QVERIFY(QFile::exists(outFile)); + + // compare to reference file + const auto args = QStringList() + << QStringLiteral("-u") + << referenceFile + << outFile; + QProcess proc; + proc.setProcessChannelMode(QProcess::ForwardedChannels); + proc.start(QStringLiteral("diff"), args); + QVERIFY(proc.waitForFinished()); + + QCOMPARE(proc.exitCode(), 0); +} diff --git a/messagecomposer/autotests/setupenv.h b/messagecomposer/autotests/setupenv.h index 9dfcfbff..9ba434d5 100644 --- a/messagecomposer/autotests/setupenv.h +++ b/messagecomposer/autotests/setupenv.h @@ -1,43 +1,56 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MESSAGECORE_TESTS_SETUPENV_H #define MESSAGECORE_TESTS_SETUPENV_H #include +#include + namespace MessageComposer { namespace Test { /** * setup a environment variables for tests: * * set LC_ALL to C * * set KDEHOME * * verify that Kleo has correctly loaded all backends */ void setupEnv(); /** * Returns list of keys used in various crypto routines */ std::vector getKeys(bool smime = false); + +/** +* Loads a message from filename and returns a message pointer +*/ +KMime::Message::Ptr loadMessageFromFile(const QString &filename); + +/** +* compare two mails via files. +* If the files are not euqal print diff output. +*/ +void compareFile(const QString &outFile, const QString &referenceFile); } } #endif diff --git a/messagecomposer/src/CMakeLists.txt b/messagecomposer/src/CMakeLists.txt index 0ee37124..e8f4b61a 100644 --- a/messagecomposer/src/CMakeLists.txt +++ b/messagecomposer/src/CMakeLists.txt @@ -1,505 +1,506 @@ add_definitions(-DTRANSLATION_DOMAIN=\"libmessagecomposer\") if(BUILD_TESTING) add_subdirectory( imagescaling/autotests ) add_subdirectory( imagescaling/tests ) add_subdirectory( composer-ng/autotests ) add_subdirectory( statusbarwidget/autotests ) add_subdirectory( plugineditor/autotests ) add_subdirectory( composer/autotests ) add_subdirectory( snippet/autotests ) endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/part) set( messagecomposer_job_src job/jobbase.cpp job/contentjobbase.cpp job/attachmentjob.cpp job/singlepartjob.cpp job/multipartjob.cpp job/maintextjob.cpp job/signjob.cpp job/encryptjob.cpp job/signencryptjob.cpp job/transparentjob.cpp job/inserttextfilejob.cpp job/skeletonmessagejob.cpp job/aliasesexpandjob.cpp job/emailaddressresolvejob.cpp job/attachmentfrompublickeyjob.cpp job/distributionlistexpandjob.cpp job/savecontactpreferencejob.cpp job/attachmentvcardfromaddressbookjob.cpp job/attachmentclipboardjob.cpp + job/protectedheaders.cpp ) set( messagecomposer_statusbarwidget_src statusbarwidget/statusbarlabeltoggledstate.cpp ) set( messagecomposer_composer_src composer/composer.cpp composer/signaturecontroller.cpp composer/composerlineedit.cpp composer/composerviewbase.cpp composer/keyresolver.cpp composer/composerviewinterface.cpp composer/composerattachmentinterface.cpp ) set( messagecomposer_recipient_src recipient/recipientspicker.cpp recipient/recipient.cpp recipient/recipientline.cpp recipient/recipientseditor.cpp recipient/recipientseditorsidewidget.cpp recipient/kwindowpositioner.cpp recipient/distributionlistdialog.cpp ) set( messagecomposer_snippet_src snippet/convertsnippetvariablesjob.cpp snippet/convertsnippetvariablesutil.cpp snippet/convertsnippetvariablemenu.cpp ) set( messagecomposer_imagescaling_src imagescaling/imagescaling.cpp imagescaling/imagescalingwidget.cpp imagescaling/imagescalingutils.cpp imagescaling/imagescalingselectformat.cpp ) set( messagecomposer_part_src part/messagepart.cpp part/globalpart.cpp part/infopart.cpp part/textpart.cpp ) set( messagecomposer_attachment_src attachment/attachmentcontrollerbase.cpp attachment/attachmentmodel.cpp ) set( messagecomposer_helper_src helper/messagehelper.cpp helper/messagefactoryng.cpp helper/messagefactoryforwardjob.cpp helper/messagefactoryreplyjob.cpp ) set( messagecomposer_sender_src sender/akonadisender.cpp ) set(messagecomposer_followupreminder_SRCS followupreminder/followupreminderselectdatedialog.cpp followupreminder/followupremindercreatejob.cpp ) set(messagecomposer_richtextcomposerng_SRCS composer-ng/richtextcomposerng.cpp composer-ng/richtextcomposersignatures.cpp ) set(messagecomposer_plugineditor_SRCS plugineditor/plugineditormanager.cpp plugineditor/plugineditor.cpp plugineditor/plugineditorinterface.cpp plugineditor/pluginactiontype.cpp plugineditor/plugincomposerinterface.cpp ) set(messagecomposer_plugineditorcheckbeforesend_SRCS plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp ) set(messagecomposer_plugineditorinit_SRCS plugineditorinit/plugineditorinitconfigurewidget.cpp plugineditorinit/plugineditorinit.cpp plugineditorinit/plugineditorinitmanager.cpp plugineditorinit/plugineditorinitinterface.cpp ) set(messagecomposer_plugineditorconverttext_SRCS plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp plugineditorconverttext/plugineditorconverttext.cpp plugineditorconverttext/plugineditorconverttextmanager.cpp plugineditorconverttext/plugineditorconverttextinterface.cpp plugineditorconverttext/plugineditorconverterinitialdata.cpp plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp ) set(messagecomposer_plugineditorgrammar_SRCS plugineditorgrammar/plugineditorgrammarmanager.cpp plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.cpp ) set( messagecomposer_src ${messagecomposer_snippet_src} ${messagecomposer_statusbarwidget_src} ${messagecomposer_plugineditorconverttext_SRCS} ${messagecomposer_plugineditorinit_SRCS} ${messagecomposer_plugineditor_SRCS} ${messagecomposer_richtextcomposerng_SRCS} ${messagecomposer_part_src} ${messagecomposer_imagescaling_src} ${messagecomposer_job_src} ${messagecomposer_composer_src} ${messagecomposer_recipient_src} ${messagecomposer_attachment_src} ${messagecomposer_helper_src} ${messagecomposer_sender_src} ${messagecomposer_followupreminder_SRCS} ${messagecomposer_plugineditorcheckbeforesend_SRCS} ${messagecomposer_plugineditorgrammar_SRCS} utils/util.cpp settings/messagecomposersettings.cpp ) ki18n_wrap_ui(messagecomposer_src imagescaling/ui/imagescalingwidget.ui ) ecm_qt_declare_logging_category(messagecomposer_src HEADER messagecomposer_debug.h IDENTIFIER MESSAGECOMPOSER_LOG CATEGORY_NAME org.kde.pim.messagecomposer DESCRIPTION "messagelib (messagecomposer)" OLD_CATEGORY_NAMES log_messagecomposer EXPORT MESSAGELIB ) if(KDEPIM_ENTERPRISE_BUILD) set(WARN_TOOMANY_RECIPIENTS_DEFAULT true) set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true) else() set(WARN_TOOMANY_RECIPIENTS_DEFAULT false) set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT false) endif() configure_file(settings/messagecomposer.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg) kconfig_add_kcfg_files(messagecomposer_src settings/messagecomposersettings_base.kcfgc ) add_library( KF5MessageComposer ${messagecomposer_src} ) generate_export_header(KF5MessageComposer BASE_NAME messagecomposer) add_library(KF5::MessageComposer ALIAS KF5MessageComposer) target_link_libraries(KF5MessageComposer PUBLIC KF5::Mime KF5::MessageCore KF5::PimCommon KF5::AkonadiCore KF5::IdentityManagement KF5::AkonadiMime KF5::Libkleo KF5::MessageViewer PRIVATE KF5::MailTransportAkonadi KF5::PimTextEdit KF5::TemplateParser KF5::AkonadiWidgets KF5::LibkdepimAkonadi KF5::KIOCore KF5::I18n KF5::KIOWidgets # for KIO::JobUiDelegate KF5::KIOFileWidgets # for KEncodingDialog KF5::XmlGui # for KActionCollection KF5::SonnetUi Grantlee5::TextDocument KF5::CalendarCore # for KCalendarCore/Todo KF5::SendLater KF5::FollowupReminder KF5::Archive KF5::Contacts KF5::SonnetCore ) target_include_directories(KF5MessageComposer INTERFACE "$") set_target_properties(KF5MessageComposer PROPERTIES VERSION ${MESSAGECOMPOSER_VERSION_STRING} SOVERSION ${MESSAGECOMPOSER_SOVERSION} EXPORT_NAME MessageComposer ) install(TARGETS KF5MessageComposer EXPORT KF5MessageComposerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) ecm_generate_headers(MessageComposer_Camelstatusbarwidget_HEADERS HEADER_NAMES StatusBarLabelToggledState REQUIRED_HEADERS MessageComposer_statusbarwidget_HEADERS PREFIX MessageComposer RELATIVE statusbarwidget ) ecm_generate_headers(MessageComposer_Camelsnippet_HEADERS HEADER_NAMES ConvertSnippetVariablesJob ConvertSnippetVariablesUtil ConvertSnippetVariableMenu REQUIRED_HEADERS MessageComposer_snippet_HEADERS PREFIX MessageComposer RELATIVE snippet ) ecm_generate_headers(MessageComposer_Camelplugineditor_HEADERS HEADER_NAMES PluginEditor PluginActionType PluginEditorInterface PluginComposerInterface PluginEditorManager REQUIRED_HEADERS MessageComposer_plugineditor_HEADERS PREFIX MessageComposer RELATIVE plugineditor ) ecm_generate_headers(MessageComposer_Camelplugineditorinit_HEADERS HEADER_NAMES PluginEditorInitConfigureWidget PluginEditorInit PluginEditorInitManager PluginEditorInitInterface REQUIRED_HEADERS MessageComposer_plugineditorinit_HEADERS PREFIX MessageComposer RELATIVE plugineditorinit ) ecm_generate_headers(MessageComposer_Camelplugineditorconverttext_HEADERS HEADER_NAMES PluginEditorConvertTextConfigureWidget PluginEditorConvertText PluginEditorConvertTextManager PluginEditorConvertTextInterface PluginEditorConverterInitialData PluginEditorConverterBeforeConvertingData REQUIRED_HEADERS MessageComposer_plugineditorconverttext_HEADERS PREFIX MessageComposer RELATIVE plugineditorconverttext ) ecm_generate_headers(MessageComposer_Camelplugineditorgrammar_HEADERS HEADER_NAMES PluginEditorGrammarManager PluginEditorGrammarCustomToolsViewInterface REQUIRED_HEADERS MessageComposer_plugineditorgrammar_HEADERS PREFIX MessageComposer RELATIVE plugineditorgrammar ) ecm_generate_headers(MessageComposer_Camelplugineditorcheckbeforesend_HEADERS HEADER_NAMES PluginEditorCheckBeforeSend PluginEditorCheckBeforeSendInterface PluginEditorCheckBeforeSendManager PluginEditorCheckBeforeSendConfigureWidget PluginEditorCheckBeforeSendParams REQUIRED_HEADERS MessageComposer_plugineditorcheckbeforesend_HEADERS PREFIX MessageComposer RELATIVE plugineditorcheckbeforesend ) ecm_generate_headers(MessageComposer_Camelcaseattachement_HEADERS HEADER_NAMES AttachmentModel AttachmentControllerBase REQUIRED_HEADERS MessageComposer_attachement_HEADERS PREFIX MessageComposer RELATIVE attachment ) ecm_generate_headers(MessageComposer_Camelcasecomposer_HEADERS HEADER_NAMES Composer ComposerLineEdit ComposerViewBase ComposerViewInterface ComposerAttachmentInterface SignatureController REQUIRED_HEADERS MessageComposer_composer_HEADERS PREFIX MessageComposer RELATIVE composer ) ecm_generate_headers(MessageComposer_Camelcasecomposerng_HEADERS HEADER_NAMES RichTextComposerNg RichTextComposerSignatures REQUIRED_HEADERS MessageComposer_composerng_HEADERS PREFIX MessageComposer RELATIVE composer-ng ) ecm_generate_headers(MessageComposer_Camelcasesender_HEADERS HEADER_NAMES AkonadiSender MessageSender REQUIRED_HEADERS MessageComposer_sender_HEADERS PREFIX MessageComposer RELATIVE sender ) ecm_generate_headers(MessageComposer_Camelcaseutils_HEADERS HEADER_NAMES Util Kleo_Util REQUIRED_HEADERS MessageComposer_utils_HEADERS PREFIX MessageComposer RELATIVE utils ) ecm_generate_headers(MessageComposer_Camelcasehelper_HEADERS HEADER_NAMES MessageHelper MessageFactoryNG REQUIRED_HEADERS MessageComposer_helper_HEADERS PREFIX MessageComposer RELATIVE helper ) ecm_generate_headers(MessageComposer_Camelcasesettings_HEADERS HEADER_NAMES MessageComposerSettings REQUIRED_HEADERS MessageComposer_settings_HEADERS PREFIX MessageComposer RELATIVE settings ) ecm_generate_headers(MessageComposer_Camelcasepart_HEADERS HEADER_NAMES TextPart GlobalPart InfoPart MessagePart REQUIRED_HEADERS MessageComposer_part_HEADERS PREFIX MessageComposer RELATIVE part ) ecm_generate_headers(MessageComposer_Camelcasefollowupreminder_HEADERS HEADER_NAMES FollowupReminderCreateJob FollowUpReminderSelectDateDialog REQUIRED_HEADERS MessageComposer_followupreminder_HEADERS PREFIX MessageComposer RELATIVE followupreminder ) ecm_generate_headers(MessageComposer_Camelcaserecipient_HEADERS HEADER_NAMES Recipient RecipientsEditor RecipientLine REQUIRED_HEADERS MessageComposer_recipient_HEADERS PREFIX MessageComposer RELATIVE recipient ) ecm_generate_headers(MessageComposer_Camelcaseimagescaling_HEADERS HEADER_NAMES ImageScalingWidget REQUIRED_HEADERS MessageComposer_imagescaling_HEADERS PREFIX MessageComposer RELATIVE imagescaling ) ecm_generate_headers(MessageComposer_Camelcasejob_HEADERS HEADER_NAMES JobBase AbstractEncryptJob ContentJobBase InsertTextFileJob AttachmentJob SinglepartJob MainTextJob AttachmentFromPublicKeyJob MultipartJob EncryptJob AttachmentVcardFromAddressBookJob SignJob TransparentJob JobBase AliasesExpandJob SkeletonMessageJob AttachmentClipBoardJob REQUIRED_HEADERS MessageComposer_job_HEADERS PREFIX MessageComposer RELATIVE job ) ecm_generate_pri_file(BASE_NAME MessageComposer LIB_NAME KF5MessageComposer DEPS "Mime MessageCore PimCommon Akonadi IdentityManagement AkonadiMime Libkleo" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageComposer ) install(FILES ${MessageComposer_Camelsnippet_HEADERS} ${MessageComposer_Camelplugineditorconverttext_HEADERS} ${MessageComposer_Camelplugineditorinit_HEADERS} ${MessageComposer_Camelplugineditorcheckbeforesend_HEADERS} ${MessageComposer_Camelcasecomposer_HEADERS} ${MessageComposer_Camelcasecomposerng_HEADERS} ${MessageComposer_Camelcasesender_HEADERS} ${MessageComposer_Camelcaseutils_HEADERS} ${MessageComposer_Camelcasehelper_HEADERS} ${MessageComposer_Camelcasesettings_HEADERS} ${MessageComposer_Camelcasepart_HEADERS} ${MessageComposer_Camelcasefollowupreminder_HEADERS} ${MessageComposer_Camelcaserecipient_HEADERS} ${MessageComposer_Camelcaseimagescaling_HEADERS} ${MessageComposer_Camelcasejob_HEADERS} ${MessageComposer_Camelcaseattachement_HEADERS} ${MessageComposer_Camelplugineditor_HEADERS} ${MessageComposer_Camelplugineditorgrammar_HEADERS} ${MessageComposer_Camelstatusbarwidget_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageComposer COMPONENT Devel ) install(FILES ${MessageComposer_HEADERS} ${MessageComposer_snippet_HEADERS} ${MessageComposer_plugineditorconverttext_HEADERS} ${MessageComposer_plugineditorinit_HEADERS} ${MessageComposer_statusbarwidget_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer_export.h ${CMAKE_CURRENT_BINARY_DIR}/messagecomposersettings_base.h ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer_debug.h ${MessageComposer_composer_HEADERS} ${MessageComposer_composerng_HEADERS} ${MessageComposer_sender_HEADERS} ${MessageComposer_utils_HEADERS} ${MessageComposer_helper_HEADERS} ${MessageComposer_settings_HEADERS} ${MessageComposer_part_HEADERS} ${MessageComposer_followupreminder_HEADERS} ${MessageComposer_recipient_HEADERS} ${MessageComposer_imagescaling_HEADERS} ${MessageComposer_attachement_HEADERS} ${MessageComposer_job_HEADERS} ${MessageComposer_plugineditor_HEADERS} ${MessageComposer_plugineditorcheckbeforesend_HEADERS} ${MessageComposer_plugineditorgrammar_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/messagecomposer COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/messagecomposer/src/composer/composer.cpp b/messagecomposer/src/composer/composer.cpp index e14a5787..7bad0b27 100644 --- a/messagecomposer/src/composer/composer.cpp +++ b/messagecomposer/src/composer/composer.cpp @@ -1,600 +1,601 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "composer.h" #include "job/attachmentjob.h" #include "part/globalpart.h" #include "part/infopart.h" #include "job/jobbase_p.h" #include "part/textpart.h" #include "job/maintextjob.h" #include "job/multipartjob.h" #include "job/signjob.h" #include "job/encryptjob.h" #include "job/signencryptjob.h" #include "job/skeletonmessagejob.h" #include "job/transparentjob.h" #include "imagescaling/imagescaling.h" #include "imagescaling/imagescalingutils.h" #include "settings/messagecomposersettings.h" #include "messagecomposer_debug.h" #include using namespace MessageComposer; using MessageCore::AttachmentPart; class MessageComposer::ComposerPrivate : public JobBasePrivate { public: ComposerPrivate(Composer *qq) : JobBasePrivate(qq) { } void init(); void doStart(); // slot void composeStep1(); void composeStep2(); QList createEncryptJobs(ContentJobBase *contentJob, bool sign); void contentJobFinished(KJob *job); // slot void composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector &keys, const QStringList &recipients); void attachmentsFinished(KJob *job); // slot void composeFinalStep(KMime::Content *headers, KMime::Content *content); QVector > > encData; std::vector signers; AttachmentPart::List attachmentParts; // attachments with different sign/encrypt settings from // main message body. added at the end of the process AttachmentPart::List lateAttachmentParts; QVector resultMessages; Kleo::CryptoMessageFormat format; // Stuff that the application plays with. GlobalPart *globalPart = nullptr; InfoPart *infoPart = nullptr; TextPart *textPart = nullptr; // Stuff that we play with. KMime::Message *skeletonMessage = nullptr; bool started = false; bool finished = false; bool sign = false; bool encrypt = false; bool noCrypto = false; bool autoSaving = false; Q_DECLARE_PUBLIC(Composer) }; void ComposerPrivate::init() { Q_Q(Composer); // We cannot create these in ComposerPrivate's constructor, because // their parent q is not fully constructed at that time. globalPart = new GlobalPart(q); infoPart = new InfoPart(q); textPart = new TextPart(q); } void ComposerPrivate::doStart() { Q_ASSERT(!started); started = true; composeStep1(); } void ComposerPrivate::composeStep1() { Q_Q(Composer); // Create skeleton message (containing headers only; no content). SkeletonMessageJob *skeletonJob = new SkeletonMessageJob(infoPart, globalPart, q); QObject::connect(skeletonJob, &SkeletonMessageJob::finished, q, [this, skeletonJob](KJob *job) { if (job->error()) { return; // KCompositeJob takes care of the error. } // SkeletonMessageJob is a special job creating a Message instead of a Content. Q_ASSERT(skeletonMessage == nullptr); skeletonMessage = skeletonJob->message(); Q_ASSERT(skeletonMessage); skeletonMessage->assemble(); composeStep2(); }); q->addSubjob(skeletonJob); skeletonJob->start(); } void ComposerPrivate::composeStep2() { Q_Q(Composer); ContentJobBase *mainJob = nullptr; MainTextJob *mainTextJob = new MainTextJob(textPart, q); if ((sign || encrypt) && format & Kleo::InlineOpenPGPFormat) { // needs custom handling --- one SignEncryptJob by itself qCDebug(MESSAGECOMPOSER_LOG) << "sending to sign/enc inline job!"; if (encrypt) { //TODO: fix Inline PGP with encrypted attachments const QList jobs = createEncryptJobs(mainTextJob, sign); for (ContentJobBase *subJob : jobs) { if (attachmentParts.isEmpty()) { // We have no attachments. Use the content given by the MainTextJob. mainJob = subJob; } else { MultipartJob *multipartJob = new MultipartJob(q); multipartJob->setMultipartSubtype("mixed"); multipartJob->appendSubjob(subJob); for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) { multipartJob->appendSubjob(new AttachmentJob(part)); } mainJob = multipartJob; } QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*))); q->addSubjob(mainJob); } } else { SignJob *subJob = new SignJob(q); subJob->setSigningKeys(signers); subJob->setCryptoMessageFormat(format); subJob->appendSubjob(mainTextJob); if (attachmentParts.isEmpty()) { // We have no attachments. Use the content given by the MainTextJob. mainJob = subJob; } else { MultipartJob *multipartJob = new MultipartJob(q); multipartJob->setMultipartSubtype("mixed"); multipartJob->appendSubjob(subJob); for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) { multipartJob->appendSubjob(new AttachmentJob(part)); } mainJob = multipartJob; } QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*))); q->addSubjob(mainJob); } if (mainJob) { mainJob->start(); } else { qCDebug(MESSAGECOMPOSER_LOG) << "main job is null"; } return; } if (attachmentParts.isEmpty()) { // We have no attachments. Use the content given by the MainTextJob. mainJob = mainTextJob; } else { // We have attachments. Create a multipart/mixed content. QMutableListIterator iter(attachmentParts); while (iter.hasNext()) { AttachmentPart::Ptr part = iter.next(); qCDebug(MESSAGECOMPOSER_LOG) << "Checking attachment crypto policy... signed: " << part->isSigned() << " isEncrypted : " << part->isEncrypted(); if (!noCrypto && !autoSaving && (sign != part->isSigned() || encrypt != part->isEncrypted())) { // different policy qCDebug(MESSAGECOMPOSER_LOG) << "got attachment with different crypto policy!"; lateAttachmentParts.append(part); iter.remove(); } } MultipartJob *multipartJob = new MultipartJob(q); multipartJob->setMultipartSubtype("mixed"); multipartJob->appendSubjob(mainTextJob); for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) { multipartJob->appendSubjob(new AttachmentJob(part)); } mainJob = multipartJob; } if (sign) { SignJob *sJob = new SignJob(q); sJob->setCryptoMessageFormat(format); sJob->setSigningKeys(signers); sJob->appendSubjob(mainJob); mainJob = sJob; } if (encrypt) { const auto lstJob = createEncryptJobs(mainJob, false); for (ContentJobBase *eJob : lstJob) { QObject::connect(eJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*))); q->addSubjob(eJob); mainJob = eJob; //start only last EncryptJob } } else { QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*))); q->addSubjob(mainJob); } mainJob->start(); } QList ComposerPrivate::createEncryptJobs(ContentJobBase *contentJob, bool sign) { Q_Q(Composer); QList jobs; // each SplitInfo holds a list of recipients/keys, if there is more than // one item in it then it means there are secondary recipients that need // different messages w/ clean headers qCDebug(MESSAGECOMPOSER_LOG) << "starting enc jobs"; qCDebug(MESSAGECOMPOSER_LOG) << "format:" << format; qCDebug(MESSAGECOMPOSER_LOG) << "enc data:" << encData.size(); if (encData.isEmpty()) { // no key data! bail! q->setErrorText(i18n("No key data for recipients found.")); q->setError(Composer::IncompleteError); q->emitResult(); return jobs; } const int encDataSize = encData.size(); jobs.reserve(encDataSize); for (int i = 0; i < encDataSize; ++i) { QPair > recipients = encData[ i ]; qCDebug(MESSAGECOMPOSER_LOG) << "got first list of recipients:" << recipients.first; ContentJobBase *subJob = nullptr; if (sign) { SignEncryptJob *seJob = new SignEncryptJob(q); seJob->setCryptoMessageFormat(format); seJob->setSigningKeys(signers); seJob->setEncryptionKeys(recipients.second); seJob->setRecipients(recipients.first); subJob = seJob; } else { EncryptJob *eJob = new EncryptJob(q); eJob->setCryptoMessageFormat(format); eJob->setEncryptionKeys(recipients.second); eJob->setRecipients(recipients.first); + eJob->setSkeletonMessage(skeletonMessage); subJob = eJob; } qCDebug(MESSAGECOMPOSER_LOG) << "subJob" << subJob; subJob->appendSubjob(contentJob); jobs.append(subJob); } qCDebug(MESSAGECOMPOSER_LOG) << jobs.size(); return jobs; } void ComposerPrivate::contentJobFinished(KJob *job) { if (job->error()) { return; // KCompositeJob takes care of the error. } qCDebug(MESSAGECOMPOSER_LOG) << "composing final message"; KMime::Message *headers = nullptr; KMime::Content *resultContent = nullptr; std::vector keys; QStringList recipients; Q_ASSERT(dynamic_cast(job) == static_cast(job)); ContentJobBase *contentJob = static_cast(job); // create the final headers and body, // taking into account secondary recipients for encryption if (encData.size() > 1) { // crypto job with secondary recipients.. Q_ASSERT(dynamic_cast(job)); // we need to get the recipients for this job MessageComposer::AbstractEncryptJob *eJob = dynamic_cast(job); keys = eJob->encryptionKeys(); recipients = eJob->recipients(); resultContent = contentJob->content(); // content() comes from superclass headers = new KMime::Message; headers->setHeader(skeletonMessage->from()); headers->setHeader(skeletonMessage->to()); headers->setHeader(skeletonMessage->cc()); headers->setHeader(skeletonMessage->subject()); headers->setHeader(skeletonMessage->date()); headers->setHeader(skeletonMessage->messageID()); KMime::Headers::Generic *realTo = new KMime::Headers::Generic("X-KMail-EncBccRecipients"); realTo->fromUnicodeString(eJob->recipients().join(QLatin1Char('%')), "utf-8"); qCDebug(MESSAGECOMPOSER_LOG) << "got one of multiple messages sending to:" << realTo->asUnicodeString(); qCDebug(MESSAGECOMPOSER_LOG) << "sending to recipients:" << recipients; headers->setHeader(realTo); headers->assemble(); } else { // just use the saved headers from before if (!encData.isEmpty()) { qCDebug(MESSAGECOMPOSER_LOG) << "setting enc data:" << encData[ 0 ].first << "with num keys:" << encData[ 0 ].second.size(); keys = encData[ 0 ].second; recipients = encData[ 0 ].first; } headers = skeletonMessage; resultContent = contentJob->content(); } if (lateAttachmentParts.isEmpty()) { composeFinalStep(headers, resultContent); } else { composeWithLateAttachments(headers, resultContent, lateAttachmentParts, keys, recipients); } } void ComposerPrivate::composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector &keys, const QStringList &recipients) { Q_Q(Composer); MultipartJob *multiJob = new MultipartJob(q); multiJob->setMultipartSubtype("mixed"); // wrap the content into a job for the multijob to handle it MessageComposer::TransparentJob *tJob = new MessageComposer::TransparentJob(q); tJob->setContent(content); multiJob->appendSubjob(tJob); multiJob->setExtraContent(headers); qCDebug(MESSAGECOMPOSER_LOG) << "attachment encr key size:" << keys.size() << " recipients: " << recipients; // operate correctly on each attachment that has a different crypto policy than body. for (const AttachmentPart::Ptr &attachment : qAsConst(parts)) { AttachmentJob *attachJob = new AttachmentJob(attachment, q); qCDebug(MESSAGECOMPOSER_LOG) << "got a late attachment"; if (attachment->isSigned() && format) { qCDebug(MESSAGECOMPOSER_LOG) << "adding signjob for late attachment"; SignJob *sJob = new SignJob(q); sJob->setContent(nullptr); sJob->setCryptoMessageFormat(format); sJob->setSigningKeys(signers); sJob->appendSubjob(attachJob); if (attachment->isEncrypted()) { qCDebug(MESSAGECOMPOSER_LOG) << "adding sign + encrypt job for late attachment"; EncryptJob *eJob = new EncryptJob(q); eJob->setCryptoMessageFormat(format); eJob->setEncryptionKeys(keys); eJob->setRecipients(recipients); eJob->appendSubjob(sJob); multiJob->appendSubjob(eJob); } else { qCDebug(MESSAGECOMPOSER_LOG) << "Just signing late attachment"; multiJob->appendSubjob(sJob); } } else if (attachment->isEncrypted() && format) { // only encryption qCDebug(MESSAGECOMPOSER_LOG) << "just encrypting late attachment"; EncryptJob *eJob = new EncryptJob(q); eJob->setCryptoMessageFormat(format); eJob->setEncryptionKeys(keys); eJob->setRecipients(recipients); eJob->appendSubjob(attachJob); multiJob->appendSubjob(eJob); } else { qCDebug(MESSAGECOMPOSER_LOG) << "attaching plain non-crypto attachment"; AttachmentJob *attachJob = new AttachmentJob(attachment, q); multiJob->appendSubjob(attachJob); } } QObject::connect(multiJob, SIGNAL(finished(KJob*)), q, SLOT(attachmentsFinished(KJob*))); q->addSubjob(multiJob); multiJob->start(); } void ComposerPrivate::attachmentsFinished(KJob *job) { if (job->error()) { return; // KCompositeJob takes care of the error. } qCDebug(MESSAGECOMPOSER_LOG) << "composing final message with late attachments"; Q_ASSERT(dynamic_cast(job)); ContentJobBase *contentJob = static_cast(job); KMime::Content *content = contentJob->content(); KMime::Content *headers = contentJob->extraContent(); composeFinalStep(headers, content); } void ComposerPrivate::composeFinalStep(KMime::Content *headers, KMime::Content *content) { content->assemble(); QByteArray allData = headers->head() + content->encodedContent(); KMime::Message::Ptr resultMessage(new KMime::Message); resultMessage->setContent(allData); resultMessage->parse(); // Not strictly necessary. resultMessages.append(resultMessage); } Composer::Composer(QObject *parent) : JobBase(*new ComposerPrivate(this), parent) { Q_D(Composer); d->init(); } Composer::~Composer() { } QVector Composer::resultMessages() const { Q_D(const Composer); Q_ASSERT(d->finished); Q_ASSERT(!error()); QVector results = d->resultMessages; return results; } GlobalPart *Composer::globalPart() const { Q_D(const Composer); return d->globalPart; } InfoPart *Composer::infoPart() const { Q_D(const Composer); return d->infoPart; } TextPart *Composer::textPart() const { Q_D(const Composer); return d->textPart; } AttachmentPart::List Composer::attachmentParts() const { Q_D(const Composer); return d->attachmentParts; } void Composer::addAttachmentPart(AttachmentPart::Ptr part, bool autoresizeImage) { Q_D(Composer); Q_ASSERT(!d->started); Q_ASSERT(!d->attachmentParts.contains(part)); if (autoresizeImage) { MessageComposer::Utils resizeUtils; if (resizeUtils.resizeImage(part)) { MessageComposer::ImageScaling autoResizeJob; autoResizeJob.setName(part->name()); autoResizeJob.setMimetype(part->mimeType()); if (autoResizeJob.loadImageFromData(part->data())) { if (autoResizeJob.resizeImage()) { part->setData(autoResizeJob.imageArray()); part->setMimeType(autoResizeJob.mimetype()); part->setName(autoResizeJob.generateNewName()); resizeUtils.changeFileName(part); } } } } d->attachmentParts.append(part); } void Composer::addAttachmentParts(const AttachmentPart::List &parts, bool autoresizeImage) { for (const AttachmentPart::Ptr &part : parts) { addAttachmentPart(part, autoresizeImage); } } void Composer::removeAttachmentPart(AttachmentPart::Ptr part) { Q_D(Composer); Q_ASSERT(!d->started); const int numberOfElements = d->attachmentParts.removeAll(part); if (numberOfElements <= 0) { qCCritical(MESSAGECOMPOSER_LOG) << "Unknown attachment part" << part.data(); Q_ASSERT(false); return; } } void Composer::setSignAndEncrypt(const bool doSign, const bool doEncrypt) { Q_D(Composer); d->sign = doSign; d->encrypt = doEncrypt; } void Composer::setMessageCryptoFormat(Kleo::CryptoMessageFormat format) { Q_D(Composer); d->format = format; } void Composer::setSigningKeys(std::vector &signers) { Q_D(Composer); d->signers = signers; } void Composer::setEncryptionKeys(const QVector > > &encData) { Q_D(Composer); d->encData = encData; } void Composer::setNoCrypto(bool noCrypto) { Q_D(Composer); d->noCrypto = noCrypto; } bool Composer::finished() const { Q_D(const Composer); return d->finished; } bool Composer::autoSave() const { Q_D(const Composer); return d->autoSaving; } void Composer::setAutoSave(bool isAutoSave) { Q_D(Composer); d->autoSaving = isAutoSave; } void Composer::start() { Q_D(Composer); d->doStart(); } void Composer::slotResult(KJob *job) { Q_D(Composer); JobBase::slotResult(job); if (!hasSubjobs()) { d->finished = true; emitResult(); } } #include "moc_composer.cpp" diff --git a/messagecomposer/src/job/encryptjob.cpp b/messagecomposer/src/job/encryptjob.cpp index 13a50806..8e9c86bf 100644 --- a/messagecomposer/src/job/encryptjob.cpp +++ b/messagecomposer/src/job/encryptjob.cpp @@ -1,195 +1,264 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "job/encryptjob.h" #include "contentjobbase_p.h" +#include "job/protectedheaders.h" #include "utils/util_p.h" #include #include #include #include "messagecomposer_debug.h" -#include -#include - #include #include #include #include using namespace MessageComposer; class MessageComposer::EncryptJobPrivate : public ContentJobBasePrivate { public: EncryptJobPrivate(EncryptJob *qq) : ContentJobBasePrivate(qq) { } QStringList recipients; std::vector keys; Kleo::CryptoMessageFormat format; KMime::Content *content = nullptr; + KMime::Message *skeletonMessage = nullptr; + + bool protectedHeaders = true; + bool protectedHeadersObvoscate = false; // copied from messagecomposer.cpp bool binaryHint(Kleo::CryptoMessageFormat f) { switch (f) { case Kleo::SMIMEFormat: case Kleo::SMIMEOpaqueFormat: return true; default: case Kleo::OpenPGPMIMEFormat: case Kleo::InlineOpenPGPFormat: return false; } } GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f) { switch (f) { case Kleo::SMIMEOpaqueFormat: return GpgME::NormalSignatureMode; case Kleo::InlineOpenPGPFormat: return GpgME::Clearsigned; default: case Kleo::SMIMEFormat: case Kleo::OpenPGPMIMEFormat: return GpgME::Detached; } } Q_DECLARE_PUBLIC(EncryptJob) }; EncryptJob::EncryptJob(QObject *parent) : ContentJobBase(*new EncryptJobPrivate(this), parent) { } EncryptJob::~EncryptJob() { } void EncryptJob::setContent(KMime::Content *content) { Q_D(EncryptJob); d->content = content; d->content->assemble(); } void EncryptJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format) { Q_D(EncryptJob); d->format = format; } void EncryptJob::setEncryptionKeys(const std::vector &keys) { Q_D(EncryptJob); d->keys = keys; } void EncryptJob::setRecipients(const QStringList &recipients) { Q_D(EncryptJob); d->recipients = recipients; } +void EncryptJob::setSkeletonMessage(KMime::Message* skeletonMessage) +{ + Q_D(EncryptJob); + + d->skeletonMessage = skeletonMessage; +} + +void EncryptJob::setProtectedHeaders(bool protectedHeaders) +{ + Q_D(EncryptJob); + + d->protectedHeaders = protectedHeaders; +} + +void EncryptJob::setProtectedHeadersObvoscate(bool protectedHeadersObvoscate) +{ + Q_D(EncryptJob); + + d->protectedHeadersObvoscate = protectedHeadersObvoscate; +} + QStringList EncryptJob::recipients() const { Q_D(const EncryptJob); return d->recipients; } std::vector EncryptJob::encryptionKeys() const { Q_D(const EncryptJob); return d->keys; } -void EncryptJob::process() +void EncryptJob::doStart() { Q_D(EncryptJob); Q_ASSERT(d->resultContent == nullptr); // Not processed before. if (d->keys.size() == 0) { // should not happen---resolver should have dealt with it earlier qCDebug(MESSAGECOMPOSER_LOG) << "HELP! Encrypt job but have no keys to encrypt with."; return; } + // if setContent hasn't been called, we assume that a subjob was added + // and we want to use that + if (!d->content || !d->content->hasContent()) { + if (d->subjobContents.size() == 1) { + d->content = d->subjobContents.first(); + } + } + + if (d->protectedHeaders && d->skeletonMessage && d->format & Kleo::OpenPGPMIMEFormat) { + ProtectedHeadersJob *pJob = new ProtectedHeadersJob; + pJob->setContent(d->content); + pJob->setSkeletonMessage(d->skeletonMessage); + pJob->setObvoscate(d->protectedHeadersObvoscate); + QObject::connect(pJob, &ProtectedHeadersJob::finished, this, [d, pJob](KJob *job) { + if (job->error()) { + return; + } + d->content = pJob->content(); + }); + appendSubjob(pJob); + } + + ContentJobBase::doStart(); +} + +void EncryptJob::slotResult(KJob *job) +{ + Q_D(EncryptJob); + if (error()) { + ContentJobBase::slotResult(job); + return; + } + if (subjobs().size() == 2) { + auto pjob = static_cast(subjobs().last()); + if (pjob) { + Q_ASSERT(dynamic_cast(job)); + auto cjob = static_cast(job); + pjob->setContent(cjob->content()); + } + } + + ContentJobBase::slotResult(job); +} + +void EncryptJob::process() +{ + Q_D(EncryptJob); + // if setContent hasn't been called, we assume that a subjob was added // and we want to use that if (!d->content || !d->content->hasContent()) { Q_ASSERT(d->subjobContents.size() == 1); d->content = d->subjobContents.first(); } - //d->resultContent = new KMime::Content; - const QGpgME::Protocol *proto = nullptr; if (d->format & Kleo::AnyOpenPGP) { proto = QGpgME::openpgp(); } else if (d->format & Kleo::AnySMIME) { proto = QGpgME::smime(); } else { qCDebug(MESSAGECOMPOSER_LOG) << "HELP! Encrypt job but have protocol to encrypt with."; return; } Q_ASSERT(proto); // for now just do the main recipients QByteArray encryptedBody; QByteArray content; d->content->assemble(); if (d->format & Kleo::InlineOpenPGPFormat) { content = d->content->body(); } else { content = d->content->encodedContent(); } qCDebug(MESSAGECOMPOSER_LOG) << "got backend, starting job"; QGpgME::EncryptJob *eJob = proto->encryptJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat); QObject::connect(eJob, &QGpgME::EncryptJob::result, this, [this, d](const GpgME::EncryptionResult &result, const QByteArray &cipherText, const QString &auditLogAsHtml, const GpgME::Error &auditLogError) { if (result.error()) { setError(result.error().code()); setErrorText(QString::fromLocal8Bit(result.error().asString())); emitResult(); return; } d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, cipherText, d->format, false); emitResult(); }); eJob->start(d->keys, content, true); } diff --git a/messagecomposer/src/job/encryptjob.h b/messagecomposer/src/job/encryptjob.h index 8b0556df..fca9990c 100644 --- a/messagecomposer/src/job/encryptjob.h +++ b/messagecomposer/src/job/encryptjob.h @@ -1,69 +1,75 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MESSAGECOMPOSER_ENCRYPTJOB_H #define MESSAGECOMPOSER_ENCRYPTJOB_H #include "abstractencryptjob.h" #include "contentjobbase.h" #include "infopart.h" #include "messagecomposer_export.h" #include #include #include namespace KMime { class Content; } namespace MessageComposer { class EncryptJobPrivate; /** Encrypt the contents of a message . Used as a subjob of CryptoMessage */ class MESSAGECOMPOSER_EXPORT EncryptJob : public ContentJobBase, public MessageComposer::AbstractEncryptJob { Q_OBJECT public: explicit EncryptJob(QObject *parent = nullptr); ~EncryptJob() override; void setContent(KMime::Content *content); void setCryptoMessageFormat(Kleo::CryptoMessageFormat format); void setEncryptionKeys(const std::vector &keys) override; void setRecipients(const QStringList &rec) override; + void setSkeletonMessage(KMime::Message *skeletonMessage); + + void setProtectedHeaders(bool protectedHeaders); + void setProtectedHeadersObvoscate(bool protectedHeadersObvoscate); std::vector encryptionKeys() const override; QStringList recipients() const override; protected Q_SLOTS: + void doStart() override; + void slotResult(KJob *job) override; void process() override; private: Q_DECLARE_PRIVATE(EncryptJob) }; } #endif diff --git a/messagecomposer/src/job/protectedheaders.cpp b/messagecomposer/src/job/protectedheaders.cpp new file mode 100644 index 00000000..1321691b --- /dev/null +++ b/messagecomposer/src/job/protectedheaders.cpp @@ -0,0 +1,167 @@ +/* + Copyright (C) 2020 Sandro Knauß + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "job/protectedheaders.h" + +#include "contentjobbase_p.h" +#include "job/singlepartjob.h" +#include "utils/util_p.h" + +#include "messagecomposer_debug.h" + +#include +#include + +using namespace MessageComposer; + +class MessageComposer::ProtectedHeadersJobPrivate : public ContentJobBasePrivate +{ +public: + ProtectedHeadersJobPrivate(ProtectedHeadersJob *qq) + : ContentJobBasePrivate(qq) + { + } + + KMime::Content *content = nullptr; + KMime::Message *skeletonMessage = nullptr; + + bool obvoscate = false; + + Q_DECLARE_PUBLIC(ProtectedHeadersJob) +}; + +ProtectedHeadersJob::ProtectedHeadersJob(QObject *parent) + : ContentJobBase(*new ProtectedHeadersJobPrivate(this), parent) +{ +} + +ProtectedHeadersJob::~ProtectedHeadersJob() +{ +} + +void ProtectedHeadersJob::setContent(KMime::Content *content) +{ + Q_D(ProtectedHeadersJob); + + d->content = content; + if (content) + { + d->content->assemble(); + } +} + +void ProtectedHeadersJob::setSkeletonMessage(KMime::Message *skeletonMessage) +{ + Q_D(ProtectedHeadersJob); + + d->skeletonMessage = skeletonMessage; +} + +void ProtectedHeadersJob::setObvoscate(bool obvoscate) +{ + Q_D(ProtectedHeadersJob); + + d->obvoscate = obvoscate; +} + +void ProtectedHeadersJob::doStart() { + Q_D(ProtectedHeadersJob); + Q_ASSERT(d->resultContent == nullptr); // Not processed before. + Q_ASSERT(d->skeletonMessage); // We need a skeletonMessage to proceed + + auto subject = d->skeletonMessage->header(); + if (d->obvoscate && subject) { + // Create protected header lagacy mimepart with replaced headers + SinglepartJob *cjob = new SinglepartJob; + cjob->contentType()->setMimeType("text/plain"); + cjob->contentType()->setCharset(subject->rfc2047Charset()); + cjob->contentType()->setParameter(QStringLiteral("protected-headers"), QStringLiteral("v1")); + cjob->contentDisposition()->setDisposition(KMime::Headers::contentDisposition::CDinline); + cjob->setData(subject->type() + QByteArray(": ") + subject->asUnicodeString().toUtf8()); + + QObject::connect(cjob, &SinglepartJob::finished, this, [d, cjob](KJob *job) { + KMime::Content *mixedPart = new KMime::Content(); + const QByteArray boundary = KMime::multiPartBoundary(); + mixedPart->contentType()->setMimeType("multipart/mixed"); + mixedPart->contentType()->setBoundary(boundary); + mixedPart->addContent(cjob->content()); + + // if setContent hasn't been called, we assume that a subjob was added + // and we want to use that + if (!d->content || !d->content->hasContent()) { + Q_ASSERT(d->subjobContents.size() == 1); + d->content = d->subjobContents.first(); + } + + mixedPart->addContent(d->content); + d->content = mixedPart; + }); + appendSubjob(cjob); + } + + ContentJobBase::doStart(); +} + +void ProtectedHeadersJob::process() +{ + Q_D(ProtectedHeadersJob); + + // if setContent hasn't been called, we assume that a subjob was added + // and we want to use that + if (!d->content || !d->content->hasContent()) { + Q_ASSERT(d->subjobContents.size() == 1); + d->content = d->subjobContents.first(); + } + + auto subject = d->skeletonMessage->header(); + const auto headers = d->skeletonMessage->headers(); + for (const auto &header: headers) { + const QByteArray headerType(header->type()); + if (headerType.startsWith("X-KMail-")) { + continue; + } + if (headerType == "MIME-Version") { + continue; + } + if (headerType == "Bcc") { + continue; + } + if (headerType.startsWith("Content-")) { + continue; + } + if (headerType == "Subject") { + KMime::Headers::Subject *copySubject = new KMime::Headers::Subject(); + copySubject->from7BitString(subject->as7BitString(false)); + d->content->appendHeader(copySubject); + } else { + d->content->appendHeader(header); + } + } + + if (d->obvoscate && subject) { + subject->clear(); + subject->from7BitString("..."); + } + auto contentType = d->content->header(); + contentType->setParameter(QStringLiteral("protected-headers"), QStringLiteral("v1")); + + d->resultContent = d->content; + + emitResult(); +} diff --git a/messagecomposer/src/job/encryptjob.h b/messagecomposer/src/job/protectedheaders.h similarity index 53% copy from messagecomposer/src/job/encryptjob.h copy to messagecomposer/src/job/protectedheaders.h index 8b0556df..2ece55b0 100644 --- a/messagecomposer/src/job/encryptjob.h +++ b/messagecomposer/src/job/protectedheaders.h @@ -1,69 +1,63 @@ /* - Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net - Copyright (c) 2009 Leo Franchi + Copyright (C) 2020 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef MESSAGECOMPOSER_ENCRYPTJOB_H -#define MESSAGECOMPOSER_ENCRYPTJOB_H +#ifndef MESSAGECOMPOSER_PROTECTEDHEADERSJOB_H +#define MESSAGECOMPOSER_PROTECTEDHEADERSJOB_H -#include "abstractencryptjob.h" #include "contentjobbase.h" #include "infopart.h" #include "messagecomposer_export.h" -#include - -#include -#include - namespace KMime { class Content; } namespace MessageComposer { -class EncryptJobPrivate; +class ProtectedHeadersJobPrivate; /** - Encrypt the contents of a message . - Used as a subjob of CryptoMessage + Copies headers from skeleton message to content. + It is used for Protected Headers for Cryptographic E-mail + currently a draft for RFC: + https://datatracker.ietf.org/doc/draft-autocrypt-lamps-protected-headers/ + Used as a subjob of EncryptJob/SignJob/SignEncryptJob */ -class MESSAGECOMPOSER_EXPORT EncryptJob : public ContentJobBase, public MessageComposer::AbstractEncryptJob +class MESSAGECOMPOSER_EXPORT ProtectedHeadersJob : public ContentJobBase { Q_OBJECT public: - explicit EncryptJob(QObject *parent = nullptr); - ~EncryptJob() override; + explicit ProtectedHeadersJob(QObject *parent = nullptr); + ~ProtectedHeadersJob() override; void setContent(KMime::Content *content); - void setCryptoMessageFormat(Kleo::CryptoMessageFormat format); - void setEncryptionKeys(const std::vector &keys) override; - void setRecipients(const QStringList &rec) override; + void setSkeletonMessage(KMime::Message *skeletonMessage); - std::vector encryptionKeys() const override; - QStringList recipients() const override; + void setObvoscate(bool obvoscate); protected Q_SLOTS: + void doStart() override; void process() override; private: - Q_DECLARE_PRIVATE(EncryptJob) + Q_DECLARE_PRIVATE(ProtectedHeadersJob) }; } #endif