diff --git a/messagecomposer/autotests/signencrypttest.cpp b/messagecomposer/autotests/signencrypttest.cpp index bf1e3ab0..a9baed2e 100644 --- a/messagecomposer/autotests/signencrypttest.cpp +++ b/messagecomposer/autotests/signencrypttest.cpp @@ -1,150 +1,199 @@ /* - 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. */ #include "signencrypttest.h" #include #include #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include #include #include #include -#include +#include #include -#include +#include #include #include -#include #include -#include - QTEST_MAIN(SignEncryptTest) using namespace MessageComposer; void SignEncryptTest::initTestCase() { Test::setupEnv(); } +void SignEncryptTest::testContent_data() +{ + QTest::addColumn("cryptoMessageFormat"); + QTest::addColumn("error"); + + QTest::newRow("OpenPGPMimeFormat") << (int) Kleo::OpenPGPMIMEFormat << QString(); + QTest::newRow("InlineOpenPGPFormat") << (int) Kleo::InlineOpenPGPFormat << QString(); + QTest::newRow("SMIMEFormat") << (int) Kleo::SMIMEFormat << QStringLiteral("Not implemented"); + QTest::newRow("SMIMEOpaqueFormat") << (int) Kleo::SMIMEOpaqueFormat << QStringLiteral("Not implemented"); +} + void SignEncryptTest::testContent() { + QFETCH(int, cryptoMessageFormat); + QFETCH(QString, error); + std::vector< GpgME::Key > keys = Test::getKeys(); + const QString data(QString::fromLocal8Bit("one flew over the cuckoo's nest")); - Composer *composer = new Composer; - SignJob *sJob = new SignJob(composer); - EncryptJob *eJob = new EncryptJob(composer); + Composer composer; - QVERIFY(composer); - QVERIFY(sJob); - QVERIFY(eJob); + const QVector charsets = {"us-ascii"}; + composer.globalPart()->setCharsets(charsets); - QVector charsets; - charsets << "us-ascii"; - composer->globalPart()->setCharsets(charsets); - TextPart *part = new TextPart(this); - part->setWordWrappingEnabled(false); - part->setCleanPlainText(QStringLiteral("one flew over the cuckoo's nest")); + TextPart part; + part.setWordWrappingEnabled(false); + part.setCleanPlainText(data); - MainTextJob *mainTextJob = new MainTextJob(part, composer); + auto mainTextJob = new MainTextJob(&part, &composer); + auto seJob = new SignEncryptJob(&composer); - QVERIFY(composer); QVERIFY(mainTextJob); VERIFYEXEC(mainTextJob); - QStringList recipients; - recipients << QString::fromLocal8Bit("test@kolab.org"); + const QStringList recipients = {QString::fromLocal8Bit("test@kolab.org")}; + + seJob->setContent(mainTextJob->content()); + seJob->setSigningKeys(keys); + seJob->setCryptoMessageFormat((Kleo::CryptoMessageFormat)cryptoMessageFormat); + seJob->setRecipients(recipients); + seJob->setEncryptionKeys(keys); - sJob->setContent(mainTextJob->content()); - sJob->setSigningKeys(keys); - sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + if (!error.isEmpty()) { + QVERIFY(!seJob->exec()); + QCOMPARE(seJob->errorString(), error); + return; + } - eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); - eJob->setRecipients(recipients); - eJob->setEncryptionKeys(keys); + VERIFYEXEC(seJob); + KMime::Content *result = seJob->content(); + QVERIFY(result); + result->assemble(); - eJob->appendSubjob(sJob); + ComposerTestUtil::verifySignatureAndEncryption( + result, + data.toUtf8(), + (Kleo::CryptoMessageFormat)cryptoMessageFormat, + false, + true); - VERIFYEXEC(eJob); + delete result; +} + +void SignEncryptTest::testContentSubjobChained() +{ + std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys(); - KMime::Content *result = eJob->content(); + const QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); + KMime::Message skeletonMessage; + + auto content = new KMime::Content; + content->contentType(true)->setMimeType("text/plain"); + content->setBody(data); + + auto tJob = new TransparentJob; + tJob->setContent(content); + + const QStringList recipients = {QString::fromLocal8Bit("test@kolab.org")}; + + Composer composer; + auto seJob = new SignEncryptJob(&composer); + + seJob->setSigningKeys(keys); + seJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + seJob->setRecipients(recipients); + seJob->setEncryptionKeys(keys); + seJob->setSkeletonMessage(&skeletonMessage); + seJob->appendSubjob(tJob); + + VERIFYEXEC(seJob); + KMime::Content *result = seJob->content(); QVERIFY(result); result->assemble(); - qDebug() << "result:" << result->encodedContent(); - ComposerTestUtil::verifySignatureAndEncryption( result, - QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8(), - Kleo::OpenPGPMIMEFormat); + data, + Kleo::OpenPGPMIMEFormat, + false, + true); + + delete result; } + void SignEncryptTest::testHeaders() { std::vector< GpgME::Key > keys = Test::getKeys(); - Composer *composer = new Composer; - SignJob *sJob = new SignJob(composer); - EncryptJob *eJob = new EncryptJob(composer); + Composer composer; + auto seJob = new SignEncryptJob(&composer); - QVERIFY(composer); - QVERIFY(sJob); - QVERIFY(eJob); + QVERIFY(seJob); - QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); - KMime::Content *content = new KMime::Content; + const QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8()); + auto content = new KMime::Content; content->setBody(data); - QStringList recipients; - recipients << QString::fromLocal8Bit("test@kolab.org"); + const QStringList recipients = {QString::fromLocal8Bit("test@kolab.org")}; - sJob->setContent(content); - sJob->setSigningKeys(keys); - sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + seJob->setContent(content); + seJob->setSigningKeys(keys); + seJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); + seJob->setRecipients(recipients); + seJob->setEncryptionKeys(keys); - eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat); - eJob->setRecipients(recipients); - eJob->setEncryptionKeys(keys); + VERIFYEXEC(seJob); - eJob->appendSubjob(sJob); - - VERIFYEXEC(eJob); - - KMime::Content *result = eJob->content(); + KMime::Content *result = seJob->content(); QVERIFY(result); result->assemble(); - QByteArray mimeType("multipart/encrypted"); - QByteArray charset("ISO-8859-1"); + QFile f(QStringLiteral("test")); + QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate)); + const QByteArray encodedContent(result->encodedContent()); + f.write(encodedContent); + if (!encodedContent.endsWith('\n')) { + f.write("\n"); + } + f.close(); QVERIFY(result->contentType(false)); - QCOMPARE(result->contentType()->mimeType(), mimeType); - QCOMPARE(result->contentType()->charset(), charset); + QCOMPARE(result->contentType()->mimeType(), "multipart/encrypted"); + QCOMPARE(result->contentType()->charset(), "ISO-8859-1"); QCOMPARE(result->contentType()->parameter(QString::fromLocal8Bit("protocol")), QString::fromLocal8Bit("application/pgp-encrypted")); QCOMPARE(result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit); + + delete result; } diff --git a/messagecomposer/autotests/signencrypttest.h b/messagecomposer/autotests/signencrypttest.h index 711ec46c..0d8c3751 100644 --- a/messagecomposer/autotests/signencrypttest.h +++ b/messagecomposer/autotests/signencrypttest.h @@ -1,38 +1,39 @@ /* - 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 SIGNENCRYPTTEST_H #define SIGNENCRYPTTEST_H #include class SignEncryptTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); private Q_SLOTS: + void testContent_data(); void testContent(); + void testContentSubjobChained(); void testHeaders(); }; #endif diff --git a/messagecomposer/src/composer/composer.cpp b/messagecomposer/src/composer/composer.cpp index d2ed6b8b..38fee608 100644 --- a/messagecomposer/src/composer/composer.cpp +++ b/messagecomposer/src/composer/composer.cpp @@ -1,604 +1,605 @@ /* 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); if (!encrypt) { sJob->setSkeletonMessage(skeletonMessage); } 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); + seJob->setSkeletonMessage(skeletonMessage); 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/signencryptjob.cpp b/messagecomposer/src/job/signencryptjob.cpp index 98e0262a..122ae6be 100644 --- a/messagecomposer/src/job/signencryptjob.cpp +++ b/messagecomposer/src/job/signencryptjob.cpp @@ -1,215 +1,282 @@ /* 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/signencryptjob.h" #include "contentjobbase_p.h" -#include "utils/util.h" +#include "job/protectedheaders.h" #include "utils/util_p.h" #include #include #include #include "messagecomposer_debug.h" #include #include #include #include #include #include #include using namespace MessageComposer; class MessageComposer::SignEncryptJobPrivate : public ContentJobBasePrivate { public: SignEncryptJobPrivate(SignEncryptJob *qq) : ContentJobBasePrivate(qq) { } std::vector signers; std::vector encKeys; QStringList recipients; 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; } } Q_DECLARE_PUBLIC(SignEncryptJob) }; SignEncryptJob::SignEncryptJob(QObject *parent) : ContentJobBase(*new SignEncryptJobPrivate(this), parent) { } SignEncryptJob::~SignEncryptJob() { } void SignEncryptJob::setContent(KMime::Content *content) { Q_D(SignEncryptJob); Q_ASSERT(content); d->content = content; } void SignEncryptJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format) { Q_D(SignEncryptJob); // There *must* be a concrete format set at this point. Q_ASSERT(format == Kleo::OpenPGPMIMEFormat || format == Kleo::InlineOpenPGPFormat || format == Kleo::SMIMEFormat || format == Kleo::SMIMEOpaqueFormat); d->format = format; } void SignEncryptJob::setSigningKeys(std::vector &signers) { Q_D(SignEncryptJob); d->signers = signers; } KMime::Content *SignEncryptJob::origContent() { Q_D(SignEncryptJob); return d->content; } void SignEncryptJob::setEncryptionKeys(const std::vector &keys) { Q_D(SignEncryptJob); d->encKeys = keys; } void SignEncryptJob::setRecipients(const QStringList &recipients) { Q_D(SignEncryptJob); d->recipients = recipients; } +void SignEncryptJob::setSkeletonMessage(KMime::Message *skeletonMessage) +{ + Q_D(SignEncryptJob); + + d->skeletonMessage = skeletonMessage; +} + +void SignEncryptJob::setProtectedHeaders(bool protectedHeaders) +{ + Q_D(SignEncryptJob); + + d->protectedHeaders = protectedHeaders; +} + +void SignEncryptJob::setProtectedHeadersObvoscate(bool protectedHeadersObvoscate) +{ + Q_D(SignEncryptJob); + + d->protectedHeadersObvoscate = protectedHeadersObvoscate; +} + QStringList SignEncryptJob::recipients() const { Q_D(const SignEncryptJob); return d->recipients; } std::vector SignEncryptJob::encryptionKeys() const { Q_D(const SignEncryptJob); return d->encKeys; } +void SignEncryptJob::doStart() +{ + Q_D(SignEncryptJob); + Q_ASSERT(d->resultContent == nullptr); // Not processed before. + + 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 SignEncryptJob::slotResult(KJob *job) +{ + Q_D(SignEncryptJob); + if (error()) { + ContentJobBase::slotResult(job); + return; + } + if (subjobs().size() == 2) { + auto pjob = static_cast(subjobs().last()); + if (pjob) { + auto cjob = dynamic_cast(job); + Q_ASSERT(cjob); + pjob->setContent(cjob->content()); + } + } + + ContentJobBase::slotResult(job); +} + void SignEncryptJob::process() { Q_D(SignEncryptJob); Q_ASSERT(d->resultContent == nullptr); // Not processed before. // 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(); } const QGpgME::Protocol *proto = nullptr; if (d->format & Kleo::AnyOpenPGP) { proto = QGpgME::openpgp(); } else if (d->format & Kleo::AnySMIME) { proto = QGpgME::smime(); } else { return; } Q_ASSERT(proto); //d->resultContent = new KMime::Content; qCDebug(MESSAGECOMPOSER_LOG) << "creating signencrypt from:" << proto->name() << proto->displayName(); std::unique_ptr job(proto->signEncryptJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat)); QByteArray encBody; d->content->assemble(); // replace simple LFs by CRLFs for all MIME supporting CryptPlugs // according to RfC 2633, 3.1.1 Canonicalization QByteArray content; if (d->format & Kleo::InlineOpenPGPFormat) { content = d->content->body(); } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) { content = KMime::LFtoCRLF(d->content->encodedContent()); } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged content = d->content->encodedContent(); } // FIXME: Make this async const std::pair res = job->exec(d->signers, d->encKeys, content, false, encBody); // exec'ed jobs don't delete themselves job->deleteLater(); if (res.first.error()) { qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << res.first.error().asString(); setError(res.first.error().code()); setErrorText(QString::fromLocal8Bit(res.first.error().asString())); emitResult(); return; } if (res.second.error()) { qCDebug(MESSAGECOMPOSER_LOG) << "encrypting failed:" << res.second.error().asString(); setError(res.second.error().code()); setErrorText(QString::fromLocal8Bit(res.second.error().asString())); emitResult(); return; } QByteArray signatureHashAlgo = res.first.createdSignature(0).hashAlgorithmAsString(); d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, encBody, d->format, false, signatureHashAlgo); emitResult(); } diff --git a/messagecomposer/src/job/signencryptjob.h b/messagecomposer/src/job/signencryptjob.h index 3fd33866..282abbb4 100644 --- a/messagecomposer/src/job/signencryptjob.h +++ b/messagecomposer/src/job/signencryptjob.h @@ -1,73 +1,80 @@ /* 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_SIGNENCRYPTJOB_H #define MESSAGECOMPOSER_SIGNENCRYPTJOB_H #include "abstractencryptjob.h" #include "contentjobbase.h" #include "part/infopart.h" #include "messagecomposer_export.h" #include #include #include namespace KMime { class Content; } namespace MessageComposer { class SignEncryptJobPrivate; /** Signs and encrypt the contents of a message. Used when doing inline pgp sign+encrypt */ class MESSAGECOMPOSER_EXPORT SignEncryptJob : public ContentJobBase, public MessageComposer::AbstractEncryptJob { Q_OBJECT public: explicit SignEncryptJob(QObject *parent = nullptr); ~SignEncryptJob() override; void setContent(KMime::Content *content); void setCryptoMessageFormat(Kleo::CryptoMessageFormat format); void setSigningKeys(std::vector &signers); 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); + Q_REQUIRED_RESULT std::vector encryptionKeys() const override; Q_REQUIRED_RESULT QStringList recipients() const override; Q_REQUIRED_RESULT KMime::Content *origContent(); protected Q_SLOTS: + void doStart() override; + void slotResult(KJob *job) override; void process() override; private: Q_DECLARE_PRIVATE(SignEncryptJob) }; } #endif