KIMAP

searchjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Andras Mantia <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "searchjob.h"
8 
9 #include <KLocalizedString>
10 #include "kimap_debug.h"
11 
12 #include <QDate>
13 
14 #include "job_p.h"
15 #include "response_p.h"
16 #include "session_p.h"
17 #include "imapset.h"
18 
19 namespace KIMAP
20 {
21 
22 class Term::Private : public QSharedData
23 {
24 public:
25  Private(): QSharedData(), isFuzzy(false), isNegated(false), isNull(false) {}
26  Private(const Private &other)
27  : QSharedData(other)
28  , command(other.command)
29  , isFuzzy(other.isFuzzy)
30  , isNegated(other.isNegated)
31  , isNull(other.isNull)
32  {}
33 
34  Private &operator=(const Private &other)
35  {
36  command = other.command;
37  isFuzzy = other.isFuzzy;
38  isNegated = other.isNegated;
39  isNull = other.isNull;
40  return *this;
41  }
42 
43  QByteArray command;
44  bool isFuzzy;
45  bool isNegated;
46  bool isNull;
47 };
48 
49 Term::Term()
50  : d(new Term::Private)
51 {
52  d->isNull = true;
53 }
54 
55 Term::Term(Term::Relation relation, const QVector<Term> &subterms)
56  : d(new Term::Private)
57 {
58  if (subterms.size() >= 2) {
59  if (relation == KIMAP::Term::Or) {
60  for (int i = 0; i < subterms.size() - 1; ++i) {
61  d->command += "(OR " + subterms[i].serialize() + " ";
62  }
63  d->command += subterms.back().serialize();
64  for (int i = 0; i < subterms.size() - 1; ++i) {
65  d->command += ")";
66  }
67  } else {
68  d->command += "(";
69  for (const Term &t : subterms) {
70  d->command += t.serialize() + ' ';
71  }
72  if (!subterms.isEmpty()) {
73  d->command.chop(1);
74  }
75  d->command += ")";
76  }
77  } else if (subterms.size() == 1) {
78  d->command += subterms.first().serialize();
79  } else {
80  d->isNull = true;
81  }
82 }
83 
84 Term::Term(Term::SearchKey key, const QString &value)
85  : d(new Term::Private)
86 {
87  switch (key) {
88  case All:
89  d->command += "ALL";
90  break;
91  case Bcc:
92  d->command += "BCC";
93  break;
94  case Cc:
95  d->command += "CC";
96  break;
97  case Body:
98  d->command += "BODY";
99  break;
100  case From:
101  d->command += "FROM";
102  break;
103  case Keyword:
104  d->command += "KEYWORD";
105  break;
106  case Subject:
107  d->command += "SUBJECT";
108  break;
109  case Text:
110  d->command += "TEXT";
111  break;
112  case To:
113  d->command += "TO";
114  break;
115  }
116  if (key != All) {
117  d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\"";
118  }
119 }
120 
121 Term::Term(const QString &header, const QString &value)
122  : d(new Term::Private)
123 {
124  d->command += "HEADER";
125  d->command += ' ' + QByteArray(header.toUtf8().constData());
126  d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\"";
127 }
128 
129 Term::Term(Term::BooleanSearchKey key)
130  : d(new Term::Private)
131 {
132  switch (key) {
133  case Answered:
134  d->command = "ANSWERED";
135  break;
136  case Deleted:
137  d->command = "DELETED";
138  break;
139  case Draft:
140  d->command = "DRAFT";
141  break;
142  case Flagged:
143  d->command = "FLAGGED";
144  break;
145  case New:
146  d->command = "NEW";
147  break;
148  case Old:
149  d->command = "OLD";
150  break;
151  case Recent:
152  d->command = "RECENT";
153  break;
154  case Seen:
155  d->command = "SEEN";
156  break;
157  }
158 }
159 
160 static QByteArray monthName(int month)
161 {
162  static const char* names[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
163  return (month >= 1 && month <= 12) ? QByteArray(names[month - 1]) : QByteArray();
164 }
165 
166 Term::Term(Term::DateSearchKey key, const QDate &date)
167  : d(new Term::Private)
168 {
169  switch (key) {
170  case Before:
171  d->command = "BEFORE";
172  break;
173  case On:
174  d->command = "ON";
175  break;
176  case SentBefore:
177  d->command = "SENTBEFORE";
178  break;
179  case SentOn:
180  d->command = "SENTON";
181  break;
182  case SentSince:
183  d->command = "SENTSINCE";
184  break;
185  case Since:
186  d->command = "SINCE";
187  break;
188  }
189  d->command += " \"";
190  d->command += QByteArray::number(date.day()) + '-';
191  d->command += monthName(date.month()) + '-';
192  d->command += QByteArray::number(date.year());
193  d->command += '\"';
194 }
195 
196 Term::Term(Term::NumberSearchKey key, int value)
197  : d(new Term::Private)
198 {
199  switch (key) {
200  case Larger:
201  d->command = "LARGER";
202  break;
203  case Smaller:
204  d->command = "SMALLER";
205  break;
206  }
207  d->command += " " + QByteArray::number(value);
208 }
209 
210 Term::Term(Term::SequenceSearchKey key, const ImapSet &set)
211  : d(new Term::Private)
212 {
213  switch (key) {
214  case Uid:
215  d->command = "UID";
216  break;
217  case SequenceNumber:
218  break;
219  }
220  auto optimizedSet = set;
221  optimizedSet.optimize();
222  d->command += " " + optimizedSet.toImapSequenceSet();
223 }
224 
225 Term::Term(const Term &other)
226  : d(new Term::Private)
227 {
228  *d = *other.d;
229 }
230 
231 Term::~Term()
232 {
233 }
234 
235 Term &Term::operator=(const Term &other)
236 {
237  *d = *other.d;
238  return *this;
239 }
240 
241 bool Term::operator==(const Term &other) const
242 {
243  return d->command == other.d->command &&
244  d->isNegated == other.d->isNegated &&
245  d->isFuzzy == other.d->isFuzzy;
246 }
247 
248 QByteArray Term::serialize() const
249 {
250  QByteArray command;
251  if (d->isFuzzy) {
252  command = "FUZZY ";
253  }
254  if (d->isNegated) {
255  command = "NOT ";
256  }
257  return command + d->command;
258 }
259 
260 Term &Term::setFuzzy(bool fuzzy)
261 {
262  d->isFuzzy = fuzzy;
263  return *this;
264 }
265 
266 Term &Term::setNegated(bool negated)
267 {
268  d->isNegated = negated;
269  return *this;
270 }
271 
272 bool Term::isNull() const
273 {
274  return d->isNull;
275 }
276 
277 //TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response
278 
279 class SearchJobPrivate : public JobPrivate
280 {
281 public:
282  SearchJobPrivate(Session *session, const QString &name) : JobPrivate(session, name), logic(SearchJob::And)
283  {
284  criteriaMap[SearchJob::All] = "ALL";
285  criteriaMap[SearchJob::Answered] = "ANSWERED";
286  criteriaMap[SearchJob::BCC] = "BCC";
287  criteriaMap[SearchJob::Before] = "BEFORE";
288  criteriaMap[SearchJob::Body] = "BODY";
289  criteriaMap[SearchJob::CC] = "CC";
290  criteriaMap[SearchJob::Deleted] = "DELETED";
291  criteriaMap[SearchJob::Draft] = "DRAFT";
292  criteriaMap[SearchJob::Flagged] = "FLAGGED";
293  criteriaMap[SearchJob::From] = "FROM";
294  criteriaMap[SearchJob::Header] = "HEADER";
295  criteriaMap[SearchJob::Keyword] = "KEYWORD";
296  criteriaMap[SearchJob::Larger] = "LARGER";
297  criteriaMap[SearchJob::New] = "NEW";
298  criteriaMap[SearchJob::Old] = "OLD";
299  criteriaMap[SearchJob::On] = "ON";
300  criteriaMap[SearchJob::Recent] = "RECENT";
301  criteriaMap[SearchJob::Seen] = "SEEN";
302  criteriaMap[SearchJob::SentBefore] = "SENTBEFORE";
303  criteriaMap[SearchJob::SentOn] = "SENTON";
304  criteriaMap[SearchJob::SentSince] = "SENTSINCE";
305  criteriaMap[SearchJob::Since] = "SINCE";
306  criteriaMap[SearchJob::Smaller] = "SMALLER";
307  criteriaMap[SearchJob::Subject] = "SUBJECT";
308  criteriaMap[SearchJob::Text] = "TEXT";
309  criteriaMap[SearchJob::To] = "TO";
310  criteriaMap[SearchJob::Uid] = "UID";
311  criteriaMap[SearchJob::Unanswered] = "UNANSWERED";
312  criteriaMap[SearchJob::Undeleted] = "UNDELETED";
313  criteriaMap[SearchJob::Undraft] = "UNDRAFT";
314  criteriaMap[SearchJob::Unflagged] = "UNFLAGGED";
315  criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD";
316  criteriaMap[SearchJob::Unseen] = "UNSEEN";
317 
318  //don't use QDate::shortMonthName(), it returns a localized month name
319  months[1] = "Jan";
320  months[2] = "Feb";
321  months[3] = "Mar";
322  months[4] = "Apr";
323  months[5] = "May";
324  months[6] = "Jun";
325  months[7] = "Jul";
326  months[8] = "Aug";
327  months[9] = "Sep";
328  months[10] = "Oct";
329  months[11] = "Nov";
330  months[12] = "Dec";
331 
332  nextContent = 0;
333  uidBased = false;
334  }
335  ~SearchJobPrivate() { }
336 
337  QByteArray charset;
338  QList<QByteArray> criterias;
340  QMap<int, QByteArray> months;
341  SearchJob::SearchLogic logic;
342  QList<QByteArray> contents;
343  QVector<qint64> results;
344  uint nextContent;
345  bool uidBased;
346  Term term;
347 };
348 }
349 
350 using namespace KIMAP;
351 
352 SearchJob::SearchJob(Session *session)
353  : Job(*new SearchJobPrivate(session, i18nc("Name of the search job", "Search")))
354 {
355 }
356 
357 SearchJob::~SearchJob()
358 {
359 }
360 
361 void SearchJob::setTerm(const Term &term)
362 {
363  Q_D(SearchJob);
364  d->term = term;
365 }
366 
367 void SearchJob::doStart()
368 {
369  Q_D(SearchJob);
370 
371  QByteArray searchKey;
372 
373  if (!d->charset.isEmpty()) {
374  searchKey = "CHARSET " + d->charset;
375  }
376 
377  if (!d->term.isNull()) {
378  const QByteArray term = d->term.serialize();
379  if (term.startsWith('(')) {
380  searchKey += term.mid(1, term.size() - 2);
381  } else {
382  searchKey += term;
383  }
384  } else {
385 
386  if (d->logic == SearchJob::Not) {
387  searchKey += "NOT ";
388  } else if (d->logic == SearchJob::Or && d->criterias.size() > 1) {
389  searchKey += "OR ";
390  }
391 
392  if (d->logic == SearchJob::And) {
393  const int numberCriterias(d->criterias.size());
394  for (int i = 0; i < numberCriterias; i++) {
395  const QByteArray key = d->criterias.at(i);
396  if (i > 0) {
397  searchKey += ' ';
398  }
399  searchKey += key;
400  }
401  } else {
402  const int numberCriterias(d->criterias.size());
403  for (int i = 0; i < numberCriterias; i++) {
404  const QByteArray key = d->criterias.at(i);
405  if (i > 0) {
406  searchKey += ' ';
407  }
408  searchKey += '(' + key + ')';
409  }
410  }
411  }
412 
413  QByteArray command = "SEARCH";
414  if (d->uidBased) {
415  command = "UID " + command;
416  }
417 
418  d->tags << d->sessionInternal()->sendCommand(command, searchKey);
419 }
420 
421 void SearchJob::handleResponse(const Response &response)
422 {
423  Q_D(SearchJob);
424 
425  if (handleErrorReplies(response) == NotHandled) {
426  if (response.content.size() >= 1 && response.content[0].toString() == "+") {
427  if (d->term.isNull()) {
428  d->sessionInternal()->sendData(d->contents[d->nextContent]);
429  } else {
430  qCWarning(KIMAP_LOG) << "The term API only supports inline strings.";
431  }
432  d->nextContent++;
433  } else if (response.content.size() >= 2 && response.content[1].toString() == "SEARCH") {
434  for (int i = 2; i < response.content.size(); i++) {
435  d->results.append(response.content[i].toString().toInt());
436  }
437  }
438  }
439 }
440 
441 void SearchJob::setCharset(const QByteArray &charset)
442 {
443  Q_D(SearchJob);
444  d->charset = charset;
445 }
446 
447 QByteArray SearchJob::charset() const
448 {
449  Q_D(const SearchJob);
450  return d->charset;
451 }
452 
453 void SearchJob::setSearchLogic(SearchLogic logic)
454 {
455  Q_D(SearchJob);
456  d->logic = logic;
457 }
458 
459 void SearchJob::addSearchCriteria(SearchCriteria criteria)
460 {
461  Q_D(SearchJob);
462 
463  switch (criteria) {
464  case All:
465  case Answered:
466  case Deleted:
467  case Draft:
468  case Flagged:
469  case New:
470  case Old:
471  case Recent:
472  case Seen:
473  case Unanswered:
474  case Undeleted:
475  case Undraft:
476  case Unflagged:
477  case Unseen:
478  d->criterias.append(d->criteriaMap[criteria]);
479  break;
480  default:
481  //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
482  qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " needs an argument, but none was specified.";
483  break;
484  }
485 }
486 
487 void SearchJob::addSearchCriteria(SearchCriteria criteria, int argument)
488 {
489  Q_D(SearchJob);
490  switch (criteria) {
491  case Larger:
492  case Smaller:
493  d->criterias.append(d->criteriaMap[criteria] + ' ' + QByteArray::number(argument));
494  break;
495  default:
496  //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
497  qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept an integer as an argument.";
498  break;
499  }
500 }
501 
502 void SearchJob::addSearchCriteria(SearchCriteria criteria, const QByteArray &argument)
503 {
504  Q_D(SearchJob);
505  switch (criteria) {
506  case BCC:
507  case Body:
508  case CC:
509  case From:
510  case Subject:
511  case Text:
512  case To:
513  d->contents.append(argument);
514  d->criterias.append(d->criteriaMap[criteria] + " {" + QByteArray::number(argument.size()) + '}');
515  break;
516  case Keyword:
517  case Unkeyword:
518  case Header:
519  case Uid:
520  d->criterias.append(d->criteriaMap[criteria] + ' ' + argument);
521  break;
522  default:
523  //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
524  qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept any argument.";
525  break;
526  }
527 }
528 
529 void SearchJob::addSearchCriteria(SearchCriteria criteria, const QDate &argument)
530 {
531  Q_D(SearchJob);
532  switch (criteria) {
533  case Before:
534  case On:
535  case SentBefore:
536  case SentSince:
537  case Since: {
538  QByteArray date = QByteArray::number(argument.day()) + '-';
539  date += d->months[argument.month()] + '-';
540  date += QByteArray::number(argument.year());
541  d->criterias.append(d->criteriaMap[criteria] + " \"" + date + '\"');
542  break;
543  }
544  default:
545  //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
546  qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept a date as argument.";
547  break;
548  }
549 }
550 
551 void SearchJob::addSearchCriteria(const QByteArray &searchCriteria)
552 {
553  Q_D(SearchJob);
554  d->criterias.append(searchCriteria);
555 }
556 
557 void SearchJob::setUidBased(bool uidBased)
558 {
559  Q_D(SearchJob);
560  d->uidBased = uidBased;
561 }
562 
563 bool SearchJob::isUidBased() const
564 {
565  Q_D(const SearchJob);
566  return d->uidBased;
567 }
568 
569 QVector<qint64> SearchJob::results() const
570 {
571  Q_D(const SearchJob);
572  return d->results;
573 }
AKONADI_MIME_EXPORT const char Header[]
Subject
char at(int i) const const
AKONADI_MIME_EXPORT const char Deleted[]
AKONADI_MIME_EXPORT const char Answered[]
bool startsWith(const QByteArray &ba) const const
int day() const const
QVector::reference back()
A query term.
Definition: searchjob.h:33
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QByteArray number(int n, int base)
const char * constData() const const
QByteArray mid(int pos, int len) const const
QByteArray & append(char ch)
AKONADI_MIME_EXPORT const char Body[]
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition: imapset.h:128
Definition: acl.cpp:12
int size() const const
int year() const const
int size() const const
int month() const const
AKONADI_MIME_EXPORT const char Seen[]
AKONADI_MIME_EXPORT const char Flagged[]
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sat Oct 24 2020 23:16:51 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.