Fix assert when Mail-Followup-To contains two emails, as Ossi's Mutt does
[kdepim.git] / messagecore / mailinglist.cpp
blob496e32f1e1a168458b2bc615bc14771e3f9de679
1 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
3 #include "mailinglist.h"
5 #include <kconfig.h>
6 #include <kconfiggroup.h>
7 #include <kurl.h>
8 #include <kdebug.h>
10 #include <QtCore/QSharedData>
11 #include <QtCore/QStringList>
13 #include <boost/shared_ptr.hpp>
15 using namespace MessageCore;
17 typedef QString (*MagicDetectorFunc)( const KMime::Message::Ptr&, QByteArray&, QString& );
19 /* Sender: (owner-([^@]+)|([^@+]-owner)@ */
20 static QString check_sender( const KMime::Message::Ptr &message,
21 QByteArray &headerName,
22 QString &headerValue )
24 QString header = message->sender()->asUnicodeString();
26 if ( header.isEmpty() )
27 return QString();
29 if ( header.left( 6 ) == QLatin1String("owner-") ) {
30 headerName = "Sender";
31 headerValue = header;
32 header = header.mid( 6, header.indexOf( '@' ) - 6 );
33 } else {
34 const int index = header.indexOf( "-owner@ " );
35 if ( index == -1 )
36 return QString();
38 header.truncate( index );
39 headerName = "Sender";
40 headerValue = header;
43 return header;
46 /* X-BeenThere: ([^@]+) */
47 static QString check_x_beenthere( const KMime::Message::Ptr &message,
48 QByteArray &headerName,
49 QString &headerValue )
51 QString header = message->headerByType( "X-BeenThere" ) ? message->headerByType( "X-BeenThere" )->asUnicodeString() : "";
52 if ( header.isNull() || header.indexOf( '@' ) == -1 )
53 return QString();
55 headerName = "X-BeenThere";
56 headerValue = header;
57 header.truncate( header.indexOf( '@' ) );
59 return header;
62 /* Delivered-To:: <([^@]+) */
63 static QString check_delivered_to( const KMime::Message::Ptr &message,
64 QByteArray &headerName,
65 QString &headerValue )
67 QString header = message->headerByType( "Delivered-To" ) ? message->headerByType( "Delivered-To" )->asUnicodeString() : "";
68 if ( header.isNull() || header.left( 13 ) != "mailing list"
69 || header.indexOf( '@' ) == -1 )
70 return QString();
72 headerName = "Delivered-To";
73 headerValue = header;
75 return header.mid( 13, header.indexOf( '@' ) - 13 );
78 /* X-Mailing-List: <?([^@]+) */
79 static QString check_x_mailing_list( const KMime::Message::Ptr &message,
80 QByteArray &headerName,
81 QString &headerValue )
83 QString header = message->headerByType( "X-Mailing-List" ) ? message->headerByType( "X-Mailing-List" )->asUnicodeString() : "";
84 if ( header.isEmpty() )
85 return QString();
87 if ( header.indexOf( '@' ) < 1 )
88 return QString();
90 headerName = "X-Mailing-List";
91 headerValue = header;
92 if ( header[0] == '<' )
93 header = header.mid( 1, header.indexOf( '@' ) - 1 );
94 else
95 header.truncate( header.indexOf( '@' ) );
97 return header;
100 /* List-Id: [^<]* <([^.]+) */
101 static QString check_list_id( const KMime::Message::Ptr &message,
102 QByteArray &headerName,
103 QString &headerValue )
105 QString header = message->headerByType( "List-Id" ) ? message->headerByType( "List-Id" )->asUnicodeString() : "";
106 if ( header.isEmpty() )
107 return QString();
109 const int leftAnglePos = header.indexOf( '<' );
110 if ( leftAnglePos < 0 )
111 return QString();
113 const int firstDotPos = header.indexOf( '.', leftAnglePos );
114 if ( firstDotPos < 0 )
115 return QString();
117 headerName = "List-Id";
118 headerValue = header.mid( leftAnglePos );
119 header = header.mid( leftAnglePos + 1, firstDotPos - leftAnglePos - 1 );
121 return header;
125 /* List-Post: <mailto:[^< ]*>) */
126 static QString check_list_post( const KMime::Message::Ptr &message,
127 QByteArray &headerName,
128 QString &headerValue )
130 QString header = message->headerByType( "List-Post" ) ? message->headerByType( "List-Post" )->asUnicodeString() : "";
131 if ( header.isEmpty() )
132 return QString();
134 int leftAnglePos = header.indexOf( "<mailto:" );
135 if ( leftAnglePos < 0 )
136 return QString();
138 headerName = "List-Post";
139 headerValue = header;
140 header = header.mid( leftAnglePos + 8, header.length() );
141 header.truncate( header.indexOf( '@' ) );
143 return header;
146 /* Mailing-List: list ([^@]+) */
147 static QString check_mailing_list( const KMime::Message::Ptr &message,
148 QByteArray &headerName,
149 QString &headerValue )
151 QString header = message->headerByType( "Mailing-List" ) ? message->headerByType( "Mailing-List" )->asUnicodeString() : "";
152 if ( header.isEmpty() )
153 return QString();
155 if ( header.left( 5 ) != "list " || header.indexOf( '@' ) < 5 )
156 return QString();
158 headerName = "Mailing-List";
159 headerValue = header;
160 header = header.mid( 5, header.indexOf( '@' ) - 5 );
162 return header;
166 /* X-Loop: ([^@]+) */
167 static QString check_x_loop( const KMime::Message::Ptr &message,
168 QByteArray &headerName,
169 QString &headerValue )
171 QString header = message->headerByType( "X-Loop" ) ? message->headerByType( "X-Loop" )->asUnicodeString() : "";
172 if ( header.isEmpty() )
173 return QString();
175 if (header.indexOf( '@' ) < 2 )
176 return QString();
178 headerName = "X-Loop";
179 headerValue = header;
180 header.truncate( header.indexOf( '@' ) );
182 return header;
185 /* X-ML-Name: (.+) */
186 static QString check_x_ml_name( const KMime::Message::Ptr &message,
187 QByteArray &headerName,
188 QString &headerValue )
190 QString header = message->headerByType( "X-ML-Name" ) ? message->headerByType( "X-ML-Name" )->asUnicodeString() : "";
191 if ( header.isEmpty() )
192 return QString();
194 headerName = "X-ML-Name";
195 headerValue = header;
196 header.truncate( header.indexOf( '@' ) );
198 return header;
201 MagicDetectorFunc magic_detector[] =
203 check_list_id,
204 check_list_post,
205 check_sender,
206 check_x_mailing_list,
207 check_mailing_list,
208 check_delivered_to,
209 check_x_beenthere,
210 check_x_loop,
211 check_x_ml_name
214 static const int num_detectors = sizeof( magic_detector ) / sizeof( magic_detector[0] );
216 static QStringList headerToAddress( const QString &header )
218 QStringList addresses;
219 int start = 0;
220 int end = 0;
222 if ( header.isEmpty() )
223 return addresses;
225 while ( (start = header.indexOf( "<", start )) != -1 ) {
226 if ( (end = header.indexOf( ">", ++start ) ) == -1 ) {
227 kWarning() << "Serious mailing list header parsing error!";
228 return addresses;
231 addresses.append( header.mid( start, end - start ) );
234 return addresses;
237 class MessageCore::MailingList::Private : public QSharedData
239 public:
240 Private()
241 : mFeatures( None ),
242 mHandler( KMail )
246 Private( const Private &other )
247 : QSharedData( other )
249 mFeatures = other.mFeatures;
250 mHandler = other.mHandler;
251 mPostUrls = other.mPostUrls;
252 mSubscribeUrls = other.mSubscribeUrls;
253 mUnsubscribeUrls = other.mUnsubscribeUrls;
254 mHelpUrls = other.mHelpUrls;
255 mArchiveUrls = other.mArchiveUrls;
256 mOwnerUrls = other.mOwnerUrls;
257 mArchivedAtUrls = other.mArchivedAtUrls;
258 mId = other.mId;
261 Features mFeatures;
262 Handler mHandler;
263 KUrl::List mPostUrls;
264 KUrl::List mSubscribeUrls;
265 KUrl::List mUnsubscribeUrls;
266 KUrl::List mHelpUrls;
267 KUrl::List mArchiveUrls;
268 KUrl::List mOwnerUrls;
269 KUrl::List mArchivedAtUrls;
270 QString mId;
273 MailingList MailingList::detect( const KMime::Message::Ptr &message )
275 MailingList mailingList;
277 if ( message->headerByType( "List-Post" ) )
278 mailingList.setPostUrls( headerToAddress( message->headerByType( "List-Post" )->asUnicodeString() ) );
280 if ( message->headerByType( "List-Help" ) )
281 mailingList.setHelpUrls( headerToAddress( message->headerByType( "List-Help" )->asUnicodeString() ) );
283 if ( message->headerByType( "List-Subscribe" ) )
284 mailingList.setSubscribeUrls( headerToAddress( message->headerByType( "List-Subscribe" )->asUnicodeString() ) );
286 if ( message->headerByType( "List-Unsubscribe" ) )
287 mailingList.setUnsubscribeUrls( headerToAddress( message->headerByType( "List-Unsubscribe" )->asUnicodeString() ) );
289 if ( message->headerByType( "List-Archive" ) )
290 mailingList.setArchiveUrls( headerToAddress( message->headerByType( "List-Archive" )->asUnicodeString() ) );
292 if ( message->headerByType( "List-Owner" ) )
293 mailingList.setOwnerUrls( headerToAddress( message->headerByType( "List-Owner" )->asUnicodeString() ) );
295 if ( message->headerByType( "Archived-At" ) ) {
296 mailingList.setArchivedAtUrls( headerToAddress( message->headerByType( "Archived-At" )->asUnicodeString() ) );
299 if ( message->headerByType( "List-Id" ) )
300 mailingList.setId( message->headerByType( "List-Id" )->asUnicodeString() );
302 return mailingList;
305 QString MailingList::name( const KMime::Message::Ptr &message,
306 QByteArray &headerName, QString &headerValue )
308 QString mailingList;
309 headerName = QByteArray();
310 headerValue.clear();
312 if ( !message )
313 return QString();
315 for ( int i = 0; i < num_detectors; i++ ) {
316 mailingList = magic_detector[i]( message, headerName, headerValue );
317 if ( !mailingList.isNull() )
318 return mailingList;
321 return QString();
324 MailingList::MailingList()
325 : d( new Private )
329 MailingList::MailingList( const MailingList &other )
330 : d( other.d )
334 MailingList& MailingList::operator=( const MailingList &other )
336 if ( this != &other )
337 d = other.d;
339 return *this;
342 MailingList::~MailingList()
346 MailingList::Features MailingList::features() const
348 return d->mFeatures;
351 void MailingList::setHandler( MailingList::Handler handler )
353 d->mHandler = handler;
356 MailingList::Handler MailingList::handler() const
358 return d->mHandler;
361 void MailingList::setPostUrls( const KUrl::List &urls )
363 d->mFeatures |= Post;
365 if ( urls.empty() ) {
366 d->mFeatures ^= Post;
369 d->mPostUrls = urls;
372 KUrl::List MailingList::postUrls() const
374 return d->mPostUrls;
377 void MailingList::setSubscribeUrls( const KUrl::List &urls )
379 d->mFeatures |= Subscribe;
381 if ( urls.empty() ) {
382 d->mFeatures ^= Subscribe;
385 d->mSubscribeUrls = urls;
388 KUrl::List MailingList::subscribeUrls() const
390 return d->mSubscribeUrls;
393 void MailingList::setUnsubscribeUrls( const KUrl::List &urls )
395 d->mFeatures |= Unsubscribe;
397 if ( urls.empty() ) {
398 d->mFeatures ^= Unsubscribe;
401 d->mUnsubscribeUrls = urls;
404 KUrl::List MailingList::unsubscribeUrls() const
406 return d->mUnsubscribeUrls;
409 void MailingList::setHelpUrls( const KUrl::List &urls )
411 d->mFeatures |= Help;
413 if ( urls.empty() ) {
414 d->mFeatures ^= Help;
417 d->mHelpUrls = urls;
420 KUrl::List MailingList::helpUrls() const
422 return d->mHelpUrls;
425 void MailingList::setArchiveUrls( const KUrl::List &urls )
427 d->mFeatures |= Archive;
429 if ( urls.empty() ) {
430 d->mFeatures ^= Archive;
433 d->mArchiveUrls = urls;
436 KUrl::List MailingList::archiveUrls() const
438 return d->mArchiveUrls;
441 void MailingList::setOwnerUrls( const KUrl::List &urls )
443 d->mFeatures |= Owner;
445 if ( urls.empty() ) {
446 d->mFeatures ^= Owner;
449 d->mOwnerUrls = urls;
452 KUrl::List MailingList::ownerUrls() const
454 return d->mOwnerUrls;
457 void MailingList::setArchivedAtUrls( const KUrl::List &urls )
459 d->mFeatures |= ArchivedAt;
461 if ( urls.isEmpty() ) {
462 d->mFeatures ^= ArchivedAt;
465 d->mArchivedAtUrls = urls;
468 KUrl::List MailingList::archivedAtUrls() const
470 return d->mArchivedAtUrls;
473 void MailingList::setId( const QString &id )
475 d->mFeatures |= Id;
477 if ( id.isEmpty() ) {
478 d->mFeatures ^= Id;
481 d->mId = id;
484 QString MailingList::id() const
486 return d->mId;
489 void MailingList::writeConfig( KConfigGroup &group ) const
491 group.writeEntry( "MailingListFeatures", static_cast<int>( d->mFeatures ) );
492 group.writeEntry( "MailingListHandler", static_cast<int>( d->mHandler ) );
493 group.writeEntry( "MailingListId", d->mId );
494 group.writeEntry( "MailingListPostingAddress", d->mPostUrls.toStringList() );
495 group.writeEntry( "MailingListSubscribeAddress", d->mSubscribeUrls.toStringList() );
496 group.writeEntry( "MailingListUnsubscribeAddress", d->mUnsubscribeUrls.toStringList() );
497 group.writeEntry( "MailingListArchiveAddress", d->mArchiveUrls.toStringList() );
498 group.writeEntry( "MailingListOwnerAddress", d->mOwnerUrls.toStringList() );
499 group.writeEntry( "MailingListHelpAddress", d->mHelpUrls.toStringList() );
500 /* Note: mArchivedAtUrl deliberately not saved here as it refers to a single
501 * instance of a message rather than an element of a general mailing list.
502 * http://reviewboard.kde.org/r/1768/#review2783
506 void MailingList::readConfig( const KConfigGroup &group )
508 d->mFeatures = static_cast<MailingList::Features>( group.readEntry( "MailingListFeatures", 0 ) );
509 d->mHandler = static_cast<MailingList::Handler>( group.readEntry( "MailingListHandler",
510 static_cast<int>( MailingList::KMail ) ) );
511 d->mId = group.readEntry("MailingListId");
512 d->mPostUrls = group.readEntry( "MailingListPostingAddress", QStringList() );
513 d->mSubscribeUrls = group.readEntry( "MailingListSubscribeAddress", QStringList() );
514 d->mUnsubscribeUrls = group.readEntry( "MailingListUnsubscribeAddress", QStringList() );
515 d->mArchiveUrls = group.readEntry( "MailingListArchiveAddress", QStringList() );
516 d->mOwnerUrls = group.readEntry( "MailingListOwnerddress", QStringList() );
517 d->mHelpUrls = group.readEntry( "MailingListHelpAddress", QStringList() );