File indexing completed on 2025-03-09 04:54:13
0001 /* 0002 * SPDX-License-Identifier: LGPL-2.1-or-later 0003 * 0004 */ 0005 0006 #include "mailinglist.h" 0007 0008 #include "messagecore_debug.h" 0009 #include <KConfig> 0010 #include <KConfigGroup> 0011 #include <QUrl> 0012 0013 #include <QSharedData> 0014 #include <QStringList> 0015 0016 using namespace MessageCore; 0017 0018 using MagicDetectorFunc = QString (*)(const KMime::Message::Ptr &, QByteArray &, QString &); 0019 0020 /* Sender: (owner-([^@]+)|([^@+]-owner)@ */ 0021 static QString check_sender(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0022 { 0023 QString header = message->sender()->asUnicodeString(); 0024 0025 if (header.isEmpty()) { 0026 return {}; 0027 } 0028 0029 if (header.left(6) == QLatin1StringView("owner-")) { 0030 headerName = "Sender"; 0031 headerValue = header; 0032 header = header.mid(6, header.indexOf(QLatin1Char('@')) - 6); 0033 } else { 0034 const int index = header.indexOf(QLatin1StringView("-owner@ ")); 0035 if (index == -1) { 0036 return {}; 0037 } 0038 0039 header.truncate(index); 0040 headerName = "Sender"; 0041 headerValue = header; 0042 } 0043 0044 return header; 0045 } 0046 0047 /* X-BeenThere: ([^@]+) */ 0048 static QString check_x_beenthere(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0049 { 0050 QString header; 0051 if (auto hrd = message->headerByType("X-BeenThere")) { 0052 header = hrd->asUnicodeString(); 0053 } 0054 if (header.isNull() || header.indexOf(QLatin1Char('@')) == -1) { 0055 return {}; 0056 } 0057 0058 headerName = "X-BeenThere"; 0059 headerValue = header; 0060 header.truncate(header.indexOf(QLatin1Char('@'))); 0061 0062 return header; 0063 } 0064 0065 /* Delivered-To:: <([^@]+) */ 0066 static QString check_delivered_to(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0067 { 0068 QString header; 0069 if (auto hrd = message->headerByType("Delivered-To")) { 0070 header = hrd->asUnicodeString(); 0071 } 0072 if (header.isNull() || header.left(13) != QLatin1StringView("mailing list") || header.indexOf(QLatin1Char('@')) == -1) { 0073 return {}; 0074 } 0075 0076 headerName = "Delivered-To"; 0077 headerValue = header; 0078 0079 return header.mid(13, header.indexOf(QLatin1Char('@')) - 13); 0080 } 0081 0082 /* X-Mailing-List: <?([^@]+) */ 0083 static QString check_x_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0084 { 0085 QString header; 0086 if (auto hrd = message->headerByType("X-Mailing-List")) { 0087 header = hrd->asUnicodeString(); 0088 } 0089 if (header.isEmpty()) { 0090 return {}; 0091 } 0092 0093 if (header.indexOf(QLatin1Char('@')) < 1) { 0094 return {}; 0095 } 0096 0097 headerName = "X-Mailing-List"; 0098 headerValue = header; 0099 if (header[0] == QLatin1Char('<')) { 0100 header = header.mid(1, header.indexOf(QLatin1Char('@')) - 1); 0101 } else { 0102 header.truncate(header.indexOf(QLatin1Char('@'))); 0103 } 0104 0105 return header; 0106 } 0107 0108 /* List-Id: [^<]* <([^.]+) */ 0109 static QString check_list_id(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0110 { 0111 QString header; 0112 if (auto hrd = message->headerByType("List-Id")) { 0113 header = hrd->asUnicodeString(); 0114 } 0115 if (header.isEmpty()) { 0116 return {}; 0117 } 0118 0119 const int leftAnglePos = header.indexOf(QLatin1Char('<')); 0120 if (leftAnglePos < 0) { 0121 return {}; 0122 } 0123 0124 const int firstDotPos = header.indexOf(QLatin1Char('.'), leftAnglePos); 0125 if (firstDotPos < 0) { 0126 return {}; 0127 } 0128 0129 headerName = "List-Id"; 0130 headerValue = header.mid(leftAnglePos); 0131 header = header.mid(leftAnglePos + 1, firstDotPos - leftAnglePos - 1); 0132 0133 return header; 0134 } 0135 0136 /* List-Post: <mailto:[^< ]*>) */ 0137 static QString check_list_post(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0138 { 0139 QString header; 0140 if (auto hrd = message->headerByType("List-Post")) { 0141 header = hrd->asUnicodeString(); 0142 } 0143 if (header.isEmpty()) { 0144 return {}; 0145 } 0146 0147 int leftAnglePos = header.indexOf(QLatin1StringView("<mailto:")); 0148 if (leftAnglePos < 0) { 0149 return {}; 0150 } 0151 0152 headerName = "List-Post"; 0153 headerValue = header; 0154 header = header.mid(leftAnglePos + 8, header.length()); 0155 header.truncate(header.indexOf(QLatin1Char('@'))); 0156 0157 return header; 0158 } 0159 0160 /* Mailing-List: list ([^@]+) */ 0161 static QString check_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0162 { 0163 QString header; 0164 if (auto hrd = message->headerByType("Mailing-List")) { 0165 header = hrd->asUnicodeString(); 0166 } 0167 if (header.isEmpty()) { 0168 return {}; 0169 } 0170 0171 if (header.left(5) != QLatin1StringView("list ") || header.indexOf(QLatin1Char('@')) < 5) { 0172 return {}; 0173 } 0174 0175 headerName = "Mailing-List"; 0176 headerValue = header; 0177 header = header.mid(5, header.indexOf(QLatin1Char('@')) - 5); 0178 0179 return header; 0180 } 0181 0182 /* X-Loop: ([^@]+) */ 0183 static QString check_x_loop(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0184 { 0185 QString header; 0186 if (auto hrd = message->headerByType("X-Loop")) { 0187 header = hrd->asUnicodeString(); 0188 } 0189 if (header.isEmpty()) { 0190 return {}; 0191 } 0192 0193 const int indexOfHeader(header.indexOf(QLatin1Char('@'))); 0194 if (indexOfHeader < 2) { 0195 return {}; 0196 } 0197 0198 headerName = "X-Loop"; 0199 headerValue = header; 0200 header.truncate(indexOfHeader); 0201 0202 return header; 0203 } 0204 0205 /* X-ML-Name: (.+) */ 0206 static QString check_x_ml_name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0207 { 0208 QString header; 0209 if (auto hrd = message->headerByType("X-ML-Name")) { 0210 header = hrd->asUnicodeString(); 0211 } 0212 if (header.isEmpty()) { 0213 return {}; 0214 } 0215 0216 headerName = "X-ML-Name"; 0217 headerValue = header; 0218 header.truncate(header.indexOf(QLatin1Char('@'))); 0219 0220 return header; 0221 } 0222 0223 static const MagicDetectorFunc magic_detectors[] = {check_list_id, 0224 check_list_post, 0225 check_sender, 0226 check_x_mailing_list, 0227 check_mailing_list, 0228 check_delivered_to, 0229 check_x_beenthere, 0230 check_x_loop, 0231 check_x_ml_name}; 0232 0233 static QStringList headerToAddress(const QString &header) 0234 { 0235 QStringList addresses; 0236 if (header.isEmpty()) { 0237 return addresses; 0238 } 0239 0240 int start = 0; 0241 while ((start = header.indexOf(QLatin1Char('<'), start)) != -1) { 0242 int end = 0; 0243 if ((end = header.indexOf(QLatin1Char('>'), ++start)) == -1) { 0244 qCWarning(MESSAGECORE_LOG) << "Serious mailing list header parsing error!"; 0245 return addresses; 0246 } 0247 0248 addresses.append(header.mid(start, end - start)); 0249 } 0250 0251 return addresses; 0252 } 0253 0254 class Q_DECL_HIDDEN MessageCore::MailingList::MailingListPrivate : public QSharedData 0255 { 0256 public: 0257 MailingListPrivate() 0258 : mFeatures(None) 0259 , mHandler(KMail) 0260 { 0261 } 0262 0263 MailingListPrivate(const MailingListPrivate &other) 0264 : QSharedData(other) 0265 { 0266 mFeatures = other.mFeatures; 0267 mHandler = other.mHandler; 0268 mPostUrls = other.mPostUrls; 0269 mSubscribeUrls = other.mSubscribeUrls; 0270 mUnsubscribeUrls = other.mUnsubscribeUrls; 0271 mHelpUrls = other.mHelpUrls; 0272 mArchiveUrls = other.mArchiveUrls; 0273 mOwnerUrls = other.mOwnerUrls; 0274 mArchivedAtUrls = other.mArchivedAtUrls; 0275 mId = other.mId; 0276 } 0277 0278 Features mFeatures; 0279 Handler mHandler; 0280 QList<QUrl> mPostUrls; 0281 QList<QUrl> mSubscribeUrls; 0282 QList<QUrl> mUnsubscribeUrls; 0283 QList<QUrl> mHelpUrls; 0284 QList<QUrl> mArchiveUrls; 0285 QList<QUrl> mOwnerUrls; 0286 QList<QUrl> mArchivedAtUrls; 0287 QString mId; 0288 }; 0289 0290 MailingList MailingList::detect(const KMime::Message::Ptr &message) 0291 { 0292 MailingList mailingList; 0293 0294 if (auto hrd = message->headerByType("List-Post")) { 0295 mailingList.setPostUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0296 } 0297 0298 if (auto hrd = message->headerByType("List-Help")) { 0299 mailingList.setHelpUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0300 } 0301 0302 if (auto hrd = message->headerByType("List-Subscribe")) { 0303 mailingList.setSubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0304 } 0305 0306 if (auto hrd = message->headerByType("List-Unsubscribe")) { 0307 mailingList.setUnsubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0308 } 0309 0310 if (auto hrd = message->headerByType("List-Archive")) { 0311 mailingList.setArchiveUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0312 } 0313 0314 if (auto hrd = message->headerByType("List-Owner")) { 0315 mailingList.setOwnerUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0316 } 0317 0318 if (auto hrd = message->headerByType("Archived-At")) { 0319 mailingList.setArchivedAtUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString()))); 0320 } 0321 0322 if (auto hrd = message->headerByType("List-Id")) { 0323 mailingList.setId(hrd->asUnicodeString()); 0324 } 0325 0326 return mailingList; 0327 } 0328 0329 QString MailingList::name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue) 0330 { 0331 QString mailingList; 0332 headerName = QByteArray(); 0333 headerValue.clear(); 0334 0335 if (!message) { 0336 return {}; 0337 } 0338 0339 for (const MagicDetectorFunc &detector : magic_detectors) { 0340 mailingList = detector(message, headerName, headerValue); 0341 if (!mailingList.isNull()) { 0342 return mailingList; 0343 } 0344 } 0345 0346 return {}; 0347 } 0348 0349 MailingList::MailingList() 0350 : d(new MailingListPrivate) 0351 { 0352 } 0353 0354 MailingList::MailingList(const MailingList &other) 0355 0356 = default; 0357 0358 MailingList &MailingList::operator=(const MailingList &other) 0359 { 0360 if (this != &other) { 0361 d = other.d; 0362 } 0363 0364 return *this; 0365 } 0366 0367 bool MailingList::operator==(const MailingList &other) const 0368 { 0369 return other.features() == d->mFeatures && other.handler() == d->mHandler && other.postUrls() == d->mPostUrls && other.subscribeUrls() == d->mSubscribeUrls 0370 && other.unsubscribeUrls() == d->mUnsubscribeUrls && other.helpUrls() == d->mHelpUrls && other.archiveUrls() == d->mArchiveUrls 0371 && other.ownerUrls() == d->mOwnerUrls && other.archivedAtUrls() == d->mArchivedAtUrls && other.id() == d->mId; 0372 } 0373 0374 MailingList::~MailingList() = default; 0375 0376 MailingList::Features MailingList::features() const 0377 { 0378 return d->mFeatures; 0379 } 0380 0381 void MailingList::setHandler(MailingList::Handler handler) 0382 { 0383 d->mHandler = handler; 0384 } 0385 0386 MailingList::Handler MailingList::handler() const 0387 { 0388 return d->mHandler; 0389 } 0390 0391 void MailingList::setPostUrls(const QList<QUrl> &urls) 0392 { 0393 d->mFeatures |= Post; 0394 0395 if (urls.empty()) { 0396 d->mFeatures ^= Post; 0397 } 0398 0399 d->mPostUrls = urls; 0400 } 0401 0402 QList<QUrl> MailingList::postUrls() const 0403 { 0404 return d->mPostUrls; 0405 } 0406 0407 void MailingList::setSubscribeUrls(const QList<QUrl> &urls) 0408 { 0409 d->mFeatures |= Subscribe; 0410 0411 if (urls.empty()) { 0412 d->mFeatures ^= Subscribe; 0413 } 0414 0415 d->mSubscribeUrls = urls; 0416 } 0417 0418 QList<QUrl> MailingList::subscribeUrls() const 0419 { 0420 return d->mSubscribeUrls; 0421 } 0422 0423 void MailingList::setUnsubscribeUrls(const QList<QUrl> &urls) 0424 { 0425 d->mFeatures |= Unsubscribe; 0426 0427 if (urls.empty()) { 0428 d->mFeatures ^= Unsubscribe; 0429 } 0430 0431 d->mUnsubscribeUrls = urls; 0432 } 0433 0434 QList<QUrl> MailingList::unsubscribeUrls() const 0435 { 0436 return d->mUnsubscribeUrls; 0437 } 0438 0439 void MailingList::setHelpUrls(const QList<QUrl> &urls) 0440 { 0441 d->mFeatures |= Help; 0442 0443 if (urls.empty()) { 0444 d->mFeatures ^= Help; 0445 } 0446 0447 d->mHelpUrls = urls; 0448 } 0449 0450 QList<QUrl> MailingList::helpUrls() const 0451 { 0452 return d->mHelpUrls; 0453 } 0454 0455 void MailingList::setArchiveUrls(const QList<QUrl> &urls) 0456 { 0457 d->mFeatures |= Archive; 0458 0459 if (urls.empty()) { 0460 d->mFeatures ^= Archive; 0461 } 0462 0463 d->mArchiveUrls = urls; 0464 } 0465 0466 QList<QUrl> MailingList::archiveUrls() const 0467 { 0468 return d->mArchiveUrls; 0469 } 0470 0471 void MailingList::setOwnerUrls(const QList<QUrl> &urls) 0472 { 0473 d->mFeatures |= Owner; 0474 0475 if (urls.empty()) { 0476 d->mFeatures ^= Owner; 0477 } 0478 0479 d->mOwnerUrls = urls; 0480 } 0481 0482 QList<QUrl> MailingList::ownerUrls() const 0483 { 0484 return d->mOwnerUrls; 0485 } 0486 0487 void MailingList::setArchivedAtUrls(const QList<QUrl> &urls) 0488 { 0489 d->mFeatures |= ArchivedAt; 0490 0491 if (urls.isEmpty()) { 0492 d->mFeatures ^= ArchivedAt; 0493 } 0494 0495 d->mArchivedAtUrls = urls; 0496 } 0497 0498 QList<QUrl> MailingList::archivedAtUrls() const 0499 { 0500 return d->mArchivedAtUrls; 0501 } 0502 0503 void MailingList::setId(const QString &id) 0504 { 0505 d->mFeatures |= Id; 0506 0507 if (id.isEmpty()) { 0508 d->mFeatures ^= Id; 0509 } 0510 0511 d->mId = id; 0512 } 0513 0514 QString MailingList::id() const 0515 { 0516 return d->mId; 0517 } 0518 0519 void MailingList::writeConfig(KConfigGroup &group) const 0520 { 0521 if (d->mFeatures != Feature::None) { 0522 group.writeEntry("MailingListFeatures", static_cast<int>(d->mFeatures)); 0523 } else { 0524 group.deleteEntry("MailingListFeatures"); 0525 } 0526 if (d->mHandler != Handler::KMail) { 0527 group.writeEntry("MailingListHandler", static_cast<int>(d->mHandler)); 0528 } else { 0529 group.deleteEntry("MailingListHandler"); 0530 } 0531 if (!d->mId.isEmpty()) { 0532 group.writeEntry("MailingListId", d->mId); 0533 } else { 0534 group.deleteEntry("MailingListId"); 0535 } 0536 QStringList lst = QUrl::toStringList(d->mPostUrls); 0537 if (!lst.isEmpty()) { 0538 group.writeEntry("MailingListPostingAddress", lst); 0539 } else { 0540 group.deleteEntry("MailingListPostingAddress"); 0541 } 0542 0543 lst = QUrl::toStringList(d->mSubscribeUrls); 0544 if (!lst.isEmpty()) { 0545 group.writeEntry("MailingListSubscribeAddress", lst); 0546 } else { 0547 group.deleteEntry("MailingListSubscribeAddress"); 0548 } 0549 0550 lst = QUrl::toStringList(d->mUnsubscribeUrls); 0551 if (!lst.isEmpty()) { 0552 group.writeEntry("MailingListUnsubscribeAddress", lst); 0553 } else { 0554 group.deleteEntry("MailingListUnsubscribeAddress"); 0555 } 0556 0557 lst = QUrl::toStringList(d->mArchiveUrls); 0558 if (!lst.isEmpty()) { 0559 group.writeEntry("MailingListArchiveAddress", lst); 0560 } else { 0561 group.deleteEntry("MailingListArchiveAddress"); 0562 } 0563 0564 lst = QUrl::toStringList(d->mOwnerUrls); 0565 if (!lst.isEmpty()) { 0566 group.writeEntry("MailingListOwnerAddress", lst); 0567 } else { 0568 group.deleteEntry("MailingListOwnerAddress"); 0569 } 0570 0571 lst = QUrl::toStringList(d->mHelpUrls); 0572 if (!lst.isEmpty()) { 0573 group.writeEntry("MailingListHelpAddress", lst); 0574 } else { 0575 group.deleteEntry("MailingListHelpAddress"); 0576 } 0577 0578 /* Note: mArchivedAtUrl deliberately not saved here as it refers to a single 0579 * instance of a message rather than an element of a general mailing list. 0580 * http://reviewboard.kde.org/r/1768/#review2783 0581 */ 0582 } 0583 0584 void MailingList::readConfig(const KConfigGroup &group) 0585 { 0586 d->mFeatures = static_cast<MailingList::Features>(group.readEntry("MailingListFeatures", 0)); 0587 d->mHandler = static_cast<MailingList::Handler>(group.readEntry("MailingListHandler", static_cast<int>(MailingList::KMail))); 0588 d->mId = group.readEntry("MailingListId"); 0589 d->mPostUrls = QUrl::fromStringList(group.readEntry("MailingListPostingAddress", QStringList())); 0590 d->mSubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListSubscribeAddress", QStringList())); 0591 d->mUnsubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListUnsubscribeAddress", QStringList())); 0592 d->mArchiveUrls = QUrl::fromStringList(group.readEntry("MailingListArchiveAddress", QStringList())); 0593 d->mOwnerUrls = QUrl::fromStringList(group.readEntry("MailingListOwnerAddress", QStringList())); 0594 d->mHelpUrls = QUrl::fromStringList(group.readEntry("MailingListHelpAddress", QStringList())); 0595 }