1/* -*- c++ -*-
2 kmime_mdn.cpp
3
4 KMime, the KDE Internet mail/usenet news message library.
5 Copyright (c) 2002 Marc Mutz <mutz@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling @ref MIME data and
25 provides functions for supporting Message Disposition Notifications (MDNs),
26 also known as email return receipts.
27
28 @brief
29 Provides support for Message Disposition Notifications.
30
31 @authors Marc Mutz \<mutz@kde.org\>
32*/
33
34#include "kmime_mdn.h"
35#include "kmime_version.h"
36#include "kmime_util.h"
37
38#include <klocalizedstring.h>
39#include <kdebug.h>
40
41#include <QtCore/QByteArray>
42#include <QtCore/QList>
43
44#include <unistd.h> // gethostname
45
46namespace KMime {
47
48namespace MDN {
49
50static const struct {
51 DispositionType dispositionType;
52 const char * string;
53 const char * description;
54} dispositionTypes[] = {
55 { Displayed, "displayed",
56 I18N_NOOP( "The message sent on ${date} to ${to} with subject "
57 "\"${subject}\" has been displayed. This is no guarantee that "
58 "the message has been read or understood." ) },
59 { Deleted, "deleted",
60 I18N_NOOP( "The message sent on ${date} to ${to} with subject "
61 "\"${subject}\" has been deleted unseen. This is no guarantee "
62 "that the message will not be \"undeleted\" and nonetheless "
63 "read later on." ) },
64 { Dispatched, "dispatched",
65 I18N_NOOP( "The message sent on ${date} to ${to} with subject "
66 "\"${subject}\" has been dispatched. This is no guarantee "
67 "that the message will not be read later on." ) },
68 { Processed, "processed",
69 I18N_NOOP( "The message sent on ${date} to ${to} with subject "
70 "\"${subject}\" has been processed by some automatic means." ) },
71 { Denied, "denied",
72 I18N_NOOP( "The message sent on ${date} to ${to} with subject "
73 "\"${subject}\" has been acted upon. The sender does not wish "
74 "to disclose more details to you than that." ) },
75 { Failed, "failed",
76 I18N_NOOP( "Generation of a Message Disposition Notification for the "
77 "message sent on ${date} to ${to} with subject \"${subject}\" "
78 "failed. Reason is given in the Failure: header field below." ) }
79};
80
81static const int numDispositionTypes =
82 sizeof dispositionTypes / sizeof *dispositionTypes;
83
84static const char *stringFor( DispositionType d )
85{
86 for ( int i = 0 ; i < numDispositionTypes ; ++i ) {
87 if ( dispositionTypes[i].dispositionType == d ) {
88 return dispositionTypes[i].string;
89 }
90 }
91 return 0;
92}
93
94//
95// disposition-modifier
96//
97static const struct {
98 DispositionModifier dispositionModifier;
99 const char * string;
100} dispositionModifiers[] = {
101 { Error, "error" },
102 { Warning, "warning" },
103 { Superseded, "superseded" },
104 { Expired, "expired" },
105 { MailboxTerminated, "mailbox-terminated" }
106};
107
108static const int numDispositionModifiers =
109 sizeof dispositionModifiers / sizeof * dispositionModifiers;
110
111static const char *stringFor( DispositionModifier m ) {
112 for ( int i = 0 ; i < numDispositionModifiers ; ++i ) {
113 if ( dispositionModifiers[i].dispositionModifier == m ) {
114 return dispositionModifiers[i].string;
115 }
116 }
117 return 0;
118}
119
120//
121// action-mode (part of disposition-mode)
122//
123
124static const struct {
125 ActionMode actionMode;
126 const char * string;
127} actionModes[] = {
128 { ManualAction, "manual-action" },
129 { AutomaticAction, "automatic-action" }
130};
131
132static const int numActionModes =
133 sizeof actionModes / sizeof *actionModes;
134
135static const char *stringFor( ActionMode a ) {
136 for ( int i = 0 ; i < numActionModes ; ++i ) {
137 if ( actionModes[i].actionMode == a ) {
138 return actionModes[i].string;
139 }
140 }
141 return 0;
142}
143
144//
145// sending-mode (part of disposition-mode)
146//
147
148static const struct {
149 SendingMode sendingMode;
150 const char * string;
151} sendingModes[] = {
152 { SentManually, "MDN-sent-manually" },
153 { SentAutomatically, "MDN-sent-automatically" }
154};
155
156static const int numSendingModes =
157 sizeof sendingModes / sizeof *sendingModes;
158
159static const char *stringFor( SendingMode s ) {
160 for ( int i = 0 ; i < numSendingModes ; ++i ) {
161 if ( sendingModes[i].sendingMode == s ) {
162 return sendingModes[i].string;
163 }
164 }
165 return 0;
166}
167
168static QByteArray dispositionField( DispositionType d, ActionMode a, SendingMode s,
169 const QList<DispositionModifier> & m ) {
170
171 // mandatory parts: Disposition: foo/baz; bar
172 QByteArray result = "Disposition: ";
173 result += stringFor( a );
174 result += '/';
175 result += stringFor( s );
176 result += "; ";
177 result += stringFor( d );
178
179 // optional parts: Disposition: foo/baz; bar/mod1,mod2,mod3
180 bool first = true;
181 for ( QList<DispositionModifier>::const_iterator mt = m.begin();
182 mt != m.end() ; ++mt ) {
183 if ( first ) {
184 result += '/';
185 first = false;
186 } else {
187 result += ',';
188 }
189 result += stringFor( *mt );
190 }
191 return result + '\n';
192}
193
194static QByteArray finalRecipient( const QString &recipient )
195{
196 if ( recipient.isEmpty() ) {
197 return QByteArray();
198 } else {
199 return "Final-Recipient: rfc822; "
200 + encodeRFC2047String( recipient, "utf-8" ) + '\n';
201 }
202}
203
204static QByteArray orginalRecipient( const QByteArray & recipient )
205{
206 if ( recipient.isEmpty() ) {
207 return QByteArray();
208 } else {
209 return "Original-Recipient: " + recipient + '\n';
210 }
211}
212
213static QByteArray originalMessageID( const QByteArray &msgid )
214{
215 if ( msgid.isEmpty() ) {
216 return QByteArray();
217 } else {
218 return "Original-Message-ID: " + msgid + '\n';
219 }
220}
221
222static QByteArray reportingUAField() {
223 char hostName[256];
224 if ( gethostname( hostName, 255 ) ) {
225 hostName[0] = '\0'; // gethostname failed: pretend empty string
226 } else {
227 hostName[255] = '\0'; // gethostname may have returned 255 chars (man page)
228 }
229 return QByteArray( "Reporting-UA: " ) + QByteArray( hostName ) +
230 QByteArray( "; KMime " KMIME_VERSION_STRING "\n" );
231}
232
233QByteArray dispositionNotificationBodyContent( const QString &r,
234 const QByteArray &o,
235 const QByteArray &omid,
236 DispositionType d,
237 ActionMode a,
238 SendingMode s,
239 const QList<DispositionModifier> &m,
240 const QString &special )
241{
242 // in Perl: chomp(special)
243 QString spec;
244 if ( special.endsWith( QLatin1Char( '\n' ) ) ) {
245 spec = special.left( special.length() - 1 );
246 } else {
247 spec = special;
248 }
249
250 // std headers:
251 QByteArray result = reportingUAField();
252 result += orginalRecipient( o );
253 result += finalRecipient( r );
254 result += originalMessageID( omid );
255 result += dispositionField( d, a, s, m );
256
257 // headers that are only present for certain disposition {types,modifiers}:
258 if ( d == Failed ) {
259 result += "Failure: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
260 } else if ( m.contains( Error ) ) {
261 result += "Error: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
262 } else if ( m.contains( Warning ) ) {
263 result += "Warning: " + encodeRFC2047String( spec, "utf-8" ) + '\n';
264 }
265
266 return result;
267}
268
269QString descriptionFor( DispositionType d,
270 const QList<DispositionModifier> & )
271{
272 for ( int i = 0 ; i < numDispositionTypes ; ++i ) {
273 if ( dispositionTypes[i].dispositionType == d ) {
274 return i18n( dispositionTypes[i].description );
275 }
276 }
277 kWarning() << "KMime::MDN::descriptionFor(): No such disposition type:"
278 << ( int )d;
279 return QString();
280}
281
282} // namespace MDN
283} // namespace KMime
284