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/qml/Icons.qml | 1 + framework/qml/MailViewer.qml | 36 ++++++++---- framework/qml/Messages.qml | 1 + 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 ++++++++ 8 files changed, 137 insertions(+), 41 deletions(-) (limited to 'framework') diff --git a/framework/qml/Icons.qml b/framework/qml/Icons.qml index 3636a192..2afe840e 100644 --- a/framework/qml/Icons.qml +++ b/framework/qml/Icons.qml @@ -41,6 +41,7 @@ Item { property string edit: "document-edit" property string edit_inverted: "document-edit-inverted" property string replyToSender: "mail-reply-sender" + property string forward: "mail-forward" property string outbox: "mail-folder-outbox" property string outbox_inverted: "mail-folder-outbox-inverted" property string copy: "edit-copy" diff --git a/framework/qml/MailViewer.qml b/framework/qml/MailViewer.qml index 1a832470..f52e694d 100644 --- a/framework/qml/MailViewer.qml +++ b/framework/qml/MailViewer.qml @@ -370,24 +370,40 @@ Rectangle { } } - Kube.IconButton { - visible: !model.trash - anchors{ + Grid { + anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: Kube.Units.largeSpacing } - activeFocusOnTab: false + columns: 2 - iconName: model.draft ? Kube.Icons.edit : Kube.Icons.replyToSender - onClicked: { - if (model.draft) { - Kube.Fabric.postMessage(Kube.Messages.edit, {"mail": model.mail, "isDraft": model.draft}) - } else { - Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail, "isDraft": model.draft}) + Kube.IconButton { + visible: !model.trash + activeFocusOnTab: false + + iconName: model.draft ? Kube.Icons.edit : Kube.Icons.replyToSender + onClicked: { + if (model.draft) { + Kube.Fabric.postMessage(Kube.Messages.edit, {"mail": model.mail}) + } else { + Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail}) + } } } + + Kube.IconButton { + visible: !model.trash && !model.draft + activeFocusOnTab: false + + iconName: Kube.Icons.forward + onClicked: { + Kube.Fabric.postMessage(Kube.Messages.forward, {"mail": model.mail}) + } + } + } + } Rectangle { anchors.fill: parent diff --git a/framework/qml/Messages.qml b/framework/qml/Messages.qml index 18db51ff..9df83863 100644 --- a/framework/qml/Messages.qml +++ b/framework/qml/Messages.qml @@ -44,6 +44,7 @@ Item { property string search: "search" property string synchronize: "synchronize" property string reply: "reply" + property string forward: "forward" property string edit: "edit" property string compose: "compose" property string sendOutbox: "sendOutbox" 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