Mailcommon

searchrule.cpp
1 /*
2  SPDX-FileCopyrightText: 2015-2022 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 #include "searchrule.h"
7 #include "mailcommon_debug.h"
8 #include "searchrule/searchruledate.h"
9 #include "searchrule/searchruleencryption.h"
10 #include "searchrule/searchrulenumerical.h"
11 #include "searchrule/searchrulestatus.h"
12 #include "searchrule/searchrulestring.h"
13 
14 #include <KConfigGroup>
15 
16 #include <QDataStream>
17 
18 #include <algorithm>
19 
20 using namespace MailCommon;
21 
22 static const char *const funcConfigNames[] = {"contains",
23  "contains-not",
24  "equals",
25  "not-equal",
26  "regexp",
27  "not-regexp",
28  "greater",
29  "less-or-equal",
30  "less",
31  "greater-or-equal",
32  "is-in-addressbook",
33  "is-not-in-addressbook",
34  "is-in-category",
35  "is-not-in-category",
36  "has-attachment",
37  "has-no-attachment",
38  "start-with",
39  "not-start-with",
40  "end-with",
41  "not-end-with"};
42 
43 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
44 
45 //==================================================
46 //
47 // class SearchRule (was: KMFilterRule)
48 //
49 //==================================================
50 
51 SearchRule::SearchRule(const QByteArray &field, Function func, const QString &contents)
52  : mField(field)
53  , mFunction(func)
54  , mContents(contents)
55 {
56 }
57 
59 
60  = default;
61 
63 {
64  if (this == &other) {
65  return *this;
66  }
67 
68  mField = other.mField;
69  mFunction = other.mFunction;
70  mContents = other.mContents;
71 
72  return *this;
73 }
74 
76 {
77  SearchRule::Ptr ret;
78  if (field == "<status>") {
80  } else if (field == "<age in days>" || field == "<size>") {
82  } else if (field == "<date>") {
83  ret = SearchRule::Ptr(new SearchRuleDate(field, func, contents));
84  } else if (field == "<encryption>") {
85  ret = SearchRule::Ptr(new SearchRuleEncryption(field, func, contents));
86  } else {
87  ret = SearchRule::Ptr(new SearchRuleString(field, func, contents));
88  }
89 
90  return ret;
91 }
92 
93 SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, const char *func, const QString &contents)
94 {
95  return createInstance(field, configValueToFunc(func), contents);
96 }
97 
99 {
100  return createInstance(other.field(), other.function(), other.contents());
101 }
102 
104 {
105  const char cIdx = char(int('A') + aIdx);
106 
107  static const QString field = QStringLiteral("field");
108  static const QString func = QStringLiteral("func");
109  static const QString contents = QStringLiteral("contents");
110 
111  const QByteArray &field2 = config.readEntry(field + cIdx, QString()).toLatin1();
112  Function func2 = configValueToFunc(config.readEntry(func + cIdx, QString()).toLatin1().constData());
113  const QString &contents2 = config.readEntry(contents + cIdx, QString());
114 
115  if (field2 == "<To or Cc>") { // backwards compat
116  return SearchRule::createInstance("<recipients>", func2, contents2);
117  } else {
118  return SearchRule::createInstance(field2, func2, contents2);
119  }
120 }
121 
123 {
125  s >> field;
126  QString function;
127  s >> function;
128  Function func = configValueToFunc(function.toUtf8().constData());
130  s >> contents;
131  return createInstance(field, func, contents);
132 }
133 
134 SearchRule::~SearchRule() = default;
135 
136 SearchRule::Function SearchRule::configValueToFunc(const char *str)
137 {
138  if (!str) {
139  return FuncNone;
140  }
141 
142  for (int i = 0; i < numFuncConfigNames; ++i) {
143  if (qstricmp(funcConfigNames[i], str) == 0) {
144  return static_cast<Function>(i);
145  }
146  }
147 
148  return FuncNone;
149 }
150 
151 QString SearchRule::functionToString(Function function)
152 {
153  if (function != FuncNone) {
154  return funcConfigNames[int(function)];
155  } else {
156  return QStringLiteral("invalid");
157  }
158 }
159 
160 void SearchRule::writeConfig(KConfigGroup &config, int aIdx) const
161 {
162  const char cIdx = char('A' + aIdx);
163  static const QString field = QStringLiteral("field");
164  static const QString func = QStringLiteral("func");
165  static const QString contents = QStringLiteral("contents");
166 
167  config.writeEntry(field + cIdx, /*QString*/ (mField));
168  config.writeEntry(func + cIdx, functionToString(mFunction));
169  config.writeEntry(contents + cIdx, mContents);
170 }
171 
172 QString SearchRule::conditionToString(Function function)
173 {
174  QString str;
175  switch (function) {
176  case FuncEquals:
177  str = i18n("equal");
178  break;
179  case FuncNotEqual:
180  str = i18n("not equal");
181  break;
182  case FuncIsGreater:
183  str = i18n("is greater");
184  break;
185  case FuncIsLessOrEqual:
186  str = i18n("is less or equal");
187  break;
188  case FuncIsLess:
189  str = i18n("is less");
190  break;
191  case FuncIsGreaterOrEqual:
192  str = i18n("is greater or equal");
193  break;
194  case FuncIsInAddressbook:
195  str = i18n("is in addressbook");
196  break;
197  case FuncIsNotInAddressbook:
198  str = i18n("is not in addressbook");
199  break;
200  case FuncIsInCategory:
201  str = i18n("is in category");
202  break;
203  case FuncIsNotInCategory:
204  str = i18n("is in category");
205  break;
206  case FuncHasAttachment:
207  str = i18n("has an attachment");
208  break;
209  case FuncHasNoAttachment:
210  str = i18n("has not an attachment");
211  break;
212  case FuncStartWith:
213  str = i18n("start with");
214  break;
215  case FuncNotStartWith:
216  str = i18n("not start with");
217  break;
218  case FuncEndWith:
219  str = i18n("end with");
220  break;
221  case FuncNotEndWith:
222  str = i18n("not end with");
223  break;
224  case FuncNone:
225  str = i18n("none");
226  break;
227  case FuncContains:
228  str = i18n("contains");
229  break;
230  case FuncContainsNot:
231  str = i18n("not contains");
232  break;
233  case FuncRegExp:
234  str = i18n("has regexp");
235  break;
236  case FuncNotRegExp:
237  str = i18n("not regexp");
238  break;
239  }
240  return str;
241 }
242 
243 void SearchRule::generateSieveScript(QStringList &requireModules, QString &code)
244 {
245  QString contentStr = mContents;
246  if (mField == "<size>") {
247  QString comparison;
248  int offset = 0;
249  switch (mFunction) {
250  case FuncEquals:
251  comparison = QLatin1Char('"') + i18n("size equals not supported") + QLatin1Char('"');
252  break;
253  case FuncNotEqual:
254  comparison = QLatin1Char('"') + i18n("size not equals not supported") + QLatin1Char('"');
255  break;
256  case FuncIsGreater:
257  comparison = QStringLiteral(":over");
258  break;
259  case FuncIsLessOrEqual:
260  comparison = QStringLiteral(":under");
261  offset = 1;
262  break;
263  case FuncIsLess:
264  comparison = QStringLiteral(":under");
265  break;
266  case FuncIsGreaterOrEqual:
267  comparison = QStringLiteral(":over");
268  offset = -1;
269  break;
270  case FuncIsInAddressbook:
271  case FuncIsNotInAddressbook:
272  case FuncIsInCategory:
273  case FuncIsNotInCategory:
274  case FuncHasAttachment:
275  case FuncHasNoAttachment:
276  case FuncStartWith:
277  case FuncNotStartWith:
278  case FuncEndWith:
279  case FuncNotEndWith:
280  case FuncNone:
281  case FuncContains:
282  case FuncContainsNot:
283  case FuncRegExp:
284  case FuncNotRegExp:
285  code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction))
286  + QLatin1Char('"');
287  return;
288  }
289  code += QStringLiteral("size %1 %2K").arg(comparison).arg(QString::number(mContents.toInt() + offset));
290  } else if (mField == "<status>") {
291  // TODO ?
292  code += QLatin1Char('"') + i18n("<status> not implemented/supported") + QLatin1Char('"');
293  } else if (mField == "<any header>") {
294  // TODO ?
295  code += QLatin1Char('"') + i18n("<any header> not implemented/supported") + QLatin1Char('"');
296  } else if (mField == "contents") {
297  // TODO ?
298  code += QLatin1Char('"') + i18n("<contents> not implemented/supported") + QLatin1Char('"');
299  } else if (mField == "<age in days>") {
300  // TODO ?
301  code += QLatin1Char('"') + i18n("<age in days> not implemented/supported") + QLatin1Char('"');
302  } else if (mField == "<date>") {
303  // TODO ?
304  code += QLatin1Char('"') + i18n("<date> not implemented/supported") + QLatin1Char('"');
305  } else if (mField == "<recipients>") {
306  // TODO ?
307  code += QLatin1Char('"') + i18n("<recipients> not implemented/supported") + QLatin1Char('"');
308  } else if (mField == "<tag>") {
309  code += QLatin1Char('"') + i18n("<Tag> is not supported") + QLatin1Char('"');
310  } else if (mField == "<message>") {
311  // TODO ?
312  code += i18n("<message> not implemented/supported");
313  } else if (mField == "<body>") {
314  if (!requireModules.contains(QLatin1String("body"))) {
315  requireModules << QStringLiteral("body");
316  }
317  QString comparison;
318  bool negative = false;
319  switch (mFunction) {
320  case FuncNone:
321  break;
322  case FuncContains:
323  comparison = QStringLiteral(":contains");
324  break;
325  case FuncContainsNot:
326  negative = true;
327  comparison = QStringLiteral(":contains");
328  break;
329  case FuncEquals:
330  comparison = QStringLiteral(":is");
331  break;
332  case FuncNotEqual:
333  comparison = QStringLiteral(":is");
334  negative = true;
335  break;
336  case FuncRegExp:
337  comparison = QStringLiteral(":regex");
338  if (!requireModules.contains(QLatin1String("regex"))) {
339  requireModules << QStringLiteral("regex");
340  }
341  break;
342  case FuncNotRegExp:
343  if (!requireModules.contains(QLatin1String("regex"))) {
344  requireModules << QStringLiteral("regex");
345  }
346  comparison = QStringLiteral(":regex");
347  negative = true;
348  break;
349  case FuncStartWith:
350  comparison = QStringLiteral(":regex");
351  if (!requireModules.contains(QLatin1String("regex"))) {
352  requireModules << QStringLiteral("regex");
353  }
354  contentStr = QLatin1Char('^') + contentStr;
355  break;
356  case FuncNotStartWith:
357  comparison = QStringLiteral(":regex");
358  if (!requireModules.contains(QLatin1String("regex"))) {
359  requireModules << QStringLiteral("regex");
360  }
361  comparison = QStringLiteral(":regex");
362  contentStr = QLatin1Char('^') + contentStr;
363  negative = true;
364  break;
365  case FuncEndWith:
366  comparison = QStringLiteral(":regex");
367  if (!requireModules.contains(QLatin1String("regex"))) {
368  requireModules << QStringLiteral("regex");
369  }
370  comparison = QStringLiteral(":regex");
371  contentStr = contentStr + QLatin1Char('$');
372  break;
373  case FuncNotEndWith:
374  comparison = QStringLiteral(":regex");
375  if (!requireModules.contains(QLatin1String("regex"))) {
376  requireModules << QStringLiteral("regex");
377  }
378  comparison = QStringLiteral(":regex");
379  contentStr = contentStr + QLatin1Char('$');
380  negative = true;
381  break;
382  case FuncIsGreater:
383  case FuncIsLessOrEqual:
384  case FuncIsLess:
385  case FuncIsGreaterOrEqual:
386  case FuncIsInAddressbook:
387  case FuncIsNotInAddressbook:
388  case FuncIsInCategory:
389  case FuncIsNotInCategory:
390  case FuncHasAttachment:
391  case FuncHasNoAttachment:
392  code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction))
393  + QLatin1Char('"');
394  return;
395  }
396  code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("body :text %1 \"%2\"").arg(comparison, contentStr);
397  } else {
398  QString comparison;
399  bool negative = false;
400  switch (mFunction) {
401  case FuncNone:
402  break;
403  case FuncContains:
404  comparison = QStringLiteral(":contains");
405  break;
406  case FuncContainsNot:
407  negative = true;
408  comparison = QStringLiteral(":contains");
409  break;
410  case FuncEquals:
411  comparison = QStringLiteral(":is");
412  break;
413  case FuncNotEqual:
414  comparison = QStringLiteral(":is");
415  negative = true;
416  break;
417  case FuncRegExp:
418  comparison = QStringLiteral(":regex");
419  if (!requireModules.contains(QLatin1String("regex"))) {
420  requireModules << QStringLiteral("regex");
421  }
422  break;
423  case FuncNotRegExp:
424  if (!requireModules.contains(QLatin1String("regex"))) {
425  requireModules << QStringLiteral("regex");
426  }
427  comparison = QStringLiteral(":regex");
428  negative = true;
429  break;
430  case FuncStartWith:
431  comparison = QStringLiteral(":regex");
432  if (!requireModules.contains(QLatin1String("regex"))) {
433  requireModules << QStringLiteral("regex");
434  }
435  contentStr = QLatin1Char('^') + contentStr;
436  break;
437  case FuncNotStartWith:
438  comparison = QStringLiteral(":regex");
439  if (!requireModules.contains(QLatin1String("regex"))) {
440  requireModules << QStringLiteral("regex");
441  }
442  comparison = QStringLiteral(":regex");
443  contentStr = QLatin1Char('^') + contentStr;
444  negative = true;
445  break;
446  case FuncEndWith:
447  comparison = QStringLiteral(":regex");
448  if (!requireModules.contains(QLatin1String("regex"))) {
449  requireModules << QStringLiteral("regex");
450  }
451  comparison = QStringLiteral(":regex");
452  contentStr = contentStr + QLatin1Char('$');
453  break;
454  case FuncNotEndWith:
455  comparison = QStringLiteral(":regex");
456  if (!requireModules.contains(QLatin1String("regex"))) {
457  requireModules << QStringLiteral("regex");
458  }
459  comparison = QStringLiteral(":regex");
460  contentStr = contentStr + QLatin1Char('$');
461  negative = true;
462  break;
463 
464  case FuncIsGreater:
465  case FuncIsLessOrEqual:
466  case FuncIsLess:
467  case FuncIsGreaterOrEqual:
468  case FuncIsInAddressbook:
469  case FuncIsNotInAddressbook:
470  case FuncIsInCategory:
471  case FuncIsNotInCategory:
472  case FuncHasAttachment:
473  case FuncHasNoAttachment:
474  code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction))
475  + QLatin1Char('"');
476  return;
477  }
478  code += (negative ? QStringLiteral("not ") : QString())
479  + QStringLiteral("header %1 \"%2\" \"%3\"").arg(comparison).arg(QLatin1String(mField)).arg(contentStr);
480  }
481 }
482 
484 {
485  mFunction = function;
486 }
487 
489 {
490  return mFunction;
491 }
492 
494 {
495  mField = field;
496 }
497 
499 {
500  return mField;
501 }
502 
503 void SearchRule::setContents(const QString &contents)
504 {
505  mContents = contents;
506 }
507 
509 {
510  return mContents;
511 }
512 
514 {
515  QString result = QLatin1String("\"") + QString::fromLatin1(mField) + QLatin1String("\" <");
516  result += functionToString(mFunction);
517  result += QLatin1String("> \"") + mContents + QLatin1String("\"");
518 
519  return result;
520 }
521 
522 Akonadi::SearchTerm::Condition SearchRule::akonadiComparator() const
523 {
524  switch (function()) {
525  case SearchRule::FuncContains:
526  case SearchRule::FuncContainsNot:
527  return Akonadi::SearchTerm::CondContains;
528 
529  case SearchRule::FuncEquals:
530  case SearchRule::FuncNotEqual:
531  return Akonadi::SearchTerm::CondEqual;
532 
533  case SearchRule::FuncIsGreater:
534  return Akonadi::SearchTerm::CondGreaterThan;
535 
536  case SearchRule::FuncIsGreaterOrEqual:
537  return Akonadi::SearchTerm::CondGreaterOrEqual;
538 
539  case SearchRule::FuncIsLess:
540  return Akonadi::SearchTerm::CondLessThan;
541 
542  case SearchRule::FuncIsLessOrEqual:
543  return Akonadi::SearchTerm::CondLessOrEqual;
544 
545  case SearchRule::FuncRegExp:
546  case SearchRule::FuncNotRegExp:
547  // TODO is this sufficient?
548  return Akonadi::SearchTerm::CondContains;
549 
550  case SearchRule::FuncStartWith:
551  case SearchRule::FuncNotStartWith:
552  case SearchRule::FuncEndWith:
553  case SearchRule::FuncNotEndWith:
554  // TODO is this sufficient?
555  return Akonadi::SearchTerm::CondContains;
556  default:
557  qCDebug(MAILCOMMON_LOG) << "Unhandled function type: " << function();
558  }
559 
560  return Akonadi::SearchTerm::CondEqual;
561 }
562 
564 {
565  bool negate = false;
566  switch (function()) {
567  case SearchRule::FuncContainsNot:
568  case SearchRule::FuncNotEqual:
569  case SearchRule::FuncNotRegExp:
570  case SearchRule::FuncHasNoAttachment:
571  case SearchRule::FuncIsNotInCategory:
572  case SearchRule::FuncIsNotInAddressbook:
573  case SearchRule::FuncNotStartWith:
574  case SearchRule::FuncNotEndWith:
575  negate = true;
576  default:
577  break;
578  }
579  return negate;
580 }
581 
582 QDataStream &SearchRule::operator>>(QDataStream &s) const
583 {
584  s << mField << functionToString(mFunction) << mContents;
585  return s;
586 }
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition: searchrule.h:29
QString number(int n, int base)
This class represents a search to be performed against the status of a message.
QString contents() const
Returns the contents of the rule.
Definition: searchrule.cpp:508
bool isNegated() const
Helper that returns whether the rule has a negated function.
Definition: searchrule.cpp:563
This class represents a search pattern rule operating on numerical values.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
static SearchRule::Ptr createInstanceFromConfig(const KConfigGroup &group, int index)
Creates a new search rule from a given config group.
Definition: searchrule.cpp:103
Akonadi::SearchTerm::Condition akonadiComparator() const
Converts the rule function into the corresponding Akonadi query operator.
Definition: searchrule.cpp:522
QString i18n(const char *text, const TYPE &arg...)
Function function() const
Returns the filter function of the rule.
Definition: searchrule.cpp:488
static SearchRule::Ptr createInstance(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates a new search rule of a certain type by instantiating the appropriate subclass depending on th...
Definition: searchrule.cpp:75
int toInt(bool *ok, int base) const const
This class represents one search pattern rule.
Definition: searchrule.h:23
Function
Describes operators for comparison of field and contents.
Definition: searchrule.h:40
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
void setField(const QByteArray &name)
Sets the message header field name.
Definition: searchrule.cpp:493
void setFunction(Function function)
Sets the filter function of the rule.
Definition: searchrule.cpp:483
void setContents(const QString &contents)
Set the contents of the rule.
Definition: searchrule.cpp:503
const SearchRule & operator=(const SearchRule &other)
Initializes this rule with an other rule.
Definition: searchrule.cpp:62
void writeConfig(KConfigGroup &group, int index) const
Saves the object into a given config group.
Definition: searchrule.cpp:160
const QString asString() const
Returns the rule as string for debugging purpose.
Definition: searchrule.cpp:513
virtual ~SearchRule()
Destroys the search rule.
SearchRule(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates new new search rule.
Definition: searchrule.cpp:51
QByteArray field() const
Returns the message header field name (without the trailing ':').
Definition: searchrule.cpp:498
The filter dialog.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Oct 1 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.