File indexing completed on 2022-11-23 12:03:47

0001 /*  -*- c++ -*-
0002     kmime_mdn.cpp
0003 
0004     KMime, the KDE Internet mail/usenet news message library.
0005     SPDX-FileCopyrightText: 2002 Marc Mutz <mutz@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 /**
0010   @file
0011   This file is part of the API for handling @ref MIME data and
0012   provides functions for supporting Message Disposition Notifications (MDNs),
0013   also known as email return receipts.
0014 
0015   @brief
0016   Provides support for Message Disposition Notifications.
0017 
0018   @authors Marc Mutz \<mutz@kde.org\>
0019 */
0020 
0021 #include "kmime_mdn.h"
0022 #include "kmime_version.h"
0023 #include "kmime_util.h"
0024 #include "kmime_codecs.h"
0025 #include "kmime_debug.h"
0026 
0027 #include <KLocalizedString>
0028 
0029 #include <QByteArray>
0030 
0031 #ifdef Q_OS_WIN // gethostname
0032 # include <winsock2.h>
0033 #else
0034 # include <unistd.h>
0035 #endif
0036 #include <KLazyLocalizedString>
0037 
0038 namespace KMime
0039 {
0040 
0041 namespace MDN
0042 {
0043 
0044 static const struct {
0045     DispositionType dispositionType;
0046     const char *string;
0047     const KLazyLocalizedString description;
0048 } dispositionTypes[] = {{Displayed,
0049                          "displayed",
0050                          kli18n("The message sent on ${date} to ${to} with subject "
0051                                 "\"${subject}\" has been displayed. This is no guarantee that "
0052                                 "the message has been read or understood.")},
0053                         {Deleted,
0054                          "deleted",
0055                          kli18n("The message sent on ${date} to ${to} with subject "
0056                                 "\"${subject}\" has been deleted unseen. This is no guarantee "
0057                                 "that the message will not be \"undeleted\" and nonetheless "
0058                                 "read later on.")},
0059                         {Dispatched,
0060                          "dispatched",
0061                          kli18n("The message sent on ${date} to ${to} with subject "
0062                                 "\"${subject}\" has been dispatched. This is no guarantee "
0063                                 "that the message will not be read later on.")},
0064                         {Processed,
0065                          "processed",
0066                          kli18n("The message sent on ${date} to ${to} with subject "
0067                                 "\"${subject}\" has been processed by some automatic means.")},
0068                         {Denied,
0069                          "denied",
0070                          kli18n("The message sent on ${date} to ${to} with subject "
0071                                 "\"${subject}\" has been acted upon. The sender does not wish "
0072                                 "to disclose more details to you than that.")},
0073                         {Failed,
0074                          "failed",
0075                          kli18n("Generation of a Message Disposition Notification for the "
0076                                 "message sent on ${date} to ${to} with subject \"${subject}\" "
0077                                 "failed. Reason is given in the Failure: header field below.")}};
0078 
0079 static const int numDispositionTypes =
0080     sizeof dispositionTypes / sizeof *dispositionTypes;
0081 
0082 static const char *stringFor(DispositionType d)
0083 {
0084     for (int i = 0 ; i < numDispositionTypes ; ++i) {
0085         if (dispositionTypes[i].dispositionType == d) {
0086             return dispositionTypes[i].string;
0087         }
0088     }
0089     return nullptr;
0090 }
0091 
0092 //
0093 // disposition-modifier
0094 //
0095 static const struct {
0096     DispositionModifier dispositionModifier;
0097     const char *string;
0098 } dispositionModifiers[] = {
0099     { Error, "error" },
0100     { Warning, "warning" },
0101     { Superseded, "superseded" },
0102     { Expired, "expired" },
0103     { MailboxTerminated, "mailbox-terminated" }
0104 };
0105 
0106 static const int numDispositionModifiers =
0107     sizeof dispositionModifiers / sizeof *dispositionModifiers;
0108 
0109 static const char *stringFor(DispositionModifier m)
0110 {
0111     for (int i = 0 ; i < numDispositionModifiers ; ++i) {
0112         if (dispositionModifiers[i].dispositionModifier == m) {
0113             return dispositionModifiers[i].string;
0114         }
0115     }
0116     return nullptr;
0117 }
0118 
0119 //
0120 // action-mode (part of disposition-mode)
0121 //
0122 
0123 static const struct {
0124     ActionMode actionMode;
0125     const char *string;
0126 } actionModes[] = {
0127     { ManualAction, "manual-action" },
0128     { AutomaticAction, "automatic-action" }
0129 };
0130 
0131 static const int numActionModes =
0132     sizeof actionModes / sizeof *actionModes;
0133 
0134 static const char *stringFor(ActionMode a)
0135 {
0136     for (int i = 0 ; i < numActionModes ; ++i) {
0137         if (actionModes[i].actionMode == a) {
0138             return actionModes[i].string;
0139         }
0140     }
0141     return nullptr;
0142 }
0143 
0144 //
0145 // sending-mode (part of disposition-mode)
0146 //
0147 
0148 static const struct {
0149     SendingMode sendingMode;
0150     const char *string;
0151 } sendingModes[] = {
0152     { SentManually, "MDN-sent-manually" },
0153     { SentAutomatically, "MDN-sent-automatically" }
0154 };
0155 
0156 static const int numSendingModes =
0157     sizeof sendingModes / sizeof *sendingModes;
0158 
0159 static const char *stringFor(SendingMode s)
0160 {
0161     for (int i = 0 ; i < numSendingModes ; ++i) {
0162         if (sendingModes[i].sendingMode == s) {
0163             return sendingModes[i].string;
0164         }
0165     }
0166     return nullptr;
0167 }
0168 
0169 static QByteArray dispositionField(DispositionType d, ActionMode a, SendingMode s,
0170                                    const QVector<DispositionModifier> &m)
0171 {
0172 
0173     // mandatory parts: Disposition: foo/baz; bar
0174     QByteArray result = "Disposition: ";
0175     result += stringFor(a);
0176     result += '/';
0177     result += stringFor(s);
0178     result += "; ";
0179     result += stringFor(d);
0180 
0181     // optional parts: Disposition: foo/baz; bar/mod1,mod2,mod3
0182     bool first = true;
0183     for (QVector<DispositionModifier>::const_iterator mt = m.begin();
0184             mt != m.end() ; ++mt) {
0185         if (first) {
0186             result += '/';
0187             first = false;
0188         } else {
0189             result += ',';
0190         }
0191         result += stringFor(*mt);
0192     }
0193     return result + '\n';
0194 }
0195 
0196 static QByteArray finalRecipient(const QString &recipient)
0197 {
0198     if (recipient.isEmpty()) {
0199       return {};
0200     } else {
0201         return "Final-Recipient: rfc822; "
0202                + encodeRFC2047String(recipient, "utf-8") + '\n';
0203     }
0204 }
0205 
0206 static QByteArray orginalRecipient(const QByteArray &recipient)
0207 {
0208     if (recipient.isEmpty()) {
0209       return {};
0210     } else {
0211         return "Original-Recipient: " + recipient + '\n';
0212     }
0213 }
0214 
0215 static QByteArray originalMessageID(const QByteArray &msgid)
0216 {
0217     if (msgid.isEmpty()) {
0218       return {};
0219     } else {
0220         return "Original-Message-ID: " + msgid + '\n';
0221     }
0222 }
0223 
0224 static QByteArray reportingUAField()
0225 {
0226     char hostName[256];
0227     if (gethostname(hostName, 255)) {
0228         hostName[0] = '\0'; // gethostname failed: pretend empty string
0229     } else {
0230         hostName[255] = '\0'; // gethostname may have returned 255 chars (man page)
0231     }
0232     return QByteArray("Reporting-UA: ") + QByteArray(hostName) +
0233            QByteArray("; KMime " KMIME_VERSION_STRING "\n");
0234 }
0235 
0236 QByteArray dispositionNotificationBodyContent(const QString &r,
0237         const QByteArray &o,
0238         const QByteArray &omid,
0239         DispositionType d,
0240         ActionMode a,
0241         SendingMode s,
0242         const QVector<DispositionModifier> &m,
0243         const QString &special)
0244 {
0245     // in Perl: chomp(special)
0246     QString spec;
0247     if (special.endsWith(QLatin1Char('\n'))) {
0248         spec = special.left(special.length() - 1);
0249     } else {
0250         spec = special;
0251     }
0252 
0253     // std headers:
0254     QByteArray result = reportingUAField();
0255     result += orginalRecipient(o);
0256     result += finalRecipient(r);
0257     result += originalMessageID(omid);
0258     result += dispositionField(d, a, s, m);
0259 
0260     // headers that are only present for certain disposition {types,modifiers}:
0261     if (d == Failed) {
0262         result += "Failure: " + encodeRFC2047String(spec, "utf-8") + '\n';
0263     } else if (m.contains(Error)) {
0264         result += "Error: " + encodeRFC2047String(spec, "utf-8") + '\n';
0265     } else if (m.contains(Warning)) {
0266         result += "Warning: " + encodeRFC2047String(spec, "utf-8") + '\n';
0267     }
0268 
0269     return result;
0270 }
0271 
0272 QString descriptionFor(DispositionType d,
0273                        const QVector<DispositionModifier> &)
0274 {
0275     for (int i = 0 ; i < numDispositionTypes ; ++i) {
0276         if (dispositionTypes[i].dispositionType == d) {
0277             return dispositionTypes[i].description.toString();
0278         }
0279     }
0280     qCWarning(KMIME_LOG) << "KMime::MDN::descriptionFor(): No such disposition type:"
0281                << static_cast<int>(d);
0282     return {};
0283 }
0284 
0285 } // namespace MDN
0286 } // namespace KMime