diff --git a/CMakeLists.txt b/CMakeLists.txt index 6417e31..a6c0766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.15.48") +set(PIM_VERSION "5.15.49") project(mailcommon VERSION ${PIM_VERSION}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9637f52..a3bca7f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,6 +174,7 @@ set(libmailcommon_widget_SRCS set(libmailcommon_mdn_SRCS mdn/sendmdnhandler.cpp mdn/mdnstateattribute.cpp + mdn/mdnadvicehelper.cpp ) set(libmailcommon_util_SRCS diff --git a/src/filter/filteractions/filteraction.cpp b/src/filter/filteractions/filteraction.cpp index 63a94cb..3e0df6c 100644 --- a/src/filter/filteractions/filteraction.cpp +++ b/src/filter/filteractions/filteraction.cpp @@ -12,6 +12,7 @@ #include "filter/mdnadvicedialog.h" #include "util/mailutil.h" #include "mailcommon_debug.h" +#include "mdn/mdnadvicehelper.h" #include #include diff --git a/src/filter/mdnadvicedialog.cpp b/src/filter/mdnadvicedialog.cpp index b129172..d2cc160 100644 --- a/src/filter/mdnadvicedialog.cpp +++ b/src/filter/mdnadvicedialog.cpp @@ -8,81 +8,17 @@ #include "kernel/mailkernel.h" #include "mailcommon_debug.h" -#include -#include -using MessageComposer::MessageFactoryNG; - -#include -#include #include -#include -#include - #include #include -#include #include #include using namespace MailCommon; -MDNAdviceHelper *MDNAdviceHelper::s_instance = nullptr; - -static const struct { - const char *dontAskAgainID; - bool canDeny; - const char *text; -} mdnMessageBoxes[] = { - { - "mdnNormalAsk", true, - I18N_NOOP("This message contains a request to return a notification " - "about your reception of the message.\n" - "You can either ignore the request or let the mail program " - "send a \"denied\" or normal response.") - }, - { - "mdnUnknownOption", false, - I18N_NOOP("This message contains a request to send a notification " - "about your reception of the message.\n" - "It contains a processing instruction that is marked as " - "\"required\", but which is unknown to the mail program.\n" - "You can either ignore the request or let the mail program " - "send a \"failed\" response.") - }, - { - "mdnMultipleAddressesInReceiptTo", true, - I18N_NOOP("This message contains a request to send a notification " - "about your reception of the message,\n" - "but it is requested to send the notification to more " - "than one address.\n" - "You can either ignore the request or let the mail program " - "send a \"denied\" or normal response.") - }, - { - "mdnReturnPathEmpty", true, - I18N_NOOP("This message contains a request to send a notification " - "about your reception of the message,\n" - "but there is no return-path set.\n" - "You can either ignore the request or let the mail program " - "send a \"denied\" or normal response.") - }, - { - "mdnReturnPathNotInReceiptTo", true, - I18N_NOOP("This message contains a request to send a notification " - "about your reception of the message,\n" - "but the return-path address differs from the address " - "the notification was requested to be sent to.\n" - "You can either ignore the request or let the mail program " - "send a \"denied\" or normal response.") - }, -}; - -static const int numMdnMessageBoxes - = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes; - MDNAdviceDialog::MDNAdviceDialog(const QString &text, bool canDeny, QWidget *parent) : QDialog(parent) { @@ -121,154 +57,6 @@ MessageComposer::MDNAdvice MDNAdviceDialog::result() const return m_result; } -MessageComposer::MDNAdvice MDNAdviceHelper::questionIgnoreSend(const QString &text, bool canDeny) -{ - MessageComposer::MDNAdvice rc = MessageComposer::MDNIgnore; - QPointer dlg(new MDNAdviceDialog(text, canDeny)); - dlg->exec(); - if (dlg) { - rc = dlg->result(); - } - delete dlg; - return rc; -} - -QPair< bool, KMime::MDN::SendingMode > MDNAdviceHelper::checkAndSetMDNInfo( - const Akonadi::Item &item, KMime::MDN::DispositionType d, bool forceSend) -{ - KMime::Message::Ptr msg = MessageComposer::Util::message(item); - - // RFC 2298: At most one MDN may be issued on behalf of each - // particular recipient by their user agent. That is, once an MDN - // has been issued on behalf of a recipient, no further MDNs may be - // issued on behalf of that recipient, even if another disposition - // is performed on the message. - if (item.hasAttribute< MailCommon::MDNStateAttribute >() - && item.attribute< MailCommon::MDNStateAttribute >()->mdnState() != MailCommon::MDNStateAttribute::MDNStateUnknown) { - // if already dealt with, don't do it again. - return QPair< bool, KMime::MDN::SendingMode >(false, KMime::MDN::SentAutomatically); - } - MailCommon::MDNStateAttribute *mdnStateAttr - = new MailCommon::MDNStateAttribute(MailCommon::MDNStateAttribute::MDNStateUnknown); - - KMime::MDN::SendingMode s = KMime::MDN::SentAutomatically; // set to manual if asked user - bool doSend = false; - // default: - int mode = MessageViewer::MessageViewerSettings::self()->defaultPolicy(); - if (forceSend) { //We must send it - mode = 3; - } else { - if (!mode || mode < 0 || mode > 3) { - // early out for ignore: - mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); - s = KMime::MDN::SentManually; - } else { - if (MessageFactoryNG::MDNMDNUnknownOption(msg)) { - mode = requestAdviceOnMDN("mdnUnknownOption"); - s = KMime::MDN::SentManually; - // TODO set type to Failed as well - // and clear modifiers - } - - if (MessageFactoryNG::MDNConfirmMultipleRecipients(msg)) { - mode = requestAdviceOnMDN("mdnMultipleAddressesInReceiptTo"); - s = KMime::MDN::SentManually; - } - - if (MessageFactoryNG::MDNReturnPathEmpty(msg)) { - mode = requestAdviceOnMDN("mdnReturnPathEmpty"); - s = KMime::MDN::SentManually; - } - - if (MessageFactoryNG::MDNReturnPathNotInRecieptTo(msg)) { - mode = requestAdviceOnMDN("mdnReturnPathNotInReceiptTo"); - s = KMime::MDN::SentManually; - } - - if (MessageFactoryNG::MDNRequested(msg)) { - if (s != KMime::MDN::SentManually) { - // don't ask again if user has already been asked. use the users' decision - mode = requestAdviceOnMDN("mdnNormalAsk"); - s = KMime::MDN::SentManually; // asked user - } - } else { // if message doesn't have a disposition header, never send anything. - mode = 0; - } - } - } - - // RFC 2298: An MDN MUST NOT be generated in response to an MDN. - if (MessageComposer::Util::findTypeInMessage(msg.data(), - "message", "disposition-notification")) { - mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); - } else if (mode == 0) { // ignore - doSend = false; - mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); - } else if (mode == 2) { // denied - doSend = true; - mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNDenied); - } else if (mode == 3) { // the user wants to send. let's make sure we can, according to the RFC. - doSend = true; - mdnStateAttr->setMDNState(dispositionToSentState(d)); - } - - // create a minimal version of item with just the attribute we want to change - Akonadi::Item i(item.id()); - i.setRevision(item.revision()); - i.setMimeType(item.mimeType()); - i.addAttribute(mdnStateAttr); - Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(i); - modify->setIgnorePayload(true); - modify->disableRevisionCheck(); - return QPair< bool, KMime::MDN::SendingMode >(doSend, s); -} - -MailCommon::MDNStateAttribute::MDNSentState MDNAdviceHelper::dispositionToSentState( - KMime::MDN::DispositionType d) -{ - switch (d) { - case KMime::MDN::Displayed: - return MailCommon::MDNStateAttribute::MDNDisplayed; - case KMime::MDN::Deleted: - return MailCommon::MDNStateAttribute::MDNDeleted; - case KMime::MDN::Dispatched: - return MailCommon::MDNStateAttribute::MDNDispatched; - case KMime::MDN::Processed: - return MailCommon::MDNStateAttribute::MDNProcessed; - case KMime::MDN::Denied: - return MailCommon::MDNStateAttribute::MDNDenied; - case KMime::MDN::Failed: - return MailCommon::MDNStateAttribute::MDNFailed; - default: - return MailCommon::MDNStateAttribute::MDNStateUnknown; - } -} - -int MDNAdviceHelper::requestAdviceOnMDN(const char *what) -{ - for (int i = 0; i < numMdnMessageBoxes; ++i) { - if (!qstrcmp(what, mdnMessageBoxes[i].dontAskAgainID)) { - KCursorSaver saver(Qt::ArrowCursor); - MessageComposer::MDNAdvice answer; - answer = questionIgnoreSend(i18n(mdnMessageBoxes[i].text), - mdnMessageBoxes[i].canDeny); - switch (answer) { - case MessageComposer::MDNSend: - return 3; - - case MessageComposer::MDNSendDenied: - return 2; - - // don't use 1, as that's used for 'default ask" in checkMDNHeaders - default: - case MessageComposer::MDNIgnore: - return 0; - } - } - } - qCWarning(MAILCOMMON_LOG) << "didn't find data for message box \"" << what << "\""; - return MessageComposer::MDNIgnore; -} void MDNAdviceDialog::slotUser1Clicked() { diff --git a/src/filter/mdnadvicedialog.h b/src/filter/mdnadvicedialog.h index 6875e2d..8fdd08b 100644 --- a/src/filter/mdnadvicedialog.h +++ b/src/filter/mdnadvicedialog.h @@ -10,58 +10,10 @@ #include "mailcommon_export.h" #include -#include "mailcommon/mdnstateattribute.h" - -#include #include namespace MailCommon { -class MDNAdviceHelper : public QObject -{ - Q_OBJECT -public: - static MDNAdviceHelper *instance() - { - if (!s_instance) { - s_instance = new MDNAdviceHelper; - } - - return s_instance; - } - - /** - * Checks the MDN headers to see if the user needs to be asked for any - * confirmations. Will ask the user if action is required. - * - * Returns whether to send an MDN or not, and the sending mode for the MDN - * to be created. - * - * Will also set the MailCommon::MDNStateAttribute on the given item - * to what the user has selected. - */ - Q_REQUIRED_RESULT QPair checkAndSetMDNInfo( - const Akonadi::Item &item, KMime::MDN::DispositionType d, bool forceSend = false); - - Q_REQUIRED_RESULT MailCommon::MDNStateAttribute::MDNSentState dispositionToSentState( - KMime::MDN::DispositionType d); - -private: - explicit MDNAdviceHelper(QObject *parent = nullptr) - : QObject(parent) - { - } - - virtual ~MDNAdviceHelper() - { - } - - int requestAdviceOnMDN(const char *what); - MessageComposer::MDNAdvice questionIgnoreSend(const QString &text, bool canDeny); - - static MDNAdviceHelper *s_instance; -}; - class MAILCOMMON_EXPORT MDNAdviceDialog : public QDialog { Q_OBJECT diff --git a/src/mdn/mdnadvicehelper.cpp b/src/mdn/mdnadvicehelper.cpp new file mode 100644 index 0000000..7391fc2 --- /dev/null +++ b/src/mdn/mdnadvicehelper.cpp @@ -0,0 +1,223 @@ + +/* + SPDX-FileCopyrightText: 2020 Laurent Montel + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "mdnadvicehelper.h" +#include "mailcommon_debug.h" +#include +#include "filter/mdnadvicedialog.h" +#include +#include +#include +#include +#include + +#include +using MessageComposer::MessageFactoryNG; +using namespace MailCommon; + +static const struct { + const char *dontAskAgainID; + bool canDeny; + const char *text; +} mdnMessageBoxes[] = { + { + "mdnNormalAsk", true, + I18N_NOOP("This message contains a request to return a notification " + "about your reception of the message.\n" + "You can either ignore the request or let the mail program " + "send a \"denied\" or normal response.") + }, + { + "mdnUnknownOption", false, + I18N_NOOP("This message contains a request to send a notification " + "about your reception of the message.\n" + "It contains a processing instruction that is marked as " + "\"required\", but which is unknown to the mail program.\n" + "You can either ignore the request or let the mail program " + "send a \"failed\" response.") + }, + { + "mdnMultipleAddressesInReceiptTo", true, + I18N_NOOP("This message contains a request to send a notification " + "about your reception of the message,\n" + "but it is requested to send the notification to more " + "than one address.\n" + "You can either ignore the request or let the mail program " + "send a \"denied\" or normal response.") + }, + { + "mdnReturnPathEmpty", true, + I18N_NOOP("This message contains a request to send a notification " + "about your reception of the message,\n" + "but there is no return-path set.\n" + "You can either ignore the request or let the mail program " + "send a \"denied\" or normal response.") + }, + { + "mdnReturnPathNotInReceiptTo", true, + I18N_NOOP("This message contains a request to send a notification " + "about your reception of the message,\n" + "but the return-path address differs from the address " + "the notification was requested to be sent to.\n" + "You can either ignore the request or let the mail program " + "send a \"denied\" or normal response.") + }, +}; + +static const int numMdnMessageBoxes + = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes; + + +MDNAdviceHelper *MDNAdviceHelper::s_instance = nullptr; +MessageComposer::MDNAdvice MDNAdviceHelper::questionIgnoreSend(const QString &text, bool canDeny) +{ + MessageComposer::MDNAdvice rc = MessageComposer::MDNIgnore; + QPointer dlg(new MDNAdviceDialog(text, canDeny)); + dlg->exec(); + if (dlg) { + rc = dlg->result(); + } + delete dlg; + return rc; +} + +QPair< bool, KMime::MDN::SendingMode > MDNAdviceHelper::checkAndSetMDNInfo( + const Akonadi::Item &item, KMime::MDN::DispositionType d, bool forceSend) +{ + KMime::Message::Ptr msg = MessageComposer::Util::message(item); + + // RFC 2298: At most one MDN may be issued on behalf of each + // particular recipient by their user agent. That is, once an MDN + // has been issued on behalf of a recipient, no further MDNs may be + // issued on behalf of that recipient, even if another disposition + // is performed on the message. + if (item.hasAttribute< MailCommon::MDNStateAttribute >() + && item.attribute< MailCommon::MDNStateAttribute >()->mdnState() != MailCommon::MDNStateAttribute::MDNStateUnknown) { + // if already dealt with, don't do it again. + return QPair< bool, KMime::MDN::SendingMode >(false, KMime::MDN::SentAutomatically); + } + MailCommon::MDNStateAttribute *mdnStateAttr + = new MailCommon::MDNStateAttribute(MailCommon::MDNStateAttribute::MDNStateUnknown); + + KMime::MDN::SendingMode s = KMime::MDN::SentAutomatically; // set to manual if asked user + bool doSend = false; + // default: + int mode = MessageViewer::MessageViewerSettings::self()->defaultPolicy(); + if (forceSend) { //We must send it + mode = 3; + } else { + if (!mode || (mode < 0) || (mode > 3)) { + // early out for ignore: + mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); + s = KMime::MDN::SentManually; + } else { + if (MessageFactoryNG::MDNMDNUnknownOption(msg)) { + mode = requestAdviceOnMDN("mdnUnknownOption"); + s = KMime::MDN::SentManually; + // TODO set type to Failed as well + // and clear modifiers + } + + if (MessageFactoryNG::MDNConfirmMultipleRecipients(msg)) { + mode = requestAdviceOnMDN("mdnMultipleAddressesInReceiptTo"); + s = KMime::MDN::SentManually; + } + + if (MessageFactoryNG::MDNReturnPathEmpty(msg)) { + mode = requestAdviceOnMDN("mdnReturnPathEmpty"); + s = KMime::MDN::SentManually; + } + + if (MessageFactoryNG::MDNReturnPathNotInRecieptTo(msg)) { + mode = requestAdviceOnMDN("mdnReturnPathNotInReceiptTo"); + s = KMime::MDN::SentManually; + } + + if (MessageFactoryNG::MDNRequested(msg)) { + if (s != KMime::MDN::SentManually) { + // don't ask again if user has already been asked. use the users' decision + mode = requestAdviceOnMDN("mdnNormalAsk"); + s = KMime::MDN::SentManually; // asked user + } + } else { // if message doesn't have a disposition header, never send anything. + mode = 0; + } + } + } + + // RFC 2298: An MDN MUST NOT be generated in response to an MDN. + if (MessageComposer::Util::findTypeInMessage(msg.data(), + "message", "disposition-notification")) { + mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); + } else if (mode == 0) { // ignore + doSend = false; + mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNIgnore); + } else if (mode == 2) { // denied + doSend = true; + mdnStateAttr->setMDNState(MailCommon::MDNStateAttribute::MDNDenied); + } else if (mode == 3) { // the user wants to send. let's make sure we can, according to the RFC. + doSend = true; + mdnStateAttr->setMDNState(dispositionToSentState(d)); + } + + // create a minimal version of item with just the attribute we want to change + Akonadi::Item i(item.id()); + i.setRevision(item.revision()); + i.setMimeType(item.mimeType()); + i.addAttribute(mdnStateAttr); + Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(i); + modify->setIgnorePayload(true); + modify->disableRevisionCheck(); + return QPair< bool, KMime::MDN::SendingMode >(doSend, s); +} + +MailCommon::MDNStateAttribute::MDNSentState MDNAdviceHelper::dispositionToSentState( + KMime::MDN::DispositionType d) +{ + switch (d) { + case KMime::MDN::Displayed: + return MailCommon::MDNStateAttribute::MDNDisplayed; + case KMime::MDN::Deleted: + return MailCommon::MDNStateAttribute::MDNDeleted; + case KMime::MDN::Dispatched: + return MailCommon::MDNStateAttribute::MDNDispatched; + case KMime::MDN::Processed: + return MailCommon::MDNStateAttribute::MDNProcessed; + case KMime::MDN::Denied: + return MailCommon::MDNStateAttribute::MDNDenied; + case KMime::MDN::Failed: + return MailCommon::MDNStateAttribute::MDNFailed; + default: + return MailCommon::MDNStateAttribute::MDNStateUnknown; + } +} + +int MDNAdviceHelper::requestAdviceOnMDN(const char *what) +{ + for (int i = 0; i < numMdnMessageBoxes; ++i) { + if (!qstrcmp(what, mdnMessageBoxes[i].dontAskAgainID)) { + KCursorSaver saver(Qt::ArrowCursor); + MessageComposer::MDNAdvice answer; + answer = questionIgnoreSend(i18n(mdnMessageBoxes[i].text), + mdnMessageBoxes[i].canDeny); + switch (answer) { + case MessageComposer::MDNSend: + return 3; + + case MessageComposer::MDNSendDenied: + return 2; + + // don't use 1, as that's used for 'default ask" in checkMDNHeaders + default: + case MessageComposer::MDNIgnore: + return 0; + } + } + } + qCWarning(MAILCOMMON_LOG) << "didn't find data for message box \"" << what << "\""; + return MessageComposer::MDNIgnore; +} diff --git a/src/mdn/mdnadvicehelper.h b/src/mdn/mdnadvicehelper.h new file mode 100644 index 0000000..496e242 --- /dev/null +++ b/src/mdn/mdnadvicehelper.h @@ -0,0 +1,62 @@ +/* + SPDX-FileCopyrightText: 2020 Laurent Montel + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef MDNADVICEHELPER_H +#define MDNADVICEHELPER_H + +#include +#include "mailcommon/mdnstateattribute.h" +//#include +#include + +namespace MailCommon { +class MDNAdviceHelper : public QObject +{ + Q_OBJECT +public: + static MDNAdviceHelper *instance() + { + if (!s_instance) { + s_instance = new MDNAdviceHelper; + } + + return s_instance; + } + + /** + * Checks the MDN headers to see if the user needs to be asked for any + * confirmations. Will ask the user if action is required. + * + * Returns whether to send an MDN or not, and the sending mode for the MDN + * to be created. + * + * Will also set the MailCommon::MDNStateAttribute on the given item + * to what the user has selected. + */ + Q_REQUIRED_RESULT QPair checkAndSetMDNInfo( + const Akonadi::Item &item, KMime::MDN::DispositionType d, bool forceSend = false); + + Q_REQUIRED_RESULT MailCommon::MDNStateAttribute::MDNSentState dispositionToSentState( + KMime::MDN::DispositionType d); + +private: + explicit MDNAdviceHelper(QObject *parent = nullptr) + : QObject(parent) + { + } + + virtual ~MDNAdviceHelper() + { + } + + Q_REQUIRED_RESULT int requestAdviceOnMDN(const char *what); + MessageComposer::MDNAdvice questionIgnoreSend(const QString &text, bool canDeny); + + static MDNAdviceHelper *s_instance; +}; +} + +#endif // MDNADVICEHELPER_H diff --git a/src/mdn/sendmdnhandler.cpp b/src/mdn/sendmdnhandler.cpp index f9da227..941d234 100644 --- a/src/mdn/sendmdnhandler.cpp +++ b/src/mdn/sendmdnhandler.cpp @@ -11,6 +11,7 @@ #include "kernel/mailkernel.h" #include "util/mailutil.h" #include "filter/mdnadvicedialog.h" +#include "mdnadvicehelper.h" #include "mailcommon_debug.h" #include #include