File indexing completed on 2024-06-23 05:18:34

0001 /*
0002   SPDX-FileCopyrightText: 2020-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "mdnadvicehelper.h"
0008 #include "mdnadvicedialog.h"
0009 #include "messagecomposer_debug.h"
0010 #include <Akonadi/ItemModifyJob>
0011 #include <KCursorSaver>
0012 #include <KLazyLocalizedString>
0013 #include <KLocalizedString>
0014 #include <MessageComposer/Util>
0015 #include <MessageViewer/MessageViewerSettings>
0016 #include <QPointer>
0017 using MessageComposer::MessageFactoryNG;
0018 using namespace MessageComposer;
0019 
0020 static const struct {
0021     const char *dontAskAgainID;
0022     bool canDeny;
0023     const KLazyLocalizedString text;
0024 } mdnMessageBoxes[] = {
0025     {"mdnNormalAsk",
0026      true,
0027      kli18n("This message contains a request to return a notification "
0028             "about your reception of the message.\n"
0029             "You can either ignore the request or let the mail program "
0030             "send a \"denied\" or normal response.")},
0031     {"mdnUnknownOption",
0032      false,
0033      kli18n("This message contains a request to send a notification "
0034             "about your reception of the message.\n"
0035             "It contains a processing instruction that is marked as "
0036             "\"required\", but which is unknown to the mail program.\n"
0037             "You can either ignore the request or let the mail program "
0038             "send a \"failed\" response.")},
0039     {"mdnMultipleAddressesInReceiptTo",
0040      true,
0041      kli18n("This message contains a request to send a notification "
0042             "about your reception of the message,\n"
0043             "but it is requested to send the notification to more "
0044             "than one address.\n"
0045             "You can either ignore the request or let the mail program "
0046             "send a \"denied\" or normal response.")},
0047     {"mdnReturnPathEmpty",
0048      true,
0049      kli18n("This message contains a request to send a notification "
0050             "about your reception of the message,\n"
0051             "but there is no return-path set.\n"
0052             "You can either ignore the request or let the mail program "
0053             "send a \"denied\" or normal response.")},
0054     {"mdnReturnPathNotInReceiptTo",
0055      true,
0056      kli18n("This message contains a request to send a notification "
0057             "about your reception of the message,\n"
0058             "but the return-path address differs from the address "
0059             "the notification was requested to be sent to.\n"
0060             "You can either ignore the request or let the mail program "
0061             "send a \"denied\" or normal response.")},
0062 };
0063 
0064 static const int numMdnMessageBoxes = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;
0065 
0066 MDNAdviceHelper *MDNAdviceHelper::s_instance = nullptr;
0067 MessageComposer::MDNAdvice MDNAdviceHelper::questionIgnoreSend(const QString &text, bool canDeny)
0068 {
0069     MessageComposer::MDNAdvice rc = MessageComposer::MDNIgnore;
0070     QPointer<MDNAdviceDialog> dlg(new MDNAdviceDialog(text, canDeny));
0071     dlg->exec();
0072     if (dlg) {
0073         rc = dlg->result();
0074     }
0075     delete dlg;
0076     return rc;
0077 }
0078 
0079 QPair<bool, KMime::MDN::SendingMode> MDNAdviceHelper::checkAndSetMDNInfo(const Akonadi::Item &item, KMime::MDN::DispositionType d, bool forceSend)
0080 {
0081     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
0082 
0083     // RFC 2298: At most one MDN may be issued on behalf of each
0084     // particular recipient by their user agent.  That is, once an MDN
0085     // has been issued on behalf of a recipient, no further MDNs may be
0086     // issued on behalf of that recipient, even if another disposition
0087     // is performed on the message.
0088     if (item.hasAttribute<Akonadi::MDNStateAttribute>()
0089         && item.attribute<Akonadi::MDNStateAttribute>()->mdnState() != Akonadi::MDNStateAttribute::MDNStateUnknown) {
0090         // if already dealt with, don't do it again.
0091         return QPair<bool, KMime::MDN::SendingMode>(false, KMime::MDN::SentAutomatically);
0092     }
0093     auto mdnStateAttr = new Akonadi::MDNStateAttribute(Akonadi::MDNStateAttribute::MDNStateUnknown);
0094 
0095     KMime::MDN::SendingMode s = KMime::MDN::SentAutomatically; // set to manual if asked user
0096     bool doSend = false;
0097     // default:
0098     int mode = MessageViewer::MessageViewerSettings::self()->defaultPolicy();
0099     if (forceSend) { // We must send it
0100         mode = 3;
0101     } else {
0102         if (!mode || (mode < 0) || (mode > 3)) {
0103             // early out for ignore:
0104             mdnStateAttr->setMDNState(Akonadi::MDNStateAttribute::MDNIgnore);
0105             s = KMime::MDN::SentManually;
0106         } else {
0107             if (MessageFactoryNG::MDNMDNUnknownOption(msg)) {
0108                 mode = requestAdviceOnMDN("mdnUnknownOption");
0109                 s = KMime::MDN::SentManually;
0110                 // TODO set type to Failed as well
0111                 //      and clear modifiers
0112             }
0113 
0114             if (MessageFactoryNG::MDNConfirmMultipleRecipients(msg)) {
0115                 mode = requestAdviceOnMDN("mdnMultipleAddressesInReceiptTo");
0116                 s = KMime::MDN::SentManually;
0117             }
0118 
0119             if (MessageFactoryNG::MDNReturnPathEmpty(msg)) {
0120                 mode = requestAdviceOnMDN("mdnReturnPathEmpty");
0121                 s = KMime::MDN::SentManually;
0122             }
0123 
0124             if (MessageFactoryNG::MDNReturnPathNotInRecieptTo(msg)) {
0125                 mode = requestAdviceOnMDN("mdnReturnPathNotInReceiptTo");
0126                 s = KMime::MDN::SentManually;
0127             }
0128 
0129             if (MessageFactoryNG::MDNRequested(msg)) {
0130                 if (s != KMime::MDN::SentManually) {
0131                     // don't ask again if user has already been asked. use the users' decision
0132                     mode = requestAdviceOnMDN("mdnNormalAsk");
0133                     s = KMime::MDN::SentManually; // asked user
0134                 }
0135             } else { // if message doesn't have a disposition header, never send anything.
0136                 mode = 0;
0137             }
0138         }
0139     }
0140 
0141     // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
0142     if (MessageComposer::Util::findTypeInMessage(msg.data(), "message", "disposition-notification")) {
0143         mdnStateAttr->setMDNState(Akonadi::MDNStateAttribute::MDNIgnore);
0144     } else if (mode == 0) { // ignore
0145         doSend = false;
0146         mdnStateAttr->setMDNState(Akonadi::MDNStateAttribute::MDNIgnore);
0147     } else if (mode == 2) { // denied
0148         doSend = true;
0149         mdnStateAttr->setMDNState(Akonadi::MDNStateAttribute::MDNDenied);
0150     } else if (mode == 3) { // the user wants to send. let's make sure we can, according to the RFC.
0151         doSend = true;
0152         mdnStateAttr->setMDNState(dispositionToSentState(d));
0153     }
0154 
0155     // create a minimal version of item with just the attribute we want to change
0156     Akonadi::Item i(item.id());
0157     i.setRevision(item.revision());
0158     i.setMimeType(item.mimeType());
0159     i.addAttribute(mdnStateAttr);
0160     auto modify = new Akonadi::ItemModifyJob(i);
0161     modify->setIgnorePayload(true);
0162     modify->disableRevisionCheck();
0163     return QPair<bool, KMime::MDN::SendingMode>(doSend, s);
0164 }
0165 
0166 Akonadi::MDNStateAttribute::MDNSentState MDNAdviceHelper::dispositionToSentState(KMime::MDN::DispositionType d)
0167 {
0168     switch (d) {
0169     case KMime::MDN::Displayed:
0170         return Akonadi::MDNStateAttribute::MDNDisplayed;
0171     case KMime::MDN::Deleted:
0172         return Akonadi::MDNStateAttribute::MDNDeleted;
0173     case KMime::MDN::Dispatched:
0174         return Akonadi::MDNStateAttribute::MDNDispatched;
0175     case KMime::MDN::Processed:
0176         return Akonadi::MDNStateAttribute::MDNProcessed;
0177     case KMime::MDN::Denied:
0178         return Akonadi::MDNStateAttribute::MDNDenied;
0179     case KMime::MDN::Failed:
0180         return Akonadi::MDNStateAttribute::MDNFailed;
0181     default:
0182         return Akonadi::MDNStateAttribute::MDNStateUnknown;
0183     }
0184 }
0185 
0186 QPair<QString, bool> MDNAdviceHelper::mdnMessageText(const char *what)
0187 {
0188     for (int i = 0; i < numMdnMessageBoxes; ++i) {
0189         if (!qstrcmp(what, mdnMessageBoxes[i].dontAskAgainID)) {
0190             return {mdnMessageBoxes[i].text.toString(), mdnMessageBoxes[i].canDeny};
0191         }
0192     }
0193     return {};
0194 }
0195 
0196 int MDNAdviceHelper::requestAdviceOnMDN(const char *what)
0197 {
0198     const QPair<QString, bool> mdnInfo = mdnMessageText(what);
0199     if (mdnInfo.first.isEmpty()) {
0200         qCWarning(MESSAGECOMPOSER_LOG) << "didn't find data for message box \"" << what << "\"";
0201         return MessageComposer::MDNIgnore;
0202     } else {
0203         KCursorSaver saver(Qt::ArrowCursor);
0204         const MessageComposer::MDNAdvice answer = questionIgnoreSend(mdnInfo.first, mdnInfo.second);
0205         switch (answer) {
0206         case MessageComposer::MDNSend:
0207             return 3;
0208 
0209         case MessageComposer::MDNSendDenied:
0210             return 2;
0211 
0212         // don't use 1, as that's used for 'default ask" in checkMDNHeaders
0213         default:
0214         case MessageComposer::MDNIgnore:
0215             return 0;
0216         }
0217     }
0218 }
0219 
0220 #include "moc_mdnadvicehelper.cpp"