Messagelib

mailinglist.cpp
1/*
2 * SPDX-License-Identifier: LGPL-2.1-or-later
3 *
4 */
5
6#include "mailinglist.h"
7
8#include "messagecore_debug.h"
9#include <KConfig>
10#include <KConfigGroup>
11#include <QUrl>
12
13#include <QSharedData>
14#include <QStringList>
15
16using namespace MessageCore;
17
18using MagicDetectorFunc = QString (*)(const KMime::Message::Ptr &, QByteArray &, QString &);
19
20/* Sender: (owner-([^@]+)|([^@+]-owner)@ */
21static QString check_sender(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
22{
23 QString header = message->sender()->asUnicodeString();
24
25 if (header.isEmpty()) {
26 return {};
27 }
28
29 if (header.left(6) == QLatin1StringView("owner-")) {
30 headerName = "Sender";
31 headerValue = header;
32 header = header.mid(6, header.indexOf(QLatin1Char('@')) - 6);
33 } else {
34 const int index = header.indexOf(QLatin1StringView("-owner@ "));
35 if (index == -1) {
36 return {};
37 }
38
39 header.truncate(index);
40 headerName = "Sender";
41 headerValue = header;
42 }
43
44 return header;
45}
46
47/* X-BeenThere: ([^@]+) */
48static QString check_x_beenthere(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
49{
50 QString header;
51 if (auto hrd = message->headerByType("X-BeenThere")) {
52 header = hrd->asUnicodeString();
53 }
54 if (header.isNull() || header.indexOf(QLatin1Char('@')) == -1) {
55 return {};
56 }
57
58 headerName = "X-BeenThere";
59 headerValue = header;
60 header.truncate(header.indexOf(QLatin1Char('@')));
61
62 return header;
63}
64
65/* Delivered-To:: <([^@]+) */
66static QString check_delivered_to(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
67{
68 QString header;
69 if (auto hrd = message->headerByType("Delivered-To")) {
70 header = hrd->asUnicodeString();
71 }
72 if (header.isNull() || header.left(13) != QLatin1StringView("mailing list") || header.indexOf(QLatin1Char('@')) == -1) {
73 return {};
74 }
75
76 headerName = "Delivered-To";
77 headerValue = header;
78
79 return header.mid(13, header.indexOf(QLatin1Char('@')) - 13);
80}
81
82/* X-Mailing-List: <?([^@]+) */
83static QString check_x_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
84{
85 QString header;
86 if (auto hrd = message->headerByType("X-Mailing-List")) {
87 header = hrd->asUnicodeString();
88 }
89 if (header.isEmpty()) {
90 return {};
91 }
92
93 if (header.indexOf(QLatin1Char('@')) < 1) {
94 return {};
95 }
96
97 headerName = "X-Mailing-List";
98 headerValue = header;
99 if (header[0] == QLatin1Char('<')) {
100 header = header.mid(1, header.indexOf(QLatin1Char('@')) - 1);
101 } else {
102 header.truncate(header.indexOf(QLatin1Char('@')));
103 }
104
105 return header;
106}
107
108/* List-Id: [^<]* <([^.]+) */
109static QString check_list_id(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
110{
111 QString header;
112 if (auto hrd = message->headerByType("List-Id")) {
113 header = hrd->asUnicodeString();
114 }
115 if (header.isEmpty()) {
116 return {};
117 }
118
119 const int leftAnglePos = header.indexOf(QLatin1Char('<'));
120 if (leftAnglePos < 0) {
121 return {};
122 }
123
124 const int firstDotPos = header.indexOf(QLatin1Char('.'), leftAnglePos);
125 if (firstDotPos < 0) {
126 return {};
127 }
128
129 headerName = "List-Id";
130 headerValue = header.mid(leftAnglePos);
131 header = header.mid(leftAnglePos + 1, firstDotPos - leftAnglePos - 1);
132
133 return header;
134}
135
136/* List-Post: <mailto:[^< ]*>) */
137static QString check_list_post(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
138{
139 QString header;
140 if (auto hrd = message->headerByType("List-Post")) {
141 header = hrd->asUnicodeString();
142 }
143 if (header.isEmpty()) {
144 return {};
145 }
146
147 int leftAnglePos = header.indexOf(QLatin1StringView("<mailto:"));
148 if (leftAnglePos < 0) {
149 return {};
150 }
151
152 headerName = "List-Post";
153 headerValue = header;
154 header = header.mid(leftAnglePos + 8, header.length());
155 header.truncate(header.indexOf(QLatin1Char('@')));
156
157 return header;
158}
159
160/* Mailing-List: list ([^@]+) */
161static QString check_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
162{
163 QString header;
164 if (auto hrd = message->headerByType("Mailing-List")) {
165 header = hrd->asUnicodeString();
166 }
167 if (header.isEmpty()) {
168 return {};
169 }
170
171 if (header.left(5) != QLatin1StringView("list ") || header.indexOf(QLatin1Char('@')) < 5) {
172 return {};
173 }
174
175 headerName = "Mailing-List";
176 headerValue = header;
177 header = header.mid(5, header.indexOf(QLatin1Char('@')) - 5);
178
179 return header;
180}
181
182/* X-Loop: ([^@]+) */
183static QString check_x_loop(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
184{
185 QString header;
186 if (auto hrd = message->headerByType("X-Loop")) {
187 header = hrd->asUnicodeString();
188 }
189 if (header.isEmpty()) {
190 return {};
191 }
192
193 const int indexOfHeader(header.indexOf(QLatin1Char('@')));
194 if (indexOfHeader < 2) {
195 return {};
196 }
197
198 headerName = "X-Loop";
199 headerValue = header;
200 header.truncate(indexOfHeader);
201
202 return header;
203}
204
205/* X-ML-Name: (.+) */
206static QString check_x_ml_name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
207{
208 QString header;
209 if (auto hrd = message->headerByType("X-ML-Name")) {
210 header = hrd->asUnicodeString();
211 }
212 if (header.isEmpty()) {
213 return {};
214 }
215
216 headerName = "X-ML-Name";
217 headerValue = header;
218 header.truncate(header.indexOf(QLatin1Char('@')));
219
220 return header;
221}
222
223static const MagicDetectorFunc magic_detectors[] = {check_list_id,
224 check_list_post,
225 check_sender,
226 check_x_mailing_list,
227 check_mailing_list,
228 check_delivered_to,
229 check_x_beenthere,
230 check_x_loop,
231 check_x_ml_name};
232
233static QStringList headerToAddress(const QString &header)
234{
235 QStringList addresses;
236 if (header.isEmpty()) {
237 return addresses;
238 }
239
240 int start = 0;
241 while ((start = header.indexOf(QLatin1Char('<'), start)) != -1) {
242 int end = 0;
243 if ((end = header.indexOf(QLatin1Char('>'), ++start)) == -1) {
244 qCWarning(MESSAGECORE_LOG) << "Serious mailing list header parsing error!";
245 return addresses;
246 }
247
248 addresses.append(header.mid(start, end - start));
249 }
250
251 return addresses;
252}
253
254class Q_DECL_HIDDEN MessageCore::MailingList::MailingListPrivate : public QSharedData
255{
256public:
257 MailingListPrivate()
258 : mFeatures(None)
259 , mHandler(KMail)
260 {
261 }
262
263 MailingListPrivate(const MailingListPrivate &other)
264 : QSharedData(other)
265 {
266 mFeatures = other.mFeatures;
267 mHandler = other.mHandler;
268 mPostUrls = other.mPostUrls;
269 mSubscribeUrls = other.mSubscribeUrls;
270 mUnsubscribeUrls = other.mUnsubscribeUrls;
271 mHelpUrls = other.mHelpUrls;
272 mArchiveUrls = other.mArchiveUrls;
273 mOwnerUrls = other.mOwnerUrls;
274 mArchivedAtUrls = other.mArchivedAtUrls;
275 mId = other.mId;
276 }
277
278 Features mFeatures;
279 Handler mHandler;
280 QList<QUrl> mPostUrls;
281 QList<QUrl> mSubscribeUrls;
282 QList<QUrl> mUnsubscribeUrls;
283 QList<QUrl> mHelpUrls;
284 QList<QUrl> mArchiveUrls;
285 QList<QUrl> mOwnerUrls;
286 QList<QUrl> mArchivedAtUrls;
287 QString mId;
288};
289
291{
292 MailingList mailingList;
293
294 if (auto hrd = message->headerByType("List-Post")) {
295 mailingList.setPostUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
296 }
297
298 if (auto hrd = message->headerByType("List-Help")) {
299 mailingList.setHelpUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
300 }
301
302 if (auto hrd = message->headerByType("List-Subscribe")) {
303 mailingList.setSubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
304 }
305
306 if (auto hrd = message->headerByType("List-Unsubscribe")) {
307 mailingList.setUnsubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
308 }
309
310 if (auto hrd = message->headerByType("List-Archive")) {
311 mailingList.setArchiveUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
312 }
313
314 if (auto hrd = message->headerByType("List-Owner")) {
315 mailingList.setOwnerUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
316 }
317
318 if (auto hrd = message->headerByType("Archived-At")) {
319 mailingList.setArchivedAtUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
320 }
321
322 if (auto hrd = message->headerByType("List-Id")) {
323 mailingList.setId(hrd->asUnicodeString());
324 }
325
326 return mailingList;
327}
328
329QString MailingList::name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
330{
331 QString mailingList;
332 headerName = QByteArray();
333 headerValue.clear();
334
335 if (!message) {
336 return {};
337 }
338
339 for (const MagicDetectorFunc &detector : magic_detectors) {
340 mailingList = detector(message, headerName, headerValue);
341 if (!mailingList.isNull()) {
342 return mailingList;
343 }
344 }
345
346 return {};
347}
348
350 : d(new MailingListPrivate)
351{
352}
353
355
356 = default;
357
359{
360 if (this != &other) {
361 d = other.d;
362 }
363
364 return *this;
365}
366
367bool MailingList::operator==(const MailingList &other) const
368{
369 return other.features() == d->mFeatures && other.handler() == d->mHandler && other.postUrls() == d->mPostUrls && other.subscribeUrls() == d->mSubscribeUrls
370 && other.unsubscribeUrls() == d->mUnsubscribeUrls && other.helpUrls() == d->mHelpUrls && other.archiveUrls() == d->mArchiveUrls
371 && other.ownerUrls() == d->mOwnerUrls && other.archivedAtUrls() == d->mArchivedAtUrls && other.id() == d->mId;
372}
373
374MailingList::~MailingList() = default;
375
377{
378 return d->mFeatures;
379}
380
382{
383 d->mHandler = handler;
384}
385
387{
388 return d->mHandler;
389}
390
392{
393 d->mFeatures |= Post;
394
395 if (urls.empty()) {
396 d->mFeatures ^= Post;
397 }
398
399 d->mPostUrls = urls;
400}
401
403{
404 return d->mPostUrls;
405}
406
408{
409 d->mFeatures |= Subscribe;
410
411 if (urls.empty()) {
412 d->mFeatures ^= Subscribe;
413 }
414
415 d->mSubscribeUrls = urls;
416}
417
419{
420 return d->mSubscribeUrls;
421}
422
424{
425 d->mFeatures |= Unsubscribe;
426
427 if (urls.empty()) {
428 d->mFeatures ^= Unsubscribe;
429 }
430
431 d->mUnsubscribeUrls = urls;
432}
433
435{
436 return d->mUnsubscribeUrls;
437}
438
440{
441 d->mFeatures |= Help;
442
443 if (urls.empty()) {
444 d->mFeatures ^= Help;
445 }
446
447 d->mHelpUrls = urls;
448}
449
451{
452 return d->mHelpUrls;
453}
454
456{
457 d->mFeatures |= Archive;
458
459 if (urls.empty()) {
460 d->mFeatures ^= Archive;
461 }
462
463 d->mArchiveUrls = urls;
464}
465
467{
468 return d->mArchiveUrls;
469}
470
472{
473 d->mFeatures |= Owner;
474
475 if (urls.empty()) {
476 d->mFeatures ^= Owner;
477 }
478
479 d->mOwnerUrls = urls;
480}
481
483{
484 return d->mOwnerUrls;
485}
486
488{
489 d->mFeatures |= ArchivedAt;
490
491 if (urls.isEmpty()) {
492 d->mFeatures ^= ArchivedAt;
493 }
494
495 d->mArchivedAtUrls = urls;
496}
497
499{
500 return d->mArchivedAtUrls;
501}
502
504{
505 d->mFeatures |= Id;
506
507 if (id.isEmpty()) {
508 d->mFeatures ^= Id;
509 }
510
511 d->mId = id;
512}
513
515{
516 return d->mId;
517}
518
520{
521 if (d->mFeatures != Feature::None) {
522 group.writeEntry("MailingListFeatures", static_cast<int>(d->mFeatures));
523 } else {
524 group.deleteEntry("MailingListFeatures");
525 }
526 if (d->mHandler != Handler::KMail) {
527 group.writeEntry("MailingListHandler", static_cast<int>(d->mHandler));
528 } else {
529 group.deleteEntry("MailingListHandler");
530 }
531 if (!d->mId.isEmpty()) {
532 group.writeEntry("MailingListId", d->mId);
533 } else {
534 group.deleteEntry("MailingListId");
535 }
536 QStringList lst = QUrl::toStringList(d->mPostUrls);
537 if (!lst.isEmpty()) {
538 group.writeEntry("MailingListPostingAddress", lst);
539 } else {
540 group.deleteEntry("MailingListPostingAddress");
541 }
542
543 lst = QUrl::toStringList(d->mSubscribeUrls);
544 if (!lst.isEmpty()) {
545 group.writeEntry("MailingListSubscribeAddress", lst);
546 } else {
547 group.deleteEntry("MailingListSubscribeAddress");
548 }
549
550 lst = QUrl::toStringList(d->mUnsubscribeUrls);
551 if (!lst.isEmpty()) {
552 group.writeEntry("MailingListUnsubscribeAddress", lst);
553 } else {
554 group.deleteEntry("MailingListUnsubscribeAddress");
555 }
556
557 lst = QUrl::toStringList(d->mArchiveUrls);
558 if (!lst.isEmpty()) {
559 group.writeEntry("MailingListArchiveAddress", lst);
560 } else {
561 group.deleteEntry("MailingListArchiveAddress");
562 }
563
564 lst = QUrl::toStringList(d->mOwnerUrls);
565 if (!lst.isEmpty()) {
566 group.writeEntry("MailingListOwnerAddress", lst);
567 } else {
568 group.deleteEntry("MailingListOwnerAddress");
569 }
570
571 lst = QUrl::toStringList(d->mHelpUrls);
572 if (!lst.isEmpty()) {
573 group.writeEntry("MailingListHelpAddress", lst);
574 } else {
575 group.deleteEntry("MailingListHelpAddress");
576 }
577
578 /* Note: mArchivedAtUrl deliberately not saved here as it refers to a single
579 * instance of a message rather than an element of a general mailing list.
580 * http://reviewboard.kde.org/r/1768/#review2783
581 */
582}
583
585{
586 d->mFeatures = static_cast<MailingList::Features>(group.readEntry("MailingListFeatures", 0));
587 d->mHandler = static_cast<MailingList::Handler>(group.readEntry("MailingListHandler", static_cast<int>(MailingList::KMail)));
588 d->mId = group.readEntry("MailingListId");
589 d->mPostUrls = QUrl::fromStringList(group.readEntry("MailingListPostingAddress", QStringList()));
590 d->mSubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListSubscribeAddress", QStringList()));
591 d->mUnsubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListUnsubscribeAddress", QStringList()));
592 d->mArchiveUrls = QUrl::fromStringList(group.readEntry("MailingListArchiveAddress", QStringList()));
593 d->mOwnerUrls = QUrl::fromStringList(group.readEntry("MailingListOwnerAddress", QStringList()));
594 d->mHelpUrls = QUrl::fromStringList(group.readEntry("MailingListHelpAddress", QStringList()));
595}
void deleteEntry(const char *key, WriteConfigFlags pFlags=Normal)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
A class to extract information about mailing lists from emails.
Definition mailinglist.h:32
MailingList()
Creates an empty mailing list.
Handler
Defines what entity should manage the mailing list.
Definition mailinglist.h:37
@ KMail
The list is handled by KMail.
Definition mailinglist.h:38
void setHelpUrls(const QList< QUrl > &urls)
Sets the list of List-Help urls.
~MailingList()
Destroys the mailing list.
static MailingList detect(const KMime::Message::Ptr &message)
Extracts the information about a mailing list from the given message.
void setId(const QString &id)
Sets the id of the mailing list.
QList< QUrl > archiveUrls() const
Returns the list of List-Archive urls.
void setHandler(Handler handler)
Sets the handler for the mailing list.
void setUnsubscribeUrls(const QList< QUrl > &urls)
Sets the list of List-Unsubscribe urls.
QList< QUrl > helpUrls() const
Returns the list of List-Help urls.
Handler handler() const
Returns the handler for the mailing list.
QList< QUrl > archivedAtUrls() const
Returns the Archived-At url.
void writeConfig(KConfigGroup &group) const
Saves the configuration for the mailing list to the config group.
void setArchiveUrls(const QList< QUrl > &urls)
Sets the list of List-Archive urls.
QList< QUrl > postUrls() const
Returns the list of List-Post urls.
void setArchivedAtUrls(const QList< QUrl > &url)
Sets the Archived-At url.
QList< QUrl > ownerUrls() const
Returns the list of List-Owner urls.
QString id() const
Returns the id of the mailing list.
@ Owner
List-Owner header exists.
Definition mailinglist.h:53
@ Id
List-ID header exists.
Definition mailinglist.h:52
@ Archive
List-Archive header exists.
Definition mailinglist.h:51
@ Help
List-Help header exists.
Definition mailinglist.h:50
@ ArchivedAt
Archive-At header exists.
Definition mailinglist.h:54
@ Unsubscribe
List-Unsubscribe header exists.
Definition mailinglist.h:49
@ None
No mailing list fields exist.
Definition mailinglist.h:46
@ Subscribe
List-Subscribe header exists.
Definition mailinglist.h:48
@ Post
List-Post header exists.
Definition mailinglist.h:47
void setSubscribeUrls(const QList< QUrl > &urls)
Sets the list of List-Subscribe urls.
Features features() const
Returns the features the mailing list supports.
MailingList & operator=(const MailingList &other)
Overwrites this mailing list with an other mailing list.
void readConfig(const KConfigGroup &group)
Restores the configuration for the mailing list from the config group.
void setOwnerUrls(const QList< QUrl > &urls)
Sets the list of List-Owner urls.
QList< QUrl > unsubscribeUrls() const
Returns the list of List-Unsubscribe urls.
QList< QUrl > subscribeUrls() const
Returns the list of List-Subscribe urls.
void setPostUrls(const QList< QUrl > &urls)
Sets the list of List-Post urls.
Q_SCRIPTABLE Q_NOREPLY void start()
const QList< QKeySequence > & end()
void append(QList< T > &&value)
bool empty() const const
bool isEmpty() const const
void clear()
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
void truncate(qsizetype position)
QList< QUrl > fromStringList(const QStringList &urls, ParsingMode mode)
QStringList toStringList(const QList< QUrl > &urls, FormattingOptions options)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:59:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.