From d3228a2899992370e3fb19609eb1433d02de0f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Nicole?= Date: Wed, 21 Feb 2018 21:11:01 +0100 Subject: T7024 fix: implement attachment-based forwarding Summary: Implement attachment-based forwarding. Some notes: - `loadAsDraft` was removed in favor of new enum `loadType` in QML, and callback based generic programming in C++ Reviewers: cmollekopf Tags: #kube Maniphest Tasks: T7024 Differential Revision: https://phabricator.kde.org/D10676 --- framework/src/domain/composercontroller.cpp | 66 +++++++++++++--------- framework/src/domain/composercontroller.h | 13 ++++- framework/src/domain/mime/mailtemplates.cpp | 36 +++++++++++- framework/src/domain/mime/mailtemplates.h | 1 + .../src/domain/mime/tests/mailtemplatetest.cpp | 24 ++++++++ 5 files changed, 109 insertions(+), 31 deletions(-) (limited to 'framework/src') diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp index 60e955ea..4bfc34ab 100644 --- a/framework/src/domain/composercontroller.cpp +++ b/framework/src/domain/composercontroller.cpp @@ -272,14 +272,8 @@ static QStringList getStringListFromAddresses(const KMime::Types::Mailbox::List void ComposerController::addAttachmentPart(KMime::Content *partToAttach) { QVariantMap map; - if (partToAttach->contentType()->mimeType() == "multipart/digest" || - partToAttach->contentType()->mimeType() == "message/rfc822") { - // if it is a digest or a full message, use the encodedContent() of the attachment, - // which already has the proper headers - map.insert("content", partToAttach->encodedContent()); - } else { - map.insert("content", partToAttach->decodedContent()); - } + // May need special care for the multipart/digest MIME type + map.insert("content", partToAttach->decodedContent()); map.insert("mimetype", partToAttach->contentType()->mimeType()); QMimeDatabase db; @@ -337,7 +331,40 @@ void ComposerController::setMessage(const KMime::Message::Ptr &msg) setExistingMessage(msg); } -void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) +void ComposerController::loadDraft(const QVariant &message) { + loadMessage(message, [this] (const KMime::Message::Ptr &mail) { + mRemoveDraft = true; + setMessage(mail); + }); +} + +void ComposerController::loadReply(const QVariant &message) { + loadMessage(message, [this] (const KMime::Message::Ptr &mail) { + //Find all personal email addresses to exclude from reply + KMime::Types::AddrSpecList me; + auto list = static_cast(mIdentitySelector.data())->getAllAddresses(); + for (const auto &a : list) { + KMime::Types::Mailbox mb; + mb.setAddress(a); + me << mb.addrSpec(); + } + + MailTemplates::reply(mail, [this] (const KMime::Message::Ptr &reply) { + //We assume reply + setMessage(reply); + }, me); + }); +} + +void ComposerController::loadForward(const QVariant &message) { + loadMessage(message, [this] (const KMime::Message::Ptr &mail) { + MailTemplates::forward(mail, [this] (const KMime::Message::Ptr &fwdMessage) { + setMessage(fwdMessage); + }); + }); +} + +void ComposerController::loadMessage(const QVariant &message, std::function callback) { using namespace Sink; using namespace Sink::ApplicationDomain; @@ -346,8 +373,7 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) Q_ASSERT(msg); Query query(*msg); query.request(); - Store::fetchOne(query).then([this, loadAsDraft](const Mail &mail) { - mRemoveDraft = loadAsDraft; + Store::fetchOne(query).then([this, callback](const Mail &mail) { setExistingMail(mail); const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); @@ -355,23 +381,7 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) KMime::Message::Ptr mail(new KMime::Message); mail->setContent(mailData); mail->parse(); - if (loadAsDraft) { - setMessage(mail); - } else { - //Find all personal email addresses to exclude from reply - KMime::Types::AddrSpecList me; - auto list = static_cast(mIdentitySelector.data())->getAllAddresses(); - for (const auto &a : list) { - KMime::Types::Mailbox mb; - mb.setAddress(a); - me << mb.addrSpec(); - } - - MailTemplates::reply(mail, [this] (const KMime::Message::Ptr &reply) { - //We assume reply - setMessage(reply); - }, me); - } + callback(mail); } else { qWarning() << "Retrieved empty message"; } diff --git a/framework/src/domain/composercontroller.h b/framework/src/domain/composercontroller.h index 0ace365b..ac83dfd3 100644 --- a/framework/src/domain/composercontroller.h +++ b/framework/src/domain/composercontroller.h @@ -81,12 +81,21 @@ class ComposerController : public Kube::Controller KUBE_CONTROLLER_ACTION(saveAsDraft) public: + enum LoadType { + Draft, + Reply, + Forward, + }; + Q_ENUMS(LoadType); + explicit ComposerController(); Completer *recipientCompleter() const; Selector *identitySelector() const; - Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft); + Q_INVOKABLE void loadDraft(const QVariant &message); + Q_INVOKABLE void loadReply(const QVariant &message); + Q_INVOKABLE void loadForward(const QVariant &message); public slots: virtual void clear() Q_DECL_OVERRIDE; @@ -95,6 +104,8 @@ private slots: void findPersonalKey(); private: + void loadMessage(const QVariant &message, std::function callback); + void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); void setMessage(const QSharedPointer &msg); void addAttachmentPart(KMime::Content *partToAttach); diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp index 9af15e91..8e644b34 100644 --- a/framework/src/domain/mime/mailtemplates.cpp +++ b/framework/src/domain/mime/mailtemplates.cpp @@ -864,6 +864,34 @@ void MailTemplates::reply(const KMime::Message::Ptr &origMsg, const std::functio }); } +void MailTemplates::forward(const KMime::Message::Ptr &origMsg, const std::function &callback) +{ + KMime::Message::Ptr wrapperMsg(new KMime::Message); + + wrapperMsg->to()->clear(); + wrapperMsg->cc()->clear(); + + wrapperMsg->subject()->fromUnicodeString(forwardSubject(origMsg->subject()->asUnicodeString()), "utf-8"); + + const QByteArray refStr = getRefStr(origMsg); + if (!refStr.isEmpty()) { + wrapperMsg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); + } + + KMime::Content* fwdAttachment = new KMime::Content; + + fwdAttachment->contentDisposition()->setDisposition(KMime::Headers::CDinline); + fwdAttachment->contentType()->setMimeType("message/rfc822"); + fwdAttachment->contentDisposition()->setFilename(origMsg->subject()->asUnicodeString() + ".eml"); + // The mail was parsed in loadMessage before, so no need to assemble it + fwdAttachment->setBody(origMsg->encodedContent()); + + wrapperMsg->addContent(fwdAttachment); + wrapperMsg->assemble(); + + callback(wrapperMsg); +} + QString MailTemplates::plaintextContent(const KMime::Message::Ptr &msg) { MimeTreeParser::ObjectTreeParser otp; @@ -899,10 +927,14 @@ static KMime::Content *createAttachmentPart(const QByteArray &content, const QSt } else { part->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment); } + part->contentType(true)->setMimeType(mimeType); part->contentType(true)->setName(name, "utf-8"); - //Just always encode attachments base64 so it's safe for binary data - part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64); + // Just always encode attachments base64 so it's safe for binary data, + // except when it's another message + if(mimeType != "message/rfc822") { + part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64); + } part->setBody(content); return part; } diff --git a/framework/src/domain/mime/mailtemplates.h b/framework/src/domain/mime/mailtemplates.h index 21efb5a0..9447e169 100644 --- a/framework/src/domain/mime/mailtemplates.h +++ b/framework/src/domain/mime/mailtemplates.h @@ -35,6 +35,7 @@ struct Attachment { namespace MailTemplates { void reply(const KMime::Message::Ptr &origMsg, const std::function &callback, const KMime::Types::AddrSpecList &me = {}); + void forward(const KMime::Message::Ptr &origMsg, const std::function &callback); QString plaintextContent(const KMime::Message::Ptr &origMsg); QString body(const KMime::Message::Ptr &msg, bool &isHtml); KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList &attachments, const std::vector &signingKeys = {}, const std::vector &encryptionKeys = {}); diff --git a/framework/src/domain/mime/tests/mailtemplatetest.cpp b/framework/src/domain/mime/tests/mailtemplatetest.cpp index 302fae95..8d044608 100644 --- a/framework/src/domain/mime/tests/mailtemplatetest.cpp +++ b/framework/src/domain/mime/tests/mailtemplatetest.cpp @@ -224,6 +224,30 @@ private slots: QCOMPARE(result->cc()->addresses(), l); } + void testForwardAsAttachment() + { + auto msg = readMail("plaintext.mbox"); + KMime::Message::Ptr result; + MailTemplates::forward(msg, [&] (const KMime::Message::Ptr &r) { + result = r; + }); + QTRY_VERIFY(result); + QCOMPARE(result->subject(false)->asUnicodeString(), {"FW: A random subject with alternative contenttype"}); + QCOMPARE(result->to()->addresses(), {}); + QCOMPARE(result->cc()->addresses(), {}); + + auto attachments = result->attachments(); + QCOMPARE(attachments.size(), 1); + auto attachment = attachments[0]; + QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); + QCOMPARE(attachment->contentDisposition(false)->filename(), {"A random subject with alternative contenttype.eml"}); + QVERIFY(attachment->bodyIsMessage()); + + attachment->parse(); + auto origMsg = attachment->bodyAsMessage(); + QCOMPARE(origMsg->subject(false)->asUnicodeString(), {"A random subject with alternative contenttype"}); + } + void testCreatePlainMail() { QStringList to = {{"to@example.org"}}; -- cgit v1.2.3