File indexing completed on 2024-11-17 04:51:14
0001 /* 0002 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "searchrule.h" 0007 #include "mailcommon_debug.h" 0008 #include "searchrule/searchruledate.h" 0009 #include "searchrule/searchruleencryption.h" 0010 #include "searchrule/searchrulenumerical.h" 0011 #include "searchrule/searchrulestatus.h" 0012 #include "searchrule/searchrulestring.h" 0013 0014 #include <KConfigGroup> 0015 0016 #include <QDataStream> 0017 0018 #include <algorithm> 0019 0020 using namespace MailCommon; 0021 0022 static const char *const funcConfigNames[] = {"contains", 0023 "contains-not", 0024 "equals", 0025 "not-equal", 0026 "regexp", 0027 "not-regexp", 0028 "greater", 0029 "less-or-equal", 0030 "less", 0031 "greater-or-equal", 0032 "is-in-addressbook", 0033 "is-not-in-addressbook", 0034 "is-in-category", 0035 "is-not-in-category", 0036 "has-attachment", 0037 "has-no-attachment", 0038 "start-with", 0039 "not-start-with", 0040 "end-with", 0041 "not-end-with"}; 0042 0043 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames; 0044 0045 //================================================== 0046 // 0047 // class SearchRule (was: KMFilterRule) 0048 // 0049 //================================================== 0050 0051 SearchRule::SearchRule(const QByteArray &field, Function func, const QString &contents) 0052 : mField(field) 0053 , mFunction(func) 0054 , mContents(contents) 0055 { 0056 } 0057 0058 SearchRule::SearchRule(const SearchRule &other) 0059 0060 = default; 0061 0062 const SearchRule &SearchRule::operator=(const SearchRule &other) 0063 { 0064 if (this == &other) { 0065 return *this; 0066 } 0067 0068 mField = other.mField; 0069 mFunction = other.mFunction; 0070 mContents = other.mContents; 0071 0072 return *this; 0073 } 0074 0075 SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, Function func, const QString &contents) 0076 { 0077 SearchRule::Ptr ret; 0078 if (field == "<status>") { 0079 ret = SearchRule::Ptr(new SearchRuleStatus(field, func, contents)); 0080 } else if (field == "<age in days>" || field == "<size>") { 0081 ret = SearchRule::Ptr(new SearchRuleNumerical(field, func, contents)); 0082 } else if (field == "<date>") { 0083 ret = SearchRule::Ptr(new SearchRuleDate(field, func, contents)); 0084 } else if (field == "<encryption>") { 0085 ret = SearchRule::Ptr(new SearchRuleEncryption(field, func, contents)); 0086 } else { 0087 ret = SearchRule::Ptr(new SearchRuleString(field, func, contents)); 0088 } 0089 0090 return ret; 0091 } 0092 0093 SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, const char *func, const QString &contents) 0094 { 0095 return createInstance(field, configValueToFunc(func), contents); 0096 } 0097 0098 SearchRule::Ptr SearchRule::createInstance(const SearchRule &other) 0099 { 0100 return createInstance(other.field(), other.function(), other.contents()); 0101 } 0102 0103 SearchRule::Ptr SearchRule::createInstanceFromConfig(const KConfigGroup &config, int aIdx) 0104 { 0105 const char cIdx = char(int('A') + aIdx); 0106 0107 static const QString field = QStringLiteral("field"); 0108 static const QString func = QStringLiteral("func"); 0109 static const QString contents = QStringLiteral("contents"); 0110 0111 const QByteArray &field2 = config.readEntry(field + cIdx, QString()).toLatin1(); 0112 Function func2 = configValueToFunc(config.readEntry(func + cIdx, QString()).toLatin1().constData()); 0113 const QString &contents2 = config.readEntry(contents + cIdx, QString()); 0114 0115 if (field2 == "<To or Cc>") { // backwards compat 0116 return SearchRule::createInstance("<recipients>", func2, contents2); 0117 } else { 0118 return SearchRule::createInstance(field2, func2, contents2); 0119 } 0120 } 0121 0122 SearchRule::Ptr SearchRule::createInstance(QDataStream &s) 0123 { 0124 QByteArray field; 0125 s >> field; 0126 QString function; 0127 s >> function; 0128 Function func = configValueToFunc(function.toUtf8().constData()); 0129 QString contents; 0130 s >> contents; 0131 return createInstance(field, func, contents); 0132 } 0133 0134 SearchRule::~SearchRule() = default; 0135 0136 SearchRule::Function SearchRule::configValueToFunc(const char *str) 0137 { 0138 if (!str) { 0139 return FuncNone; 0140 } 0141 0142 for (int i = 0; i < numFuncConfigNames; ++i) { 0143 if (qstricmp(funcConfigNames[i], str) == 0) { 0144 return static_cast<Function>(i); 0145 } 0146 } 0147 0148 return FuncNone; 0149 } 0150 0151 QString SearchRule::functionToString(Function function) 0152 { 0153 if (function != FuncNone) { 0154 return funcConfigNames[int(function)]; 0155 } else { 0156 return QStringLiteral("invalid"); 0157 } 0158 } 0159 0160 void SearchRule::writeConfig(KConfigGroup &config, int aIdx) const 0161 { 0162 const char cIdx = char('A' + aIdx); 0163 static const QString field = QStringLiteral("field"); 0164 static const QString func = QStringLiteral("func"); 0165 static const QString contents = QStringLiteral("contents"); 0166 0167 config.writeEntry(field + cIdx, /*QString*/ (mField)); 0168 config.writeEntry(func + cIdx, functionToString(mFunction)); 0169 config.writeEntry(contents + cIdx, mContents); 0170 } 0171 0172 QString SearchRule::conditionToString(Function function) 0173 { 0174 QString str; 0175 switch (function) { 0176 case FuncEquals: 0177 str = i18n("equal"); 0178 break; 0179 case FuncNotEqual: 0180 str = i18n("not equal"); 0181 break; 0182 case FuncIsGreater: 0183 str = i18n("is greater"); 0184 break; 0185 case FuncIsLessOrEqual: 0186 str = i18n("is less or equal"); 0187 break; 0188 case FuncIsLess: 0189 str = i18n("is less"); 0190 break; 0191 case FuncIsGreaterOrEqual: 0192 str = i18n("is greater or equal"); 0193 break; 0194 case FuncIsInAddressbook: 0195 str = i18n("is in addressbook"); 0196 break; 0197 case FuncIsNotInAddressbook: 0198 str = i18n("is not in addressbook"); 0199 break; 0200 case FuncIsInCategory: 0201 str = i18n("is in category"); 0202 break; 0203 case FuncIsNotInCategory: 0204 str = i18n("is in category"); 0205 break; 0206 case FuncHasAttachment: 0207 str = i18n("has an attachment"); 0208 break; 0209 case FuncHasNoAttachment: 0210 str = i18n("has not an attachment"); 0211 break; 0212 case FuncStartWith: 0213 str = i18n("start with"); 0214 break; 0215 case FuncNotStartWith: 0216 str = i18n("not start with"); 0217 break; 0218 case FuncEndWith: 0219 str = i18n("end with"); 0220 break; 0221 case FuncNotEndWith: 0222 str = i18n("not end with"); 0223 break; 0224 case FuncNone: 0225 str = i18n("none"); 0226 break; 0227 case FuncContains: 0228 str = i18n("contains"); 0229 break; 0230 case FuncContainsNot: 0231 str = i18n("not contains"); 0232 break; 0233 case FuncRegExp: 0234 str = i18n("has regexp"); 0235 break; 0236 case FuncNotRegExp: 0237 str = i18n("not regexp"); 0238 break; 0239 } 0240 return str; 0241 } 0242 0243 void SearchRule::generateSieveScript(QStringList &requireModules, QString &code) 0244 { 0245 QString contentStr = mContents; 0246 if (mField == "<size>") { 0247 QString comparison; 0248 int offset = 0; 0249 switch (mFunction) { 0250 case FuncEquals: 0251 comparison = QLatin1Char('"') + i18n("size equals not supported") + QLatin1Char('"'); 0252 break; 0253 case FuncNotEqual: 0254 comparison = QLatin1Char('"') + i18n("size not equals not supported") + QLatin1Char('"'); 0255 break; 0256 case FuncIsGreater: 0257 comparison = QStringLiteral(":over"); 0258 break; 0259 case FuncIsLessOrEqual: 0260 comparison = QStringLiteral(":under"); 0261 offset = 1; 0262 break; 0263 case FuncIsLess: 0264 comparison = QStringLiteral(":under"); 0265 break; 0266 case FuncIsGreaterOrEqual: 0267 comparison = QStringLiteral(":over"); 0268 offset = -1; 0269 break; 0270 case FuncIsInAddressbook: 0271 case FuncIsNotInAddressbook: 0272 case FuncIsInCategory: 0273 case FuncIsNotInCategory: 0274 case FuncHasAttachment: 0275 case FuncHasNoAttachment: 0276 case FuncStartWith: 0277 case FuncNotStartWith: 0278 case FuncEndWith: 0279 case FuncNotEndWith: 0280 case FuncNone: 0281 case FuncContains: 0282 case FuncContainsNot: 0283 case FuncRegExp: 0284 case FuncNotRegExp: 0285 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction)) 0286 + QLatin1Char('"'); 0287 return; 0288 } 0289 code += QStringLiteral("size %1 %2K").arg(comparison).arg(QString::number(mContents.toInt() + offset)); 0290 } else if (mField == "<status>") { 0291 // TODO ? 0292 code += QLatin1Char('"') + i18n("<status> not implemented/supported") + QLatin1Char('"'); 0293 } else if (mField == "<any header>") { 0294 // TODO ? 0295 code += QLatin1Char('"') + i18n("<any header> not implemented/supported") + QLatin1Char('"'); 0296 } else if (mField == "contents") { 0297 // TODO ? 0298 code += QLatin1Char('"') + i18n("<contents> not implemented/supported") + QLatin1Char('"'); 0299 } else if (mField == "<age in days>") { 0300 // TODO ? 0301 code += QLatin1Char('"') + i18n("<age in days> not implemented/supported") + QLatin1Char('"'); 0302 } else if (mField == "<date>") { 0303 // TODO ? 0304 code += QLatin1Char('"') + i18n("<date> not implemented/supported") + QLatin1Char('"'); 0305 } else if (mField == "<recipients>") { 0306 // TODO ? 0307 code += QLatin1Char('"') + i18n("<recipients> not implemented/supported") + QLatin1Char('"'); 0308 } else if (mField == "<tag>") { 0309 code += QLatin1Char('"') + i18n("<Tag> is not supported") + QLatin1Char('"'); 0310 } else if (mField == "<message>") { 0311 // TODO ? 0312 code += i18n("<message> not implemented/supported"); 0313 } else if (mField == "<body>") { 0314 if (!requireModules.contains(QLatin1StringView("body"))) { 0315 requireModules << QStringLiteral("body"); 0316 } 0317 QString comparison; 0318 bool negative = false; 0319 switch (mFunction) { 0320 case FuncNone: 0321 break; 0322 case FuncContains: 0323 comparison = QStringLiteral(":contains"); 0324 break; 0325 case FuncContainsNot: 0326 negative = true; 0327 comparison = QStringLiteral(":contains"); 0328 break; 0329 case FuncEquals: 0330 comparison = QStringLiteral(":is"); 0331 break; 0332 case FuncNotEqual: 0333 comparison = QStringLiteral(":is"); 0334 negative = true; 0335 break; 0336 case FuncRegExp: 0337 comparison = QStringLiteral(":regex"); 0338 if (!requireModules.contains(QLatin1StringView("regex"))) { 0339 requireModules << QStringLiteral("regex"); 0340 } 0341 break; 0342 case FuncNotRegExp: 0343 if (!requireModules.contains(QLatin1StringView("regex"))) { 0344 requireModules << QStringLiteral("regex"); 0345 } 0346 comparison = QStringLiteral(":regex"); 0347 negative = true; 0348 break; 0349 case FuncStartWith: 0350 comparison = QStringLiteral(":regex"); 0351 if (!requireModules.contains(QLatin1StringView("regex"))) { 0352 requireModules << QStringLiteral("regex"); 0353 } 0354 contentStr = QLatin1Char('^') + contentStr; 0355 break; 0356 case FuncNotStartWith: 0357 comparison = QStringLiteral(":regex"); 0358 if (!requireModules.contains(QLatin1StringView("regex"))) { 0359 requireModules << QStringLiteral("regex"); 0360 } 0361 comparison = QStringLiteral(":regex"); 0362 contentStr = QLatin1Char('^') + contentStr; 0363 negative = true; 0364 break; 0365 case FuncEndWith: 0366 comparison = QStringLiteral(":regex"); 0367 if (!requireModules.contains(QLatin1StringView("regex"))) { 0368 requireModules << QStringLiteral("regex"); 0369 } 0370 comparison = QStringLiteral(":regex"); 0371 contentStr = contentStr + QLatin1Char('$'); 0372 break; 0373 case FuncNotEndWith: 0374 comparison = QStringLiteral(":regex"); 0375 if (!requireModules.contains(QLatin1StringView("regex"))) { 0376 requireModules << QStringLiteral("regex"); 0377 } 0378 comparison = QStringLiteral(":regex"); 0379 contentStr = contentStr + QLatin1Char('$'); 0380 negative = true; 0381 break; 0382 case FuncIsGreater: 0383 case FuncIsLessOrEqual: 0384 case FuncIsLess: 0385 case FuncIsGreaterOrEqual: 0386 case FuncIsInAddressbook: 0387 case FuncIsNotInAddressbook: 0388 case FuncIsInCategory: 0389 case FuncIsNotInCategory: 0390 case FuncHasAttachment: 0391 case FuncHasNoAttachment: 0392 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction)) 0393 + QLatin1Char('"'); 0394 return; 0395 } 0396 code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("body :text %1 \"%2\"").arg(comparison, contentStr); 0397 } else { 0398 QString comparison; 0399 bool negative = false; 0400 switch (mFunction) { 0401 case FuncNone: 0402 break; 0403 case FuncContains: 0404 comparison = QStringLiteral(":contains"); 0405 break; 0406 case FuncContainsNot: 0407 negative = true; 0408 comparison = QStringLiteral(":contains"); 0409 break; 0410 case FuncEquals: 0411 comparison = QStringLiteral(":is"); 0412 break; 0413 case FuncNotEqual: 0414 comparison = QStringLiteral(":is"); 0415 negative = true; 0416 break; 0417 case FuncRegExp: 0418 comparison = QStringLiteral(":regex"); 0419 if (!requireModules.contains(QLatin1StringView("regex"))) { 0420 requireModules << QStringLiteral("regex"); 0421 } 0422 break; 0423 case FuncNotRegExp: 0424 if (!requireModules.contains(QLatin1StringView("regex"))) { 0425 requireModules << QStringLiteral("regex"); 0426 } 0427 comparison = QStringLiteral(":regex"); 0428 negative = true; 0429 break; 0430 case FuncStartWith: 0431 comparison = QStringLiteral(":regex"); 0432 if (!requireModules.contains(QLatin1StringView("regex"))) { 0433 requireModules << QStringLiteral("regex"); 0434 } 0435 contentStr = QLatin1Char('^') + contentStr; 0436 break; 0437 case FuncNotStartWith: 0438 comparison = QStringLiteral(":regex"); 0439 if (!requireModules.contains(QLatin1StringView("regex"))) { 0440 requireModules << QStringLiteral("regex"); 0441 } 0442 comparison = QStringLiteral(":regex"); 0443 contentStr = QLatin1Char('^') + contentStr; 0444 negative = true; 0445 break; 0446 case FuncEndWith: 0447 comparison = QStringLiteral(":regex"); 0448 if (!requireModules.contains(QLatin1StringView("regex"))) { 0449 requireModules << QStringLiteral("regex"); 0450 } 0451 comparison = QStringLiteral(":regex"); 0452 contentStr = contentStr + QLatin1Char('$'); 0453 break; 0454 case FuncNotEndWith: 0455 comparison = QStringLiteral(":regex"); 0456 if (!requireModules.contains(QLatin1StringView("regex"))) { 0457 requireModules << QStringLiteral("regex"); 0458 } 0459 comparison = QStringLiteral(":regex"); 0460 contentStr = contentStr + QLatin1Char('$'); 0461 negative = true; 0462 break; 0463 0464 case FuncIsGreater: 0465 case FuncIsLessOrEqual: 0466 case FuncIsLess: 0467 case FuncIsGreaterOrEqual: 0468 case FuncIsInAddressbook: 0469 case FuncIsNotInAddressbook: 0470 case FuncIsInCategory: 0471 case FuncIsNotInCategory: 0472 case FuncHasAttachment: 0473 case FuncHasNoAttachment: 0474 code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1StringView(mField), conditionToString(mFunction)) 0475 + QLatin1Char('"'); 0476 return; 0477 } 0478 code += (negative ? QStringLiteral("not ") : QString()) 0479 + QStringLiteral("header %1 \"%2\" \"%3\"").arg(comparison).arg(QLatin1StringView(mField)).arg(contentStr); 0480 } 0481 } 0482 0483 void SearchRule::setFunction(Function function) 0484 { 0485 mFunction = function; 0486 } 0487 0488 SearchRule::Function SearchRule::function() const 0489 { 0490 return mFunction; 0491 } 0492 0493 void SearchRule::setField(const QByteArray &field) 0494 { 0495 mField = field; 0496 } 0497 0498 QByteArray SearchRule::field() const 0499 { 0500 return mField; 0501 } 0502 0503 void SearchRule::setContents(const QString &contents) 0504 { 0505 mContents = contents; 0506 } 0507 0508 QString SearchRule::contents() const 0509 { 0510 return mContents; 0511 } 0512 0513 const QString SearchRule::asString() const 0514 { 0515 QString result = QLatin1StringView("\"") + QString::fromLatin1(mField) + QLatin1StringView("\" <"); 0516 result += functionToString(mFunction); 0517 result += QLatin1StringView("> \"") + mContents + QLatin1StringView("\""); 0518 0519 return result; 0520 } 0521 0522 Akonadi::SearchTerm::Condition SearchRule::akonadiComparator() const 0523 { 0524 switch (function()) { 0525 case SearchRule::FuncContains: 0526 case SearchRule::FuncContainsNot: 0527 return Akonadi::SearchTerm::CondContains; 0528 0529 case SearchRule::FuncEquals: 0530 case SearchRule::FuncNotEqual: 0531 return Akonadi::SearchTerm::CondEqual; 0532 0533 case SearchRule::FuncIsGreater: 0534 return Akonadi::SearchTerm::CondGreaterThan; 0535 0536 case SearchRule::FuncIsGreaterOrEqual: 0537 return Akonadi::SearchTerm::CondGreaterOrEqual; 0538 0539 case SearchRule::FuncIsLess: 0540 return Akonadi::SearchTerm::CondLessThan; 0541 0542 case SearchRule::FuncIsLessOrEqual: 0543 return Akonadi::SearchTerm::CondLessOrEqual; 0544 0545 case SearchRule::FuncRegExp: 0546 case SearchRule::FuncNotRegExp: 0547 // TODO is this sufficient? 0548 return Akonadi::SearchTerm::CondContains; 0549 0550 case SearchRule::FuncStartWith: 0551 case SearchRule::FuncNotStartWith: 0552 case SearchRule::FuncEndWith: 0553 case SearchRule::FuncNotEndWith: 0554 // TODO is this sufficient? 0555 return Akonadi::SearchTerm::CondContains; 0556 default: 0557 qCDebug(MAILCOMMON_LOG) << "Unhandled function type: " << function(); 0558 } 0559 0560 return Akonadi::SearchTerm::CondEqual; 0561 } 0562 0563 bool SearchRule::isNegated() const 0564 { 0565 bool negate = false; 0566 switch (function()) { 0567 case SearchRule::FuncContainsNot: 0568 case SearchRule::FuncNotEqual: 0569 case SearchRule::FuncNotRegExp: 0570 case SearchRule::FuncHasNoAttachment: 0571 case SearchRule::FuncIsNotInCategory: 0572 case SearchRule::FuncIsNotInAddressbook: 0573 case SearchRule::FuncNotStartWith: 0574 case SearchRule::FuncNotEndWith: 0575 negate = true; 0576 default: 0577 break; 0578 } 0579 return negate; 0580 } 0581 0582 QDataStream &SearchRule::operator>>(QDataStream &s) const 0583 { 0584 s << mField << functionToString(mFunction) << mContents; 0585 return s; 0586 }