File indexing completed on 2024-05-12 05:17:24
0001 /* 0002 Copyright (c) 2009 Andras Mantia <amantia@kde.org> 0003 0004 This library is free software; you can redistribute it and/or modify it 0005 under the terms of the GNU Library General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or (at your 0007 option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, but WITHOUT 0010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 0012 License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to the 0016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301, USA. 0018 */ 0019 0020 #include "searchjob.h" 0021 0022 #include "kimap_debug.h" 0023 0024 #include <QtCore/QDate> 0025 0026 #include "job_p.h" 0027 #include "message_p.h" 0028 #include "session_p.h" 0029 #include "imapset.h" 0030 0031 namespace KIMAP2 0032 { 0033 0034 class Term::Private 0035 { 0036 public: 0037 Private(): isFuzzy(false), isNegated(false), isNull(false) {} 0038 QByteArray command; 0039 bool isFuzzy; 0040 bool isNegated; 0041 bool isNull; 0042 }; 0043 0044 Term::Term() 0045 : d(new Term::Private) 0046 { 0047 d->isNull = true; 0048 } 0049 0050 Term::Term(Term::Relation relation, const QVector<Term> &subterms) 0051 : d(new Term::Private) 0052 { 0053 if (subterms.size() >= 2) { 0054 if (relation == KIMAP2::Term::Or) { 0055 for (int i = 0; i < subterms.size() - 1; ++i) { 0056 d->command += "(OR " + subterms[i].serialize() + " "; 0057 } 0058 d->command += subterms.back().serialize(); 0059 for (int i = 0; i < subterms.size() - 1; ++i) { 0060 d->command += ")"; 0061 } 0062 } else { 0063 d->command += "("; 0064 for (const Term &t : subterms) { 0065 d->command += t.serialize() + ' '; 0066 } 0067 if (!subterms.isEmpty()) { 0068 d->command.chop(1); 0069 } 0070 d->command += ")"; 0071 } 0072 } else if (subterms.size() == 1) { 0073 d->command += subterms.first().serialize(); 0074 } else { 0075 d->isNull = true; 0076 } 0077 } 0078 0079 Term::Term(Term::SearchKey key, const QString &value) 0080 : d(new Term::Private) 0081 { 0082 switch (key) { 0083 case All: 0084 d->command += "ALL"; 0085 break; 0086 case Bcc: 0087 d->command += "BCC"; 0088 break; 0089 case Cc: 0090 d->command += "CC"; 0091 break; 0092 case Body: 0093 d->command += "BODY"; 0094 break; 0095 case From: 0096 d->command += "FROM"; 0097 break; 0098 case Keyword: 0099 d->command += "KEYWORD"; 0100 break; 0101 case Subject: 0102 d->command += "SUBJECT"; 0103 break; 0104 case Text: 0105 d->command += "TEXT"; 0106 break; 0107 case To: 0108 d->command += "TO"; 0109 break; 0110 } 0111 if (key != All) { 0112 d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\""; 0113 } 0114 } 0115 0116 Term::Term(const QString &header, const QString &value) 0117 : d(new Term::Private) 0118 { 0119 d->command += "HEADER"; 0120 d->command += ' ' + QByteArray(header.toUtf8().constData()); 0121 d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\""; 0122 } 0123 0124 Term::Term(Term::BooleanSearchKey key) 0125 : d(new Term::Private) 0126 { 0127 switch (key) { 0128 case Answered: 0129 d->command = "ANSWERED"; 0130 break; 0131 case Deleted: 0132 d->command = "DELETED"; 0133 break; 0134 case Draft: 0135 d->command = "DRAFT"; 0136 break; 0137 case Flagged: 0138 d->command = "FLAGGED"; 0139 break; 0140 case New: 0141 d->command = "NEW"; 0142 break; 0143 case Old: 0144 d->command = "OLD"; 0145 break; 0146 case Recent: 0147 d->command = "RECENT"; 0148 break; 0149 case Seen: 0150 d->command = "SEEN"; 0151 break; 0152 } 0153 } 0154 0155 static QByteArray monthName(int month) 0156 { 0157 static const char* names[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 0158 return (month >= 1 && month <= 12) ? QByteArray(names[month - 1]) : QByteArray(); 0159 } 0160 0161 Term::Term(Term::DateSearchKey key, const QDate &date) 0162 : d(new Term::Private) 0163 { 0164 switch (key) { 0165 case Before: 0166 d->command = "BEFORE"; 0167 break; 0168 case On: 0169 d->command = "ON"; 0170 break; 0171 case SentBefore: 0172 d->command = "SENTBEFORE"; 0173 break; 0174 case SentOn: 0175 d->command = "SENTON"; 0176 break; 0177 case SentSince: 0178 d->command = "SENTSINCE"; 0179 break; 0180 case Since: 0181 d->command = "SINCE"; 0182 break; 0183 } 0184 d->command += " \""; 0185 d->command += QByteArray::number(date.day()) + '-'; 0186 d->command += monthName(date.month()) + '-'; 0187 d->command += QByteArray::number(date.year()); 0188 d->command += '\"'; 0189 } 0190 0191 Term::Term(Term::NumberSearchKey key, int value) 0192 : d(new Term::Private) 0193 { 0194 switch (key) { 0195 case Larger: 0196 d->command = "LARGER"; 0197 break; 0198 case Smaller: 0199 d->command = "SMALLER"; 0200 break; 0201 } 0202 d->command += " " + QByteArray::number(value); 0203 } 0204 0205 Term::Term(Term::SequenceSearchKey key, const ImapSet &set) 0206 : d(new Term::Private) 0207 { 0208 switch (key) { 0209 case Uid: 0210 d->command = "UID"; 0211 break; 0212 case SequenceNumber: 0213 break; 0214 } 0215 auto optimizedSet = set; 0216 optimizedSet.optimize(); 0217 d->command += " " + optimizedSet.toImapSequenceSet(); 0218 } 0219 0220 Term::Term(const Term &other) 0221 : d(new Term::Private) 0222 { 0223 *d = *other.d; 0224 } 0225 0226 Term &Term::operator=(const Term &other) 0227 { 0228 *d = *other.d; 0229 return *this; 0230 } 0231 0232 bool Term::operator==(const Term &other) const 0233 { 0234 return d->command == other.d->command && 0235 d->isNegated == other.d->isNegated && 0236 d->isFuzzy == other.d->isFuzzy; 0237 } 0238 0239 QByteArray Term::serialize() const 0240 { 0241 QByteArray command; 0242 if (d->isFuzzy) { 0243 command = "FUZZY "; 0244 } 0245 if (d->isNegated) { 0246 command = "NOT "; 0247 } 0248 return command + d->command; 0249 } 0250 0251 Term &Term::setFuzzy(bool fuzzy) 0252 { 0253 d->isFuzzy = fuzzy; 0254 return *this; 0255 } 0256 0257 Term &Term::setNegated(bool negated) 0258 { 0259 d->isNegated = negated; 0260 return *this; 0261 } 0262 0263 bool Term::isNull() const 0264 { 0265 return d->isNull; 0266 } 0267 0268 //TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response 0269 0270 class SearchJobPrivate : public JobPrivate 0271 { 0272 public: 0273 SearchJobPrivate(Session *session, const QString &name) : JobPrivate(session, name), logic(SearchJob::And) 0274 { 0275 criteriaMap[SearchJob::All] = "ALL"; 0276 criteriaMap[SearchJob::Answered] = "ANSWERED"; 0277 criteriaMap[SearchJob::BCC] = "BCC"; 0278 criteriaMap[SearchJob::Before] = "BEFORE"; 0279 criteriaMap[SearchJob::Body] = "BODY"; 0280 criteriaMap[SearchJob::CC] = "CC"; 0281 criteriaMap[SearchJob::Deleted] = "DELETED"; 0282 criteriaMap[SearchJob::Draft] = "DRAFT"; 0283 criteriaMap[SearchJob::Flagged] = "FLAGGED"; 0284 criteriaMap[SearchJob::From] = "FROM"; 0285 criteriaMap[SearchJob::Header] = "HEADER"; 0286 criteriaMap[SearchJob::Keyword] = "KEYWORD"; 0287 criteriaMap[SearchJob::Larger] = "LARGER"; 0288 criteriaMap[SearchJob::New] = "NEW"; 0289 criteriaMap[SearchJob::Old] = "OLD"; 0290 criteriaMap[SearchJob::On] = "ON"; 0291 criteriaMap[SearchJob::Recent] = "RECENT"; 0292 criteriaMap[SearchJob::Seen] = "SEEN"; 0293 criteriaMap[SearchJob::SentBefore] = "SENTBEFORE"; 0294 criteriaMap[SearchJob::SentOn] = "SENTON"; 0295 criteriaMap[SearchJob::SentSince] = "SENTSINCE"; 0296 criteriaMap[SearchJob::Since] = "SINCE"; 0297 criteriaMap[SearchJob::Smaller] = "SMALLER"; 0298 criteriaMap[SearchJob::Subject] = "SUBJECT"; 0299 criteriaMap[SearchJob::Text] = "TEXT"; 0300 criteriaMap[SearchJob::To] = "TO"; 0301 criteriaMap[SearchJob::Uid] = "UID"; 0302 criteriaMap[SearchJob::Unanswered] = "UNANSWERED"; 0303 criteriaMap[SearchJob::Undeleted] = "UNDELETED"; 0304 criteriaMap[SearchJob::Undraft] = "UNDRAFT"; 0305 criteriaMap[SearchJob::Unflagged] = "UNFLAGGED"; 0306 criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD"; 0307 criteriaMap[SearchJob::Unseen] = "UNSEEN"; 0308 0309 //don't use QDate::shortMonthName(), it returns a localized month name 0310 months[1] = "Jan"; 0311 months[2] = "Feb"; 0312 months[3] = "Mar"; 0313 months[4] = "Apr"; 0314 months[5] = "May"; 0315 months[6] = "Jun"; 0316 months[7] = "Jul"; 0317 months[8] = "Aug"; 0318 months[9] = "Sep"; 0319 months[10] = "Oct"; 0320 months[11] = "Nov"; 0321 months[12] = "Dec"; 0322 0323 nextContent = 0; 0324 uidBased = false; 0325 } 0326 ~SearchJobPrivate() { } 0327 0328 QByteArray charset; 0329 QList<QByteArray> criterias; 0330 QMap<SearchJob::SearchCriteria, QByteArray > criteriaMap; 0331 QMap<int, QByteArray> months; 0332 SearchJob::SearchLogic logic; 0333 QList<QByteArray> contents; 0334 QVector<qint64> results; 0335 uint nextContent; 0336 bool uidBased; 0337 Term term; 0338 }; 0339 } 0340 0341 using namespace KIMAP2; 0342 0343 SearchJob::SearchJob(Session *session) 0344 : Job(*new SearchJobPrivate(session, "Search")) 0345 { 0346 } 0347 0348 SearchJob::~SearchJob() 0349 { 0350 } 0351 0352 void SearchJob::setTerm(const Term &term) 0353 { 0354 Q_D(SearchJob); 0355 d->term = term; 0356 } 0357 0358 void SearchJob::doStart() 0359 { 0360 Q_D(SearchJob); 0361 0362 QByteArray searchKey; 0363 0364 if (!d->charset.isEmpty()) { 0365 searchKey = "CHARSET " + d->charset; 0366 } 0367 0368 if (!d->term.isNull()) { 0369 const QByteArray term = d->term.serialize(); 0370 if (term.startsWith('(')) { 0371 searchKey += term.mid(1, term.size() - 2); 0372 } else { 0373 searchKey += term; 0374 } 0375 } else { 0376 0377 if (d->logic == SearchJob::Not) { 0378 searchKey += "NOT "; 0379 } else if (d->logic == SearchJob::Or && d->criterias.size() > 1) { 0380 searchKey += "OR "; 0381 } 0382 0383 if (d->logic == SearchJob::And) { 0384 for (int i = 0; i < d->criterias.size(); i++) { 0385 const QByteArray key = d->criterias.at(i); 0386 if (i > 0) { 0387 searchKey += ' '; 0388 } 0389 searchKey += key; 0390 } 0391 } else { 0392 for (int i = 0; i < d->criterias.size(); i++) { 0393 const QByteArray key = d->criterias.at(i); 0394 if (i > 0) { 0395 searchKey += ' '; 0396 } 0397 searchKey += '(' + key + ')'; 0398 } 0399 } 0400 } 0401 0402 QByteArray command = "SEARCH"; 0403 if (d->uidBased) { 0404 command = "UID " + command; 0405 } 0406 0407 d->sendCommand(command, searchKey); 0408 } 0409 0410 void SearchJob::handleResponse(const Message &response) 0411 { 0412 Q_D(SearchJob); 0413 0414 if (handleErrorReplies(response) == NotHandled) { 0415 if (response.content.size() >= 1 && response.content[0].toString() == "+") { 0416 if (d->term.isNull()) { 0417 d->sessionInternal()->sendData(d->contents[d->nextContent]); 0418 } else { 0419 qCWarning(KIMAP2_LOG) << "The term API only supports inline strings."; 0420 } 0421 d->nextContent++; 0422 } else if (response.content.size() >= 2 && response.content[1].toString() == "SEARCH") { 0423 for (int i = 2; i < response.content.size(); i++) { 0424 d->results.append(response.content[i].toString().toInt()); 0425 } 0426 } 0427 } 0428 } 0429 0430 void SearchJob::setCharset(const QByteArray &charset) 0431 { 0432 Q_D(SearchJob); 0433 d->charset = charset; 0434 } 0435 0436 QByteArray SearchJob::charset() const 0437 { 0438 Q_D(const SearchJob); 0439 return d->charset; 0440 } 0441 0442 void SearchJob::setUidBased(bool uidBased) 0443 { 0444 Q_D(SearchJob); 0445 d->uidBased = uidBased; 0446 } 0447 0448 bool SearchJob::isUidBased() const 0449 { 0450 Q_D(const SearchJob); 0451 return d->uidBased; 0452 } 0453 0454 QVector<qint64> SearchJob::results() const 0455 { 0456 Q_D(const SearchJob); 0457 return d->results; 0458 }