File indexing completed on 2024-05-12 05:17:16
0001 /* 0002 SPDX-FileCopyrightText: 2009 Andras Mantia <amantia@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "searchjob.h" 0008 0009 #include "kimap_debug.h" 0010 #include <KLocalizedString> 0011 0012 #include <QDate> 0013 0014 #include "imapset.h" 0015 #include "job_p.h" 0016 #include "response_p.h" 0017 #include "session_p.h" 0018 0019 namespace KIMAP 0020 { 0021 class Term::Private : public QSharedData 0022 { 0023 public: 0024 Private() 0025 : QSharedData() 0026 , isFuzzy(false) 0027 , isNegated(false) 0028 , isNull(false) 0029 { 0030 } 0031 Private(const Private &other) 0032 : QSharedData(other) 0033 , command(other.command) 0034 , isFuzzy(other.isFuzzy) 0035 , isNegated(other.isNegated) 0036 , isNull(other.isNull) 0037 { 0038 } 0039 0040 Private &operator=(const Private &other) 0041 { 0042 command = other.command; 0043 isFuzzy = other.isFuzzy; 0044 isNegated = other.isNegated; 0045 isNull = other.isNull; 0046 return *this; 0047 } 0048 0049 QByteArray command; 0050 bool isFuzzy; 0051 bool isNegated; 0052 bool isNull; 0053 }; 0054 0055 Term::Term() 0056 : d(new Term::Private) 0057 { 0058 d->isNull = true; 0059 } 0060 0061 Term::Term(Term::Relation relation, const QList<Term> &subterms) 0062 : d(new Term::Private) 0063 { 0064 if (subterms.size() >= 2) { 0065 if (relation == KIMAP::Term::Or) { 0066 for (int i = 0; i < subterms.size() - 1; ++i) { 0067 d->command += "(OR " + subterms[i].serialize() + " "; 0068 } 0069 d->command += subterms.back().serialize(); 0070 for (int i = 0; i < subterms.size() - 1; ++i) { 0071 d->command += ")"; 0072 } 0073 } else { 0074 d->command += "("; 0075 for (const Term &t : subterms) { 0076 d->command += t.serialize() + ' '; 0077 } 0078 if (!subterms.isEmpty()) { 0079 d->command.chop(1); 0080 } 0081 d->command += ")"; 0082 } 0083 } else if (subterms.size() == 1) { 0084 d->command += subterms.first().serialize(); 0085 } else { 0086 d->isNull = true; 0087 } 0088 } 0089 0090 Term::Term(Term::SearchKey key, const QString &value) 0091 : d(new Term::Private) 0092 { 0093 switch (key) { 0094 case All: 0095 d->command += "ALL"; 0096 break; 0097 case Bcc: 0098 d->command += "BCC"; 0099 break; 0100 case Cc: 0101 d->command += "CC"; 0102 break; 0103 case Body: 0104 d->command += "BODY"; 0105 break; 0106 case From: 0107 d->command += "FROM"; 0108 break; 0109 case Keyword: 0110 d->command += "KEYWORD"; 0111 break; 0112 case Subject: 0113 d->command += "SUBJECT"; 0114 break; 0115 case Text: 0116 d->command += "TEXT"; 0117 break; 0118 case To: 0119 d->command += "TO"; 0120 break; 0121 } 0122 if (key != All) { 0123 d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\""; 0124 } 0125 } 0126 0127 Term::Term(const QString &header, const QString &value) 0128 : d(new Term::Private) 0129 { 0130 d->command += "HEADER"; 0131 d->command += ' ' + QByteArray(header.toUtf8().constData()); 0132 d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\""; 0133 } 0134 0135 Term::Term(Term::BooleanSearchKey key) 0136 : d(new Term::Private) 0137 { 0138 switch (key) { 0139 case Answered: 0140 d->command = "ANSWERED"; 0141 break; 0142 case Deleted: 0143 d->command = "DELETED"; 0144 break; 0145 case Draft: 0146 d->command = "DRAFT"; 0147 break; 0148 case Flagged: 0149 d->command = "FLAGGED"; 0150 break; 0151 case New: 0152 d->command = "NEW"; 0153 break; 0154 case Old: 0155 d->command = "OLD"; 0156 break; 0157 case Recent: 0158 d->command = "RECENT"; 0159 break; 0160 case Seen: 0161 d->command = "SEEN"; 0162 break; 0163 } 0164 } 0165 0166 static QByteArray monthName(int month) 0167 { 0168 static const char *names[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 0169 return (month >= 1 && month <= 12) ? QByteArray(names[month - 1]) : QByteArray(); 0170 } 0171 0172 Term::Term(Term::DateSearchKey key, const QDate &date) 0173 : d(new Term::Private) 0174 { 0175 switch (key) { 0176 case Before: 0177 d->command = "BEFORE"; 0178 break; 0179 case On: 0180 d->command = "ON"; 0181 break; 0182 case SentBefore: 0183 d->command = "SENTBEFORE"; 0184 break; 0185 case SentOn: 0186 d->command = "SENTON"; 0187 break; 0188 case SentSince: 0189 d->command = "SENTSINCE"; 0190 break; 0191 case Since: 0192 d->command = "SINCE"; 0193 break; 0194 } 0195 d->command += " \""; 0196 d->command += QByteArray::number(date.day()) + '-'; 0197 d->command += monthName(date.month()) + '-'; 0198 d->command += QByteArray::number(date.year()); 0199 d->command += '\"'; 0200 } 0201 0202 Term::Term(Term::NumberSearchKey key, int value) 0203 : d(new Term::Private) 0204 { 0205 switch (key) { 0206 case Larger: 0207 d->command = "LARGER"; 0208 break; 0209 case Smaller: 0210 d->command = "SMALLER"; 0211 break; 0212 } 0213 d->command += " " + QByteArray::number(value); 0214 } 0215 0216 Term::Term(Term::SequenceSearchKey key, const ImapSet &set) 0217 : d(new Term::Private) 0218 { 0219 switch (key) { 0220 case Uid: 0221 d->command = "UID"; 0222 break; 0223 case SequenceNumber: 0224 break; 0225 } 0226 auto optimizedSet = set; 0227 optimizedSet.optimize(); 0228 d->command += " " + optimizedSet.toImapSequenceSet(); 0229 } 0230 0231 Term::Term(const Term &other) 0232 : d(new Term::Private) 0233 { 0234 *d = *other.d; 0235 } 0236 0237 Term::~Term() 0238 { 0239 } 0240 0241 Term &Term::operator=(const Term &other) 0242 { 0243 *d = *other.d; 0244 return *this; 0245 } 0246 0247 bool Term::operator==(const Term &other) const 0248 { 0249 return d->command == other.d->command && d->isNegated == other.d->isNegated && d->isFuzzy == other.d->isFuzzy; 0250 } 0251 0252 QByteArray Term::serialize() const 0253 { 0254 QByteArray command; 0255 if (d->isFuzzy) { 0256 command = "FUZZY "; 0257 } 0258 if (d->isNegated) { 0259 command = "NOT "; 0260 } 0261 return command + d->command; 0262 } 0263 0264 Term &Term::setFuzzy(bool fuzzy) 0265 { 0266 d->isFuzzy = fuzzy; 0267 return *this; 0268 } 0269 0270 Term &Term::setNegated(bool negated) 0271 { 0272 d->isNegated = negated; 0273 return *this; 0274 } 0275 0276 bool Term::isNull() const 0277 { 0278 return d->isNull; 0279 } 0280 0281 // TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response 0282 0283 class SearchJobPrivate : public JobPrivate 0284 { 0285 public: 0286 SearchJobPrivate(Session *session, const QString &name) 0287 : JobPrivate(session, name) 0288 , logic(SearchJob::And) 0289 { 0290 criteriaMap[SearchJob::All] = "ALL"; 0291 criteriaMap[SearchJob::Answered] = "ANSWERED"; 0292 criteriaMap[SearchJob::BCC] = "BCC"; 0293 criteriaMap[SearchJob::Before] = "BEFORE"; 0294 criteriaMap[SearchJob::Body] = "BODY"; 0295 criteriaMap[SearchJob::CC] = "CC"; 0296 criteriaMap[SearchJob::Deleted] = "DELETED"; 0297 criteriaMap[SearchJob::Draft] = "DRAFT"; 0298 criteriaMap[SearchJob::Flagged] = "FLAGGED"; 0299 criteriaMap[SearchJob::From] = "FROM"; 0300 criteriaMap[SearchJob::Header] = "HEADER"; 0301 criteriaMap[SearchJob::Keyword] = "KEYWORD"; 0302 criteriaMap[SearchJob::Larger] = "LARGER"; 0303 criteriaMap[SearchJob::New] = "NEW"; 0304 criteriaMap[SearchJob::Old] = "OLD"; 0305 criteriaMap[SearchJob::On] = "ON"; 0306 criteriaMap[SearchJob::Recent] = "RECENT"; 0307 criteriaMap[SearchJob::Seen] = "SEEN"; 0308 criteriaMap[SearchJob::SentBefore] = "SENTBEFORE"; 0309 criteriaMap[SearchJob::SentOn] = "SENTON"; 0310 criteriaMap[SearchJob::SentSince] = "SENTSINCE"; 0311 criteriaMap[SearchJob::Since] = "SINCE"; 0312 criteriaMap[SearchJob::Smaller] = "SMALLER"; 0313 criteriaMap[SearchJob::Subject] = "SUBJECT"; 0314 criteriaMap[SearchJob::Text] = "TEXT"; 0315 criteriaMap[SearchJob::To] = "TO"; 0316 criteriaMap[SearchJob::Uid] = "UID"; 0317 criteriaMap[SearchJob::Unanswered] = "UNANSWERED"; 0318 criteriaMap[SearchJob::Undeleted] = "UNDELETED"; 0319 criteriaMap[SearchJob::Undraft] = "UNDRAFT"; 0320 criteriaMap[SearchJob::Unflagged] = "UNFLAGGED"; 0321 criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD"; 0322 criteriaMap[SearchJob::Unseen] = "UNSEEN"; 0323 0324 // don't use QDate::shortMonthName(), it returns a localized month name 0325 months[1] = "Jan"; 0326 months[2] = "Feb"; 0327 months[3] = "Mar"; 0328 months[4] = "Apr"; 0329 months[5] = "May"; 0330 months[6] = "Jun"; 0331 months[7] = "Jul"; 0332 months[8] = "Aug"; 0333 months[9] = "Sep"; 0334 months[10] = "Oct"; 0335 months[11] = "Nov"; 0336 months[12] = "Dec"; 0337 0338 nextContent = 0; 0339 uidBased = false; 0340 } 0341 ~SearchJobPrivate() 0342 { 0343 } 0344 0345 QByteArray charset; 0346 QList<QByteArray> criterias; 0347 QMap<SearchJob::SearchCriteria, QByteArray> criteriaMap; 0348 QMap<int, QByteArray> months; 0349 SearchJob::SearchLogic logic; 0350 QList<QByteArray> contents; 0351 QList<qint64> results; 0352 uint nextContent; 0353 bool uidBased; 0354 Term term; 0355 }; 0356 } 0357 0358 using namespace KIMAP; 0359 0360 SearchJob::SearchJob(Session *session) 0361 : Job(*new SearchJobPrivate(session, i18nc("Name of the search job", "Search"))) 0362 { 0363 } 0364 0365 SearchJob::~SearchJob() 0366 { 0367 } 0368 0369 void SearchJob::setTerm(const Term &term) 0370 { 0371 Q_D(SearchJob); 0372 d->term = term; 0373 } 0374 0375 void SearchJob::doStart() 0376 { 0377 Q_D(SearchJob); 0378 0379 QByteArray searchKey; 0380 0381 if (!d->charset.isEmpty()) { 0382 searchKey = "CHARSET " + d->charset; 0383 } 0384 0385 if (!d->term.isNull()) { 0386 const QByteArray term = d->term.serialize(); 0387 if (term.startsWith('(')) { 0388 searchKey += term.mid(1, term.size() - 2); 0389 } else { 0390 searchKey += term; 0391 } 0392 } else { 0393 if (d->logic == SearchJob::Not) { 0394 searchKey += "NOT "; 0395 } else if (d->logic == SearchJob::Or && d->criterias.size() > 1) { 0396 searchKey += "OR "; 0397 } 0398 0399 if (d->logic == SearchJob::And) { 0400 const int numberCriterias(d->criterias.size()); 0401 for (int i = 0; i < numberCriterias; i++) { 0402 const QByteArray key = d->criterias.at(i); 0403 if (i > 0) { 0404 searchKey += ' '; 0405 } 0406 searchKey += key; 0407 } 0408 } else { 0409 const int numberCriterias(d->criterias.size()); 0410 for (int i = 0; i < numberCriterias; i++) { 0411 const QByteArray key = d->criterias.at(i); 0412 if (i > 0) { 0413 searchKey += ' '; 0414 } 0415 searchKey += '(' + key + ')'; 0416 } 0417 } 0418 } 0419 0420 QByteArray command = "SEARCH"; 0421 if (d->uidBased) { 0422 command = "UID " + command; 0423 } 0424 0425 d->tags << d->sessionInternal()->sendCommand(command, searchKey); 0426 } 0427 0428 void SearchJob::handleResponse(const Response &response) 0429 { 0430 Q_D(SearchJob); 0431 0432 if (handleErrorReplies(response) == NotHandled) { 0433 if (response.content.size() >= 1 && response.content[0].toString() == "+") { 0434 if (d->term.isNull()) { 0435 d->sessionInternal()->sendData(d->contents[d->nextContent]); 0436 } else { 0437 qCWarning(KIMAP_LOG) << "The term API only supports inline strings."; 0438 } 0439 d->nextContent++; 0440 } else if (response.content.size() >= 2 && response.content[1].toString() == "SEARCH") { 0441 for (int i = 2; i < response.content.size(); i++) { 0442 d->results.append(response.content[i].toString().toInt()); 0443 } 0444 } 0445 } 0446 } 0447 0448 void SearchJob::setCharset(const QByteArray &charset) 0449 { 0450 Q_D(SearchJob); 0451 d->charset = charset; 0452 } 0453 0454 QByteArray SearchJob::charset() const 0455 { 0456 Q_D(const SearchJob); 0457 return d->charset; 0458 } 0459 0460 void SearchJob::setSearchLogic(SearchLogic logic) 0461 { 0462 Q_D(SearchJob); 0463 d->logic = logic; 0464 } 0465 0466 void SearchJob::addSearchCriteria(SearchCriteria criteria) 0467 { 0468 Q_D(SearchJob); 0469 0470 switch (criteria) { 0471 case All: 0472 case Answered: 0473 case Deleted: 0474 case Draft: 0475 case Flagged: 0476 case New: 0477 case Old: 0478 case Recent: 0479 case Seen: 0480 case Unanswered: 0481 case Undeleted: 0482 case Undraft: 0483 case Unflagged: 0484 case Unseen: 0485 d->criterias.append(d->criteriaMap[criteria]); 0486 break; 0487 default: 0488 // TODO Discuss if we keep error checking here, or accept anything, even if it is wrong 0489 qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " needs an argument, but none was specified."; 0490 break; 0491 } 0492 } 0493 0494 void SearchJob::addSearchCriteria(SearchCriteria criteria, int argument) 0495 { 0496 Q_D(SearchJob); 0497 switch (criteria) { 0498 case Larger: 0499 case Smaller: 0500 d->criterias.append(d->criteriaMap[criteria] + ' ' + QByteArray::number(argument)); 0501 break; 0502 default: 0503 // TODO Discuss if we keep error checking here, or accept anything, even if it is wrong 0504 qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept an integer as an argument."; 0505 break; 0506 } 0507 } 0508 0509 void SearchJob::addSearchCriteria(SearchCriteria criteria, const QByteArray &argument) 0510 { 0511 Q_D(SearchJob); 0512 switch (criteria) { 0513 case BCC: 0514 case Body: 0515 case CC: 0516 case From: 0517 case Subject: 0518 case Text: 0519 case To: 0520 d->contents.append(argument); 0521 d->criterias.append(d->criteriaMap[criteria] + " {" + QByteArray::number(argument.size()) + '}'); 0522 break; 0523 case Keyword: 0524 case Unkeyword: 0525 case Header: 0526 case Uid: 0527 d->criterias.append(d->criteriaMap[criteria] + ' ' + argument); 0528 break; 0529 default: 0530 // TODO Discuss if we keep error checking here, or accept anything, even if it is wrong 0531 qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept any argument."; 0532 break; 0533 } 0534 } 0535 0536 void SearchJob::addSearchCriteria(SearchCriteria criteria, const QDate &argument) 0537 { 0538 Q_D(SearchJob); 0539 switch (criteria) { 0540 case Before: 0541 case On: 0542 case SentBefore: 0543 case SentSince: 0544 case Since: { 0545 QByteArray date = QByteArray::number(argument.day()) + '-'; 0546 date += d->months[argument.month()] + '-'; 0547 date += QByteArray::number(argument.year()); 0548 d->criterias.append(d->criteriaMap[criteria] + " \"" + date + '\"'); 0549 break; 0550 } 0551 default: 0552 // TODO Discuss if we keep error checking here, or accept anything, even if it is wrong 0553 qCDebug(KIMAP_LOG) << "Criteria " << d->criteriaMap[criteria] << " doesn't accept a date as argument."; 0554 break; 0555 } 0556 } 0557 0558 void SearchJob::addSearchCriteria(const QByteArray &searchCriteria) 0559 { 0560 Q_D(SearchJob); 0561 d->criterias.append(searchCriteria); 0562 } 0563 0564 void SearchJob::setUidBased(bool uidBased) 0565 { 0566 Q_D(SearchJob); 0567 d->uidBased = uidBased; 0568 } 0569 0570 bool SearchJob::isUidBased() const 0571 { 0572 Q_D(const SearchJob); 0573 return d->uidBased; 0574 } 0575 0576 QList<qint64> SearchJob::results() const 0577 { 0578 Q_D(const SearchJob); 0579 return d->results; 0580 } 0581 0582 #include "moc_searchjob.cpp"