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 }