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"