diff --git a/src/search/searchrule/searchrulestring.cpp b/src/search/searchrule/searchrulestring.cpp index 8011a48..c4e69d1 100644 --- a/src/search/searchrule/searchrulestring.cpp +++ b/src/search/searchrule/searchrulestring.cpp @@ -1,401 +1,403 @@ /* Copyright (c) 2015-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchrulestring.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include #include #include #include #include #include #include #include using namespace MailCommon; SearchRuleString::SearchRuleString(const QByteArray &field, Function func, const QString &contents) : SearchRule(field, func, contents) { } SearchRuleString::SearchRuleString(const SearchRuleString &other) : SearchRule(other) { } const SearchRuleString &SearchRuleString::operator=(const SearchRuleString &other) { if (this == &other) { return *this; } setField(other.field()); setFunction(other.function()); setContents(other.contents()); return *this; } SearchRuleString::~SearchRuleString() { } bool SearchRuleString::isEmpty() const { return field().trimmed().isEmpty() || contents().isEmpty(); } SearchRule::RequiredPart SearchRuleString::requiredPart() const { const QByteArray f = field(); SearchRule::RequiredPart part = Header; if (qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "subject") == 0 || qstricmp(f.constData(), "from") == 0 || qstricmp(f.constData(), "sender") == 0 || qstricmp(f.constData(), "reply-to") == 0 || qstricmp(f.constData(), "to") == 0 || qstricmp(f.constData(), "cc") == 0 || qstricmp(f.constData(), "bcc") == 0 || qstricmp(f.constData(), "in-reply-to") == 0 || qstricmp(f.constData(), "message-id") == 0 || qstricmp(f.constData(), "references") == 0) { // these fields are directly provided by KMime::Message, no need to fetch the whole Header part part = Envelope; } else if (qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0) { part = CompleteMessage; } return part; } bool SearchRuleString::matches(const Akonadi::Item &item) const { if (isEmpty()) { return false; } if (!item.hasPayload()) { return false; } const KMime::Message::Ptr msg = item.payload(); Q_ASSERT(msg.data()); if (!msg->hasHeader("From")) { msg->parse(); // probably not parsed yet: make sure we can access all headers } QString msgContents; // Show the value used to compare the rules against in the log. // Overwrite the value for complete messages and all headers! bool logContents = true; if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->encodedContent()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->body()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->head()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { // (mmutz 2001-11-05) hack to fix " !contains foo" to // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail // handbook if (function() == FuncEquals || function() == FuncNotEqual) { // do we need to treat this case specially? Ie.: What shall // "equality" mean for recipients. return matchesInternal(msg->to()->asUnicodeString()) || matchesInternal(msg->cc()->asUnicodeString()) || matchesInternal(msg->bcc()->asUnicodeString()); } msgContents = msg->to()->asUnicodeString(); msgContents += QStringLiteral(", ") + msg->cc()->asUnicodeString(); msgContents += QStringLiteral(", ") + msg->bcc()->asUnicodeString(); } else if (qstricmp(field().constData(), "") == 0) { //port? // const Nepomuk2::Resource res( item.url() ); // foreach ( const Nepomuk2::Tag &tag, res.tags() ) { // msgContents += tag.label(); // } logContents = false; } else { // make sure to treat messages with multiple header lines for // the same header correctly msgContents = QStringLiteral(""); if (auto hrd = msg->headerByType(field().constData())) { msgContents = hrd->asUnicodeString(); } } if (function() == FuncIsInAddressbook || function() == FuncIsNotInAddressbook) { // I think only the "from"-field makes sense. msgContents = QStringLiteral(""); if (auto hrd = msg->headerByType(field().constData())) { msgContents = hrd->asUnicodeString(); } if (msgContents.isEmpty()) { return (function() == FuncIsInAddressbook) ? false : true; } } // these two functions need the kmmessage therefore they don't call matchesInternal if (function() == FuncHasAttachment) { return KMime::hasAttachment(msg.data()); } else if (function() == FuncHasNoAttachment) { return !KMime::hasAttachment(msg.data()); } bool rc = matchesInternal(msgContents); if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); // only log headers bcause messages and bodies can be pretty large if (logContents) { msg += QStringLiteral(" (") + FilterLog::recode(msgContents) + QStringLiteral(")"); } FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } void SearchRuleString::addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { using namespace Akonadi; emptyIsNotAnError = false; SearchTerm termGroup(SearchTerm::RelOr); if (qstricmp(field().constData(), "subject") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Subject, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "reply-to") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderReplyTo, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Message, contents(), akonadiComparator())); } else if (field() == "") { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Body, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Attachment, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderTo, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderCC, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderBCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Headers, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Subject, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "to") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderTo, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "cc") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "bcc") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderBCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "from") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderFrom, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "list-id") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderListId, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "resent-from") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderResentFrom, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-loop") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXLoop, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-mailing-list") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXMailingList, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-spam-flag") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXSpamFlag, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "organization") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderOrganization, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::MessageTag, contents(), akonadiComparator())); + } else if (!field().isEmpty()){ + termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Headers, contents(), akonadiComparator())); } // TODO complete for other headers, generic headers if (!termGroup.subTerms().isEmpty()) { termGroup.setIsNegated(isNegated()); groupTerm.addSubTerm(termGroup); } } QString SearchRuleString::informationAboutNotValidRules() const { return i18n("String is empty."); } // helper, does the actual comparing bool SearchRuleString::matchesInternal(const QString &msgContents) const { if (msgContents.isEmpty()) { return false; } switch (function()) { case SearchRule::FuncEquals: return QString::compare(msgContents.toLower(), contents().toLower()) == 0; case SearchRule::FuncNotEqual: return QString::compare(msgContents.toLower(), contents().toLower()) != 0; case SearchRule::FuncContains: return msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncContainsNot: return !msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) >= 0; } case SearchRule::FuncNotRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) < 0; } case SearchRule::FuncStartWith: return msgContents.startsWith(contents()); case SearchRule::FuncNotStartWith: return !msgContents.startsWith(contents()); case SearchRule::FuncEndWith: return msgContents.endsWith(contents()); case SearchRule::FuncNotEndWith: return !msgContents.endsWith(contents()); case FuncIsGreater: return QString::compare(msgContents.toLower(), contents().toLower()) > 0; case FuncIsLessOrEqual: return QString::compare(msgContents.toLower(), contents().toLower()) <= 0; case FuncIsLess: return QString::compare(msgContents.toLower(), contents().toLower()) < 0; case FuncIsGreaterOrEqual: return QString::compare(msgContents.toLower(), contents().toLower()) >= 0; case FuncIsInAddressbook: { const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); (it != end); ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); if (!job->contacts().isEmpty()) { return true; } } } return false; } case FuncIsNotInAddressbook: { const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); (it != end); ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); if (job->contacts().isEmpty()) { return true; } } } return false; } case FuncIsInCategory: { QString category = contents(); const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); it != end; ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); const KContacts::Addressee::List contacts = job->contacts(); for (const KContacts::Addressee &contact : contacts) { if (contact.hasCategory(category)) { return true; } } } } return false; } case FuncIsNotInCategory: { QString category = contents(); const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); it != end; ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); const KContacts::Addressee::List contacts = job->contacts(); for (const KContacts::Addressee &contact : contacts) { if (contact.hasCategory(category)) { return false; } } } } return true; } default: ; } return false; }