File indexing completed on 2024-09-22 04:52:51
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 #include <typeinfo> 0023 #include <QSslError> 0024 #include "Response.h" 0025 #include "Message.h" 0026 #include "LowLevelParser.h" 0027 #include "../Model/Model.h" 0028 #include "../Tasks/ImapTask.h" 0029 0030 namespace Imap 0031 { 0032 namespace Responses 0033 { 0034 0035 AbstractResponse::~AbstractResponse() 0036 { 0037 } 0038 0039 static QString threadDumpHelper(const ThreadingNode &node); 0040 static void threadingHelperInsertHere(ThreadingNode *where, const QVariantList &what); 0041 0042 QTextStream &operator<<(QTextStream &stream, const Code &r) 0043 { 0044 #define CASE(X) case X: stream << #X; break; 0045 switch (r) { 0046 case ATOM: 0047 stream << "<<ATOM>>'"; break; 0048 CASE(ALERT); 0049 CASE(BADCHARSET); 0050 CASE(CAPABILITIES); 0051 CASE(PARSE); 0052 CASE(PERMANENTFLAGS); 0053 case READ_ONLY: 0054 stream << "READ-ONLY"; break; 0055 case READ_WRITE: 0056 stream << "READ-WRITE"; break; 0057 CASE(TRYCREATE); 0058 CASE(UIDNEXT); 0059 CASE(UIDVALIDITY); 0060 CASE(UNSEEN); 0061 case NONE: 0062 stream << "<<NONE>>"; break; 0063 CASE(NEWNAME); 0064 CASE(REFERRAL); 0065 case UNKNOWN_CTE: 0066 stream << "UNKNOWN-CTE"; break; 0067 CASE(UIDNOTSTICKY); 0068 CASE(APPENDUID); 0069 CASE(COPYUID); 0070 CASE(URLMECH); 0071 CASE(TOOBIG); 0072 CASE(BADURL); 0073 CASE(HIGHESTMODSEQ); 0074 CASE(NOMODSEQ); 0075 CASE(MODIFIED); 0076 CASE(COMPRESSIONACTIVE); 0077 CASE(CLOSED); 0078 CASE(NOTSAVED); 0079 CASE(BADCOMPARATOR); 0080 CASE(ANNOTATE); 0081 CASE(ANNOTATIONS); 0082 CASE(TEMPFAIL); 0083 CASE(MAXCONVERTMESSAGES); 0084 CASE(MAXCONVERTPARTS); 0085 CASE(NOUPDATE); 0086 CASE(METADATA); 0087 CASE(NOTIFICATIONOVERFLOW); 0088 CASE(BADEVENT); 0089 case UNDEFINED_FILTER: 0090 stream << "UNDEFINED-FILTER"; break; 0091 CASE(UNAVAILABLE); 0092 CASE(AUTHENTICATIONFAILED); 0093 CASE(AUTHORIZATIONFAILED); 0094 CASE(EXPIRED); 0095 CASE(PRIVACYREQUIRED); 0096 CASE(CONTACTADMIN); 0097 CASE(NOPERM); 0098 CASE(INUSE); 0099 CASE(EXPUNGEISSUED); 0100 CASE(CORRUPTION); 0101 CASE(SERVERBUG); 0102 CASE(CLIENTBUG); 0103 CASE(CANNOT); 0104 CASE(LIMIT); 0105 CASE(OVERQUOTA); 0106 CASE(ALREADYEXISTS); 0107 CASE(NONEXISTENT); 0108 CASE(POLICYDENIED); 0109 CASE(SUBMISSIONRACE); 0110 } 0111 return stream; 0112 #undef CASE 0113 } 0114 0115 QTextStream &operator<<(QTextStream &stream, const Kind &res) 0116 { 0117 switch (res) { 0118 case OK: 0119 stream << "OK"; 0120 break; 0121 case NO: 0122 stream << "NO"; 0123 break; 0124 case BAD: 0125 stream << "BAD"; 0126 break; 0127 case BYE: 0128 stream << "BYE"; 0129 break; 0130 case PREAUTH: 0131 stream << "PREAUTH"; 0132 break; 0133 case EXPUNGE: 0134 stream << "EXPUNGE"; 0135 break; 0136 case FETCH: 0137 stream << "FETCH"; 0138 break; 0139 case EXISTS: 0140 stream << "EXISTS"; 0141 break; 0142 case RECENT: 0143 stream << "RECENT"; 0144 break; 0145 case CAPABILITY: 0146 stream << "CAPABILITY"; 0147 break; 0148 case LIST: 0149 stream << "LIST"; 0150 break; 0151 case LSUB: 0152 stream << "LSUB"; 0153 break; 0154 case FLAGS: 0155 stream << "FLAGS"; 0156 break; 0157 case SEARCH: 0158 stream << "SEARCH"; 0159 break; 0160 case ESEARCH: 0161 stream << "ESEARCH"; 0162 break; 0163 case STATUS: 0164 stream << "STATUS"; 0165 break; 0166 case NAMESPACE: 0167 stream << "NAMESPACE"; 0168 break; 0169 case SORT: 0170 stream << "SORT"; 0171 break; 0172 case THREAD: 0173 stream << "THREAD"; 0174 break; 0175 case ID: 0176 stream << "ID"; 0177 break; 0178 case ENABLED: 0179 stream << "ENABLED"; 0180 break; 0181 case VANISHED: 0182 stream << "VANISHED"; 0183 break; 0184 case GENURLAUTH: 0185 stream << "GENURLAUTH"; 0186 break; 0187 } 0188 return stream; 0189 } 0190 0191 Kind kindFromString(QByteArray str) 0192 { 0193 str = str.toUpper(); 0194 0195 if (str == "OK") 0196 return OK; 0197 if (str == "NO") 0198 return NO; 0199 if (str == "BAD") 0200 return BAD; 0201 if (str == "BYE") 0202 return BYE; 0203 if (str == "PREAUTH") 0204 return PREAUTH; 0205 if (str == "FETCH") 0206 return FETCH; 0207 if (str == "EXPUNGE") 0208 return EXPUNGE; 0209 if (str == "EXISTS") 0210 return EXISTS; 0211 if (str == "RECENT") 0212 return RECENT; 0213 if (str == "CAPABILITY") 0214 return CAPABILITY; 0215 if (str == "LIST") 0216 return LIST; 0217 if (str == "LSUB") 0218 return LSUB; 0219 if (str == "FLAGS") 0220 return FLAGS; 0221 if (str == "SEARCH" || str == "SEARCH\r\n") 0222 return SEARCH; 0223 if (str == "ESEARCH") 0224 return ESEARCH; 0225 if (str == "STATUS") 0226 return STATUS; 0227 if (str == "NAMESPACE") 0228 return NAMESPACE; 0229 if (str == "SORT") 0230 return SORT; 0231 if (str == "THREAD") 0232 return THREAD; 0233 if (str == "ID") 0234 return ID; 0235 if (str == "ENABLED") 0236 return ENABLED; 0237 if (str == "VANISHED") 0238 return VANISHED; 0239 if (str == "GENURLAUTH") 0240 return GENURLAUTH; 0241 throw UnrecognizedResponseKind(str.constData()); 0242 } 0243 0244 QTextStream &operator<<(QTextStream &stream, const Status::StateKind &kind) 0245 { 0246 switch (kind) { 0247 case Status::MESSAGES: 0248 stream << "MESSAGES"; break; 0249 case Status::RECENT: 0250 stream << "RECENT"; break; 0251 case Status::UIDNEXT: 0252 stream << "UIDNEXT"; break; 0253 case Status::UIDVALIDITY: 0254 stream << "UIDVALIDITY"; break; 0255 case Status::UNSEEN: 0256 stream << "UNSEEN"; break; 0257 } 0258 return stream; 0259 } 0260 0261 QTextStream &operator<<(QTextStream &stream, const NamespaceData &data) 0262 { 0263 return stream << "( prefix \"" << data.prefix << "\", separator \"" << data.separator << "\")"; 0264 } 0265 0266 Status::StateKind Status::stateKindFromStr(QString s) 0267 { 0268 s = s.toUpper(); 0269 if (s == QLatin1String("MESSAGES")) 0270 return MESSAGES; 0271 if (s == QLatin1String("RECENT")) 0272 return RECENT; 0273 if (s == QLatin1String("UIDNEXT")) 0274 return UIDNEXT; 0275 if (s == QLatin1String("UIDVALIDITY")) 0276 return UIDVALIDITY; 0277 if (s == QLatin1String("UNSEEN")) 0278 return UNSEEN; 0279 throw UnrecognizedResponseKind(s.toStdString()); 0280 } 0281 0282 QTextStream &operator<<(QTextStream &stream, const AbstractResponse &res) 0283 { 0284 return res.dump(stream); 0285 } 0286 0287 State::State(const QByteArray &tag, const Kind kind, const QByteArray &line, int &start): 0288 tag(tag), kind(kind), respCode(NONE) 0289 { 0290 if (!tag.isEmpty()) { 0291 // tagged 0292 switch (kind) { 0293 case OK: 0294 case NO: 0295 case BAD: 0296 break; 0297 default: 0298 throw UnexpectedHere(line, start); // tagged response of weird kind 0299 } 0300 } 0301 0302 QStringList list; 0303 QVariantList originalList; 0304 try { 0305 originalList = LowLevelParser::parseList('[', ']', line, start); 0306 list = QVariant(originalList).toStringList(); 0307 ++start; 0308 } catch (UnexpectedHere &) { 0309 // this is perfectly possible 0310 } 0311 0312 if (!list.isEmpty()) { 0313 const QString r = list.first().toUpper(); 0314 #define CASE(X) else if (r == QLatin1String(#X)) respCode = Responses::X; 0315 if (r == QLatin1String("ALERT")) 0316 respCode = Responses::ALERT; 0317 CASE(BADCHARSET) 0318 else if (r == QLatin1String("CAPABILITY")) 0319 respCode = Responses::CAPABILITIES; 0320 CASE(PARSE) 0321 CASE(PERMANENTFLAGS) 0322 else if (r == QLatin1String("READ-ONLY")) 0323 respCode = Responses::READ_ONLY; 0324 else if (r == QLatin1String("READ-WRITE")) 0325 respCode = Responses::READ_WRITE; 0326 CASE(TRYCREATE) 0327 CASE(UIDNEXT) 0328 CASE(UIDVALIDITY) 0329 CASE(UNSEEN) 0330 CASE(NEWNAME) 0331 CASE(REFERRAL) 0332 else if (r == QLatin1String("UNKNOWN-CTE")) 0333 respCode = Responses::UNKNOWN_CTE; 0334 CASE(UIDNOTSTICKY) 0335 CASE(APPENDUID) 0336 CASE(COPYUID) 0337 // FIXME: not implemented CASE(URLMECH) 0338 CASE(TOOBIG) 0339 CASE(BADURL) 0340 CASE(HIGHESTMODSEQ) 0341 CASE(NOMODSEQ) 0342 // FIXME: not implemented CASE(MODIFIED) 0343 CASE(COMPRESSIONACTIVE) 0344 CASE(CLOSED) 0345 CASE(NOTSAVED) 0346 CASE(BADCOMPARATOR) 0347 CASE(ANNOTATE) 0348 // FIXME: not implemented CASE(ANNOTATIONS) 0349 CASE(TEMPFAIL) 0350 CASE(MAXCONVERTMESSAGES) 0351 CASE(MAXCONVERTPARTS) 0352 CASE(NOUPDATE) 0353 // FIXME: not implemented CASE(METADATA) 0354 CASE(NOTIFICATIONOVERFLOW) 0355 CASE(BADEVENT) 0356 else if (r == QLatin1String("UNDEFINED-FILTER")) 0357 respCode = Responses::UNDEFINED_FILTER; 0358 CASE(UNAVAILABLE) 0359 CASE(AUTHENTICATIONFAILED) 0360 CASE(AUTHORIZATIONFAILED) 0361 CASE(EXPIRED) 0362 CASE(PRIVACYREQUIRED) 0363 CASE(CONTACTADMIN) 0364 CASE(NOPERM) 0365 CASE(INUSE) 0366 CASE(EXPUNGEISSUED) 0367 CASE(CORRUPTION) 0368 CASE(SERVERBUG) 0369 CASE(CLIENTBUG) 0370 CASE(CANNOT) 0371 CASE(LIMIT) 0372 CASE(OVERQUOTA) 0373 CASE(ALREADYEXISTS) 0374 CASE(NONEXISTENT) 0375 CASE(POLICYDENIED) 0376 CASE(SUBMISSIONRACE) 0377 else 0378 respCode = Responses::ATOM; 0379 0380 if (respCode != Responses::ATOM) 0381 list.pop_front(); 0382 0383 // now perform validity check and construct & store the storage object 0384 switch (respCode) { 0385 case Responses::ALERT: 0386 case Responses::PARSE: 0387 case Responses::READ_ONLY: 0388 case Responses::READ_WRITE: 0389 case Responses::TRYCREATE: 0390 case Responses::UNAVAILABLE: 0391 case Responses::AUTHENTICATIONFAILED: 0392 case Responses::AUTHORIZATIONFAILED: 0393 case Responses::EXPIRED: 0394 case Responses::PRIVACYREQUIRED: 0395 case Responses::CONTACTADMIN: 0396 case Responses::NOPERM: 0397 case Responses::INUSE: 0398 case Responses::EXPUNGEISSUED: 0399 case Responses::CORRUPTION: 0400 case Responses::SERVERBUG: 0401 case Responses::CLIENTBUG: 0402 case Responses::CANNOT: 0403 case Responses::LIMIT: 0404 case Responses::OVERQUOTA: 0405 case Responses::ALREADYEXISTS: 0406 case Responses::NONEXISTENT: 0407 case Responses::UNKNOWN_CTE: 0408 case Responses::UIDNOTSTICKY: 0409 case Responses::TOOBIG: 0410 case Responses::NOMODSEQ: 0411 case Responses::COMPRESSIONACTIVE: 0412 case Responses::CLOSED: 0413 case Responses::NOTSAVED: 0414 case Responses::BADCOMPARATOR: 0415 case Responses::TEMPFAIL: 0416 case Responses::NOTIFICATIONOVERFLOW: 0417 case Responses::POLICYDENIED: 0418 case Responses::SUBMISSIONRACE: 0419 // check for "no more stuff" 0420 if (list.count()) 0421 throw InvalidResponseCode("Got a response code with extra data inside the brackets", 0422 line, start); 0423 respCodeData = QSharedPointer<AbstractData>(new RespData<void>()); 0424 break; 0425 case Responses::UIDNEXT: 0426 case Responses::UIDVALIDITY: 0427 case Responses::UNSEEN: 0428 case Responses::MAXCONVERTMESSAGES: 0429 case Responses::MAXCONVERTPARTS: 0430 // check for "number only" 0431 { 0432 if (list.count() != 1) 0433 throw InvalidResponseCode(line, start); 0434 bool ok; 0435 uint number = list.first().toUInt(&ok); 0436 if (!ok) 0437 throw InvalidResponseCode(line, start); 0438 respCodeData = QSharedPointer<AbstractData>(new RespData<uint>(number)); 0439 } 0440 break; 0441 case Responses::HIGHESTMODSEQ: 0442 // similar to the above, but an unsigned 64bit int 0443 { 0444 if (list.count() != 1) 0445 throw InvalidResponseCode(line, start); 0446 bool ok; 0447 quint64 number = list.first().toULongLong(&ok); 0448 if (!ok) 0449 throw InvalidResponseCode(line, start); 0450 respCodeData = QSharedPointer<AbstractData>(new RespData<quint64>(number)); 0451 } 0452 break; 0453 case Responses::BADCHARSET: 0454 case Responses::PERMANENTFLAGS: 0455 case Responses::BADEVENT: 0456 // The following text should be a parenthesized list of atoms 0457 if (originalList.size() == 1) { 0458 if (respCode != Responses::BADCHARSET) { 0459 // BADCHARSET can be empty without any problem, but PERMANENTFLAGS or BADEVENT shouldn't 0460 qDebug() << "Parser warning: empty PERMANENTFLAGS or BADEVENT"; 0461 } 0462 respCodeData = QSharedPointer<AbstractData>(new RespData<QStringList>(QStringList())); 0463 } else if (originalList.size() == 2 && originalList[1].type() == QVariant::List) { 0464 respCodeData = QSharedPointer<AbstractData>(new RespData<QStringList>(originalList[1].toStringList())); 0465 } else { 0466 // Well, we used to accept "* OK [PERMANENTFLAGS foo bar] xyz" for quite long time, 0467 // so no need to break backwards compatibility here 0468 respCodeData = QSharedPointer<AbstractData>(new RespData<QStringList>(list)); 0469 qDebug() << "Parser warning: obsolete format of BADCAHRSET/PERMANENTFLAGS/BADEVENT"; 0470 } 0471 break; 0472 case Responses::CAPABILITIES: 0473 // no check here 0474 respCodeData = QSharedPointer<AbstractData>(new RespData<QStringList>(list)); 0475 break; 0476 case Responses::ATOM: // no sanity check here, just make a string 0477 case Responses::NONE: // this won't happen, but if we omit it, gcc warns us about that 0478 respCodeData = QSharedPointer<AbstractData>(new RespData<QString>(list.join(QStringLiteral(" ")))); 0479 break; 0480 case Responses::NEWNAME: 0481 case Responses::REFERRAL: 0482 case Responses::BADURL: 0483 // FIXME: check if indeed won't eat the spaces 0484 respCodeData = QSharedPointer<AbstractData>(new RespData<QString>(list.join(QStringLiteral(" ")))); 0485 break; 0486 case Responses::NOUPDATE: 0487 // FIXME: check if indeed won't eat the spaces, and optionally verify that it came as a quoted string 0488 respCodeData = QSharedPointer<AbstractData>(new RespData<QString>(list.join(QStringLiteral(" ")))); 0489 break; 0490 case Responses::UNDEFINED_FILTER: 0491 // FIXME: check if indeed won't eat the spaces, and optionally verify that it came as an atom 0492 respCodeData = QSharedPointer<AbstractData>(new RespData<QString>(list.join(QStringLiteral(" ")))); 0493 break; 0494 case Responses::APPENDUID: 0495 { 0496 // FIXME: this is broken with MULTIAPPEND 0497 if (originalList.size() != 3) 0498 throw InvalidResponseCode("Malformed APPENDUID: wrong number of arguments", line, start); 0499 bool ok; 0500 uint uidValidity = originalList[1].toUInt(&ok); 0501 if (!ok) 0502 throw InvalidResponseCode("Malformed APPENDUID: cannot extract UIDVALIDITY", line, start); 0503 int pos = 0; 0504 QByteArray s1 = originalList[2].toByteArray(); 0505 Sequence seq = Sequence::fromVector(LowLevelParser::getSequence(s1, pos)); 0506 if (!seq.isValid()) 0507 throw InvalidResponseCode("Malformed APPENDUID: cannot extract UID or the list of UIDs", line, start); 0508 if (pos != s1.size()) 0509 throw InvalidResponseCode("Malformed APPENDUID: garbage found after the list of UIDs", line, start); 0510 respCodeData = QSharedPointer<AbstractData>(new RespData<QPair<uint,Sequence> >(qMakePair(uidValidity, seq))); 0511 break; 0512 } 0513 case Responses::COPYUID: 0514 { 0515 // FIXME: don't return anything, this is broken with multiple messages, unfortunately 0516 break; 0517 if (originalList.size() != 4) 0518 throw InvalidResponseCode("Malformed COPYUID: wrong number of arguments", line, start); 0519 bool ok; 0520 uint uidValidity = originalList[1].toUInt(&ok); 0521 if (!ok) 0522 throw InvalidResponseCode("Malformed COPYUID: cannot extract UIDVALIDITY", line, start); 0523 int pos = 0; 0524 QByteArray s1 = originalList[2].toByteArray(); 0525 Sequence seq1 = Sequence::fromVector(LowLevelParser::getSequence(s1, pos)); 0526 if (!seq1.isValid()) 0527 throw InvalidResponseCode("Malformed COPYUID: cannot extract the first sequence", line, start); 0528 if (pos != s1.size()) 0529 throw InvalidResponseCode("Malformed COPYUID: garbage found after the first sequence", line, start); 0530 pos = 0; 0531 QByteArray s2 = originalList[3].toByteArray(); 0532 Sequence seq2 = Sequence::fromVector(LowLevelParser::getSequence(s2, pos)); 0533 if (!seq2.isValid()) 0534 throw InvalidResponseCode("Malformed COPYUID: cannot extract the second sequence", line, start); 0535 if (pos != s2.size()) 0536 throw InvalidResponseCode("Malformed COPYUID: garbage found after the second sequence", line, start); 0537 respCodeData = QSharedPointer<AbstractData>(new RespData<QPair<uint,QPair<Sequence, Sequence> > >( 0538 qMakePair(uidValidity, qMakePair<Sequence, Sequence>(seq1, seq2)))); 0539 break; 0540 } 0541 case Responses::URLMECH: 0542 // FIXME: implement me 0543 Q_ASSERT(false); 0544 break; 0545 case Responses::MODIFIED: 0546 // FIXME: clarify what "set" in the RFC 4551 means and implement it 0547 Q_ASSERT(false); 0548 break; 0549 case Responses::ANNOTATE: 0550 { 0551 if (list.count() != 1) 0552 throw InvalidResponseCode("ANNOTATE response code got weird number of elements", line, start); 0553 QString token = list.first().toUpper(); 0554 if (token == QLatin1String("TOOBIG") || token == QLatin1String("TOOMANY")) 0555 respCodeData = QSharedPointer<AbstractData>(new RespData<QString>(token)); 0556 else 0557 throw InvalidResponseCode("ANNOTATE response code with invalid value", line, start); 0558 } 0559 break; 0560 case Responses::ANNOTATIONS: 0561 case Responses::METADATA: 0562 // FIXME: implement me 0563 Q_ASSERT(false); 0564 break; 0565 } 0566 } 0567 0568 if (start >= line.size() - 2) { 0569 if (originalList.isEmpty()) { 0570 qDebug() << "Response with no data at all, yuck" << line; 0571 } else { 0572 qDebug() << "Response with no data besides the response code, yuck" << line; 0573 } 0574 } else { 0575 message = QString::fromUtf8(line.mid(start)); 0576 Q_ASSERT(message.endsWith(QLatin1String("\r\n"))); 0577 message.chop(2); 0578 } 0579 } 0580 0581 NumberResponse::NumberResponse(const Kind kind, const uint number): 0582 kind(kind), number(number) 0583 { 0584 if (kind != EXISTS && kind != EXPUNGE && kind != RECENT) 0585 throw UnexpectedHere("Attempted to create NumberResponse of invalid kind"); 0586 } 0587 0588 List::List(const Kind kind, const QByteArray &line, int &start): 0589 kind(kind) 0590 { 0591 if (kind != LIST && kind != LSUB) 0592 throw UnexpectedHere(line, start); // FIXME: well, "start" is too late here... 0593 0594 flags = QVariant(LowLevelParser::parseList('(', ')', line, start)).toStringList(); 0595 ++start; 0596 0597 if (start >= line.size() - 5) 0598 throw NoData(line, start); // flags and nothing else 0599 0600 if (line.at(start) == '"') { 0601 ++start; 0602 if (line.at(start) == '\\') 0603 ++start; 0604 separator = QChar::fromLatin1(line.at(start)); 0605 ++start; 0606 if (line.at(start) != '"') 0607 throw ParseError(line, start); 0608 ++start; 0609 } else if (line.mid(start, 3).toLower() == "nil") { 0610 separator = QString(); 0611 start += 3; 0612 } else { 0613 throw ParseError(line, start); 0614 } 0615 0616 ++start; 0617 0618 if (start >= line.size() - 2) 0619 throw NoData(line, start); // no mailbox 0620 0621 mailbox = LowLevelParser::getMailbox(line, start); 0622 0623 LowLevelParser::eatSpaces(line, start); 0624 if (start < line.size() - 4) { 0625 QVariantList extData = LowLevelParser::parseList('(', ')', line, start); 0626 if (extData.size() % 2) { 0627 throw ParseError("list-extended contains list with odd number of items", line, start); 0628 } 0629 0630 for (int i = 0; i < extData.size(); i += 2) { 0631 QByteArray key = extData[i].toByteArray(); 0632 QVariant data = extData[i+1]; 0633 extendedData[key] = data; 0634 } 0635 } 0636 0637 if (start > line.size() - 2) 0638 throw TooMuchData(line, start); 0639 } 0640 0641 Flags::Flags(const QByteArray &line, int &start) 0642 { 0643 flags = QVariant(LowLevelParser::parseList('(', ')', line, start)).toStringList(); 0644 if (start >= line.size()) 0645 throw TooMuchData(line, start); 0646 } 0647 0648 Search::Search(const QByteArray &line, int &start) 0649 { 0650 while (start < line.size() - 2) { 0651 try { 0652 uint number = LowLevelParser::getUInt(line, start); 0653 items << number; 0654 ++start; 0655 } catch (ParseError &) { 0656 throw UnexpectedHere(line, start); 0657 } 0658 } 0659 } 0660 0661 ESearch::ESearch(const QByteArray &line, int &start): seqOrUids(SEQUENCE) 0662 { 0663 LowLevelParser::eatSpaces(line, start); 0664 0665 if (start >= line.size() - 2) { 0666 // an empty ESEARCH response; that shall be OK 0667 return; 0668 } 0669 0670 if (line[start] == '(') { 0671 // Extract the search-correlator 0672 ++start; 0673 if (start >= line.size()) throw NoData(line, start); 0674 0675 // extract the optional tag specifier 0676 QByteArray header = LowLevelParser::getAtom(line, start).toUpper(); 0677 if (header != QByteArray("TAG")) { 0678 throw ParseError("ESEARCH response: malformed search-correlator", line, start); 0679 } 0680 0681 LowLevelParser::eatSpaces(line, start); 0682 if (start >= line.size()) throw NoData(line, start); 0683 0684 QPair<QByteArray,LowLevelParser::ParsedAs> astring = LowLevelParser::getAString(line, start); 0685 tag = astring.first; 0686 if (start >= line.size()) throw NoData(line, start); 0687 0688 if (line[start] != ')') 0689 throw ParseError("ESEARCH: search-correlator not enclosed in parentheses", line, start); 0690 0691 ++start; 0692 LowLevelParser::eatSpaces(line, start); 0693 } 0694 0695 if (start >= line.size() - 2) { 0696 // So the search-correlator was given, but there isn't anything besides that. Well, let's accept that. 0697 return; 0698 } 0699 0700 // Extract the "UID" specifier, if present 0701 try { 0702 int oldStart = start; 0703 QByteArray uid = LowLevelParser::getAtom(line, start); 0704 if (uid.toUpper() == QByteArray("UID")) { 0705 seqOrUids = UIDS; 0706 } else { 0707 // got to push the token "back" 0708 start = oldStart; 0709 } 0710 } catch (ParseError &) { 0711 seqOrUids = SEQUENCE; 0712 } 0713 0714 LowLevelParser::eatSpaces(line, start); 0715 0716 if (start >= line.size() - 2) { 0717 // No data -> again, accept 0718 return; 0719 } 0720 0721 while (start < line.size() - 2) { 0722 QByteArray label = LowLevelParser::getAtom(line, start).toUpper(); 0723 0724 LowLevelParser::eatSpaces(line, start); 0725 0726 if (label == "ADDTO" || label == "REMOVEFROM") { 0727 // These two are special, their structure is more complex and has to be parsed into a specialized storage 0728 0729 // We don't really want to use generic LowLevelParser::parseList() here as it'd happily cut our sequence-se 0730 // into a list of numbers which we'd have to join back together and feed to LowLevelParser::getSequence. 0731 // This hand-crafted code is better (and isn't so longer, either). 0732 0733 if (start >= line.size() - 2) 0734 throw NoData("CONTEXT ESEARCH: no incremental update found", line, start); 0735 0736 if (line[start] != '(') 0737 throw UnexpectedHere("CONTEXT ESEARCH: missing '('", line, start); 0738 ++start; 0739 0740 do { 0741 // Each ADDTO/REMOVEFROM record can contain many offsets-seq pairs 0742 0743 uint offset = LowLevelParser::getUInt(line, start); 0744 LowLevelParser::eatSpaces(line, start); 0745 auto uids = LowLevelParser::getSequence(line, start); 0746 0747 incrementalContextData.push_back(ContextIncrementalItem( 0748 (label == "ADDTO" ? 0749 ContextIncrementalItem::ADDTO : 0750 ContextIncrementalItem::REMOVEFROM), 0751 offset, uids)); 0752 0753 LowLevelParser::eatSpaces(line, start); 0754 0755 if (start >= line.size() - 2) 0756 throw NoData("CONTEXT ESEARCH: truncated update record?", line, start); 0757 0758 if (line[start] == ')') { 0759 // end of current ADDTO/REMOVEFROM group 0760 ++start; 0761 LowLevelParser::eatSpaces(line, start); 0762 break; 0763 } 0764 0765 } while (true); 0766 } else if (label == "INCTHREAD") { 0767 // another special case using completely different structure 0768 0769 if (start >= line.size() - 2) 0770 throw NoData("ESEARCH THREAD: no data", line, start); 0771 0772 const uint previousRoot = LowLevelParser::getUInt(line, start); 0773 LowLevelParser::eatSpaces(line, start); 0774 0775 ThreadingNode node; 0776 while (start < line.size() - 2 && line[start] == '(') { 0777 QVariantList current = LowLevelParser::parseList('(', ')', line, start); 0778 threadingHelperInsertHere(&node, current); 0779 } 0780 incThreadData.push_back(IncrementalThreadingItem_t(previousRoot, node.children)); 0781 LowLevelParser::eatSpaces(line, start); 0782 } else { 0783 // A generic case: be prepapred to accept a (sequence of) numbers 0784 0785 auto numbers = LowLevelParser::getSequence(line, start); 0786 // There's no syntactic difference between a single-item sequence set and one number, which is why we always parse 0787 // such "sequences" as full blown sequences. That's better than deal with two nasties of the ListData_t kind -- one such 0788 // beast is more than enough, IMHO. 0789 listData.push_back(qMakePair<>(label, numbers)); 0790 0791 LowLevelParser::eatSpaces(line, start); 0792 } 0793 } 0794 } 0795 0796 Status::Status(const QByteArray &line, int &start) 0797 { 0798 mailbox = LowLevelParser::getMailbox(line, start); 0799 ++start; 0800 if (start >= line.size()) 0801 throw NoData(line, start); 0802 QStringList items = QVariant(LowLevelParser::parseList('(', ')', line, start)).toStringList(); 0803 if (start != line.size() - 2 && line.mid(start) != QByteArray(" \r\n")) 0804 throw TooMuchData("STATUS response contains data after the list of items", line, start); 0805 0806 bool gotIdentifier = false; 0807 QString identifier; 0808 for (QStringList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) { 0809 if (gotIdentifier) { 0810 gotIdentifier = false; 0811 bool ok; 0812 uint number = it->toUInt(&ok); 0813 if (!ok) 0814 throw ParseError(line, start); 0815 StateKind kind; 0816 try { 0817 kind = stateKindFromStr(identifier); 0818 } catch (UnrecognizedResponseKind &e) { 0819 throw UnrecognizedResponseKind(e.what(), line, start); 0820 } 0821 states[kind] = number; 0822 } else { 0823 identifier = *it; 0824 gotIdentifier = true; 0825 } 0826 } 0827 if (gotIdentifier) 0828 throw ParseError(line, start); 0829 } 0830 0831 QDateTime Fetch::dateify(QByteArray str, const QByteArray &line, const int start) 0832 { 0833 // FIXME: all offsets in exceptions are broken here. 0834 if (str.size() == 25) 0835 str = QByteArray::number(0) + str; 0836 if (str.size() != 26) 0837 throw ParseError(line, start); 0838 0839 QDateTime date = QLocale(QLocale::C).toDateTime(QString::fromUtf8(str.left(20)), QStringLiteral("d-MMM-yyyy HH:mm:ss")); 0840 const char sign = str[21]; 0841 bool ok; 0842 int hours = str.mid(22, 2).toInt(&ok); 0843 if (!ok) 0844 throw ParseError(line, start); 0845 int minutes = str.mid(24, 2).toInt(&ok); 0846 if (!ok) 0847 throw ParseError(line, start); 0848 switch (sign) { 0849 case '+': 0850 date = date.addSecs(-3600*hours - 60*minutes); 0851 break; 0852 case '-': 0853 date = date.addSecs(+3600*hours + 60*minutes); 0854 break; 0855 default: 0856 throw ParseError(line, start); 0857 } 0858 date.setTimeSpec(Qt::UTC); 0859 return date; 0860 } 0861 0862 Fetch::Fetch(const uint number, const QByteArray &line, int &start): number(number) 0863 { 0864 ++start; 0865 0866 if (start >= line.size()) 0867 throw NoData(line, start); 0868 0869 if (line[start++] != '(') 0870 throw UnexpectedHere("FETCH response should consist of a parenthesized list", line, start); 0871 0872 while (start < line.size() && line[start] != ')') { 0873 int posBeforeIdentifier = start; 0874 QByteArray identifier = LowLevelParser::getAtom(line, start).toUpper(); 0875 if (identifier.contains('[')) { 0876 // special case: these identifiers can contain spaces 0877 int pos = line.indexOf(']', posBeforeIdentifier); 0878 if (pos == -1) 0879 throw UnexpectedHere("FETCH identifier contains \"[\", but no matching \"]\" was found", line, posBeforeIdentifier); 0880 identifier = line.mid(posBeforeIdentifier, pos - posBeforeIdentifier + 1).toUpper(); 0881 start = pos + 1; 0882 } 0883 0884 if (data.contains(identifier)) 0885 throw UnexpectedHere("FETCH response contains duplicate data", line, start); 0886 0887 if (start >= line.size()) 0888 throw NoData(line, start); 0889 0890 LowLevelParser::eatSpaces(line, start); 0891 0892 if (identifier == "MODSEQ") { 0893 if (line[start++] != '(') 0894 throw UnexpectedHere("FETCH MODSEQ must be a list"); 0895 data[identifier] = QSharedPointer<AbstractData>(new RespData<quint64>(LowLevelParser::getUInt64(line, start))); 0896 if (start >= line.size()) 0897 throw NoData(line, start); 0898 if (line[start++] != ')') 0899 throw UnexpectedHere("FETCH MODSEQ must be a list"); 0900 } else if (identifier == "FLAGS") { 0901 if (line[start++] != '(') 0902 throw UnexpectedHere("FETCH FLAGS must be a list"); 0903 QStringList flags; 0904 while (start < line.size() && line[start] != ')') { 0905 flags << QString::fromUtf8(LowLevelParser::getPossiblyBackslashedAtom(line, start)); 0906 LowLevelParser::eatSpaces(line, start); 0907 } 0908 data[identifier] = QSharedPointer<AbstractData>(new RespData<QStringList>(flags)); 0909 if (start >= line.size()) 0910 throw NoData(line, start); 0911 if (line[start++] != ')') 0912 throw UnexpectedHere("FETCH FLAGS must be a list"); 0913 } else if (identifier == "UID") { 0914 data[identifier] = QSharedPointer<AbstractData>(new RespData<uint>(LowLevelParser::getUInt(line, start))); 0915 } else if (identifier == "RFC822.SIZE") { 0916 data[identifier] = QSharedPointer<AbstractData>(new RespData<quint64>(LowLevelParser::getUInt64(line, start))); 0917 } else if (identifier.startsWith("BODY[") || identifier.startsWith("BINARY[") || identifier.startsWith("RFC822")) { 0918 data[identifier] = QSharedPointer<AbstractData>(new RespData<QByteArray>(LowLevelParser::getNString(line, start).first)); 0919 } else if (identifier == "ENVELOPE") { 0920 QVariantList list = LowLevelParser::parseList('(', ')', line, start); 0921 data[identifier] = QSharedPointer<AbstractData>(new RespData<Message::Envelope>(Message::Envelope::fromList(list, line, start))); 0922 } else if (identifier == "INTERNALDATE") { 0923 QByteArray buf = LowLevelParser::getNString(line, start).first; 0924 data[identifier] = QSharedPointer<AbstractData>(new RespData<QDateTime>(dateify(buf, line, start))); 0925 } else if (identifier == "BODY" || identifier == "BODYSTRUCTURE") { 0926 QVariantList list = LowLevelParser::parseList('(', ')', line, start); 0927 data[identifier] = Message::AbstractMessage::fromList(list, line, start); 0928 QByteArray buffer; 0929 QDataStream stream(&buffer, QIODevice::WriteOnly); 0930 stream.setVersion(QDataStream::Qt_4_6); 0931 stream << list; 0932 data["x-trojita-bodystructure"] = QSharedPointer<AbstractData>(new RespData<QByteArray>(buffer)); 0933 } else { 0934 // Unrecognized identifier, let's treat it as QByteArray so that we don't break needlessly 0935 data[identifier] = QSharedPointer<AbstractData>(new RespData<QByteArray>(LowLevelParser::getNString(line, start).first)); 0936 } 0937 0938 if (start >= line.size()) 0939 throw NoData(line, start); 0940 0941 LowLevelParser::eatSpaces(line, start); 0942 } 0943 0944 if (start >= line.size()) 0945 throw NoData(line, start); 0946 if (line[start] == ')') 0947 ++start; 0948 if (start != line.size() - 2) 0949 throw TooMuchData(line, start); 0950 } 0951 0952 Fetch::Fetch(const uint number, const Fetch::dataType &data): number(number), data(data) 0953 { 0954 } 0955 0956 QList<NamespaceData> NamespaceData::listFromLine(const QByteArray &line, int &start) 0957 { 0958 QList<NamespaceData> result; 0959 try { 0960 QVariantList list = LowLevelParser::parseList('(', ')', line, start); 0961 for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { 0962 if (it->type() != QVariant::List) 0963 throw UnexpectedHere("Malformed data found when processing one item " 0964 "in NAMESPACE record (not a list)", line, start); 0965 QStringList list = it->toStringList(); 0966 if (list.size() != 2) 0967 throw UnexpectedHere("Malformed data found when processing one item " 0968 "in NAMESPACE record (list of weird size)", line, start); 0969 result << NamespaceData(list[0], list[1]); 0970 } 0971 } catch (UnexpectedHere &) { 0972 // must be a NIL, then 0973 QPair<QByteArray,LowLevelParser::ParsedAs> res = LowLevelParser::getNString(line, start); 0974 if (res.second != LowLevelParser::NIL) { 0975 throw UnexpectedHere("Top-level NAMESPACE record is neither list nor NIL", line, start); 0976 } 0977 } 0978 ++start; 0979 return result; 0980 } 0981 0982 Namespace::Namespace(const QByteArray &line, int &start) 0983 { 0984 personal = NamespaceData::listFromLine(line, start); 0985 users = NamespaceData::listFromLine(line, start); 0986 other = NamespaceData::listFromLine(line, start); 0987 } 0988 0989 Sort::Sort(const QByteArray &line, int &start) 0990 { 0991 while (start < line.size() - 2) { 0992 try { 0993 uint number = LowLevelParser::getUInt(line, start); 0994 numbers << number; 0995 ++start; 0996 } catch (ParseError &) { 0997 throw UnexpectedHere(line, start); 0998 } 0999 } 1000 } 1001 1002 1003 Thread::Thread(const QByteArray &line, int &start) 1004 { 1005 ThreadingNode node; 1006 while (start < line.size() - 2) { 1007 QVariantList current = LowLevelParser::parseList('(', ')', line, start); 1008 threadingHelperInsertHere(&node, current); 1009 } 1010 rootItems = node.children; 1011 } 1012 1013 /** @short Helper for parsing the brain-dead RFC5256 THREAD response 1014 1015 Please note that the syntax for the THREAD untagged response, as defined in 1016 RFC5256, is rather counter-intuitive -- items placed at the *same* level in the 1017 list are actually parent/child, not siblings. 1018 */ 1019 static void threadingHelperInsertHere(ThreadingNode *where, const QVariantList &what) 1020 { 1021 bool first = true; 1022 for (QVariantList::const_iterator it = what.begin(); it != what.end(); ++it) { 1023 if (it->type() == QVariant::ByteArray) { 1024 where->children.append(ThreadingNode()); 1025 where = &(where->children.last()); 1026 bool ok; 1027 where->num = it->toUInt(&ok); 1028 if (!ok) { 1029 QString str; 1030 QTextStream ss(&str); 1031 ss << "THREAD response: cannot parse \"" << it->toByteArray() << "\" as an unsigned integer"; 1032 throw UnexpectedHere(str.toUtf8().constData()); 1033 } 1034 } else if (it->type() == QVariant::List) { 1035 if (first) { 1036 where->children.append(ThreadingNode()); 1037 where = &(where->children.last()); 1038 } 1039 threadingHelperInsertHere(where, it->toList()); 1040 } else { 1041 throw UnexpectedHere("THREAD structure contains garbage; expecting fancy list of uints"); 1042 } 1043 first = false; 1044 } 1045 } 1046 1047 Id::Id(const QByteArray &line, int &start) 1048 { 1049 try { 1050 QVariantList list = LowLevelParser::parseList('(', ')', line, start); 1051 if (list.size() % 2) { 1052 throw ParseError("ID response with invalid number of entries", line, start); 1053 } 1054 for (int i = 0; i < list.size() - 1; i += 2) { 1055 data[list[i].toByteArray()] = list[i+1].toByteArray(); 1056 } 1057 } catch (UnexpectedHere &) { 1058 // Check for NIL 1059 QPair<QByteArray,LowLevelParser::ParsedAs> nString = LowLevelParser::getNString(line, start); 1060 if (nString.second != LowLevelParser::NIL) { 1061 // Let's propagate the original exception from here 1062 throw; 1063 } else { 1064 // It's a NIL, which is explicitly OK, so let's just accept it 1065 } 1066 } 1067 } 1068 1069 Enabled::Enabled(const QByteArray &line, int &start) 1070 { 1071 LowLevelParser::eatSpaces(line, start); 1072 while (start < line.size() - 2) { 1073 QByteArray extension = LowLevelParser::getAtom(line, start); 1074 extensions << extension; 1075 LowLevelParser::eatSpaces(line, start); 1076 } 1077 } 1078 1079 Vanished::Vanished(const QByteArray &line, int &start): earlier(NOT_EARLIER) 1080 { 1081 LowLevelParser::eatSpaces(line, start); 1082 1083 if (start >= line.size() - 2) { 1084 throw NoData(line, start); 1085 } 1086 1087 const int prefixLength = strlen("(EARLIER)"); 1088 if (start < line.size() - prefixLength && line.mid(start, prefixLength).toUpper() == "(EARLIER)") { 1089 earlier = EARLIER; 1090 start += prefixLength + 1; // one for the required space 1091 } 1092 1093 uids = LowLevelParser::getSequence(line, start); 1094 1095 if (start != line.size() - 2) 1096 throw TooMuchData(line, start); 1097 } 1098 1099 GenUrlAuth::GenUrlAuth(const QByteArray &line, int &start) 1100 { 1101 url = QString::fromUtf8(LowLevelParser::getAString(line, start).first); 1102 if (start != line.size() - 2) 1103 throw TooMuchData(line, start); 1104 } 1105 1106 SocketEncryptedResponse::SocketEncryptedResponse(const QList<QSslCertificate> &sslChain, const QList<QSslError> &sslErrors): 1107 sslChain(sslChain), sslErrors(sslErrors) 1108 { 1109 } 1110 1111 QTextStream &State::dump(QTextStream &stream) const 1112 { 1113 if (!tag.isEmpty()) 1114 stream << tag; 1115 else 1116 stream << '*'; 1117 stream << ' ' << kind; 1118 if (respCode != NONE) 1119 stream << " [" << respCode << ' ' << *respCodeData << ']'; 1120 return stream << ' ' << message; 1121 } 1122 1123 QTextStream &Capability::dump(QTextStream &stream) const 1124 { 1125 return stream << "* CAPABILITY " << capabilities.join(QStringLiteral(", ")); 1126 } 1127 1128 QTextStream &NumberResponse::dump(QTextStream &stream) const 1129 { 1130 return stream << kind << " " << number; 1131 } 1132 1133 QTextStream &List::dump(QTextStream &stream) const 1134 { 1135 stream << kind << " '" << mailbox << "' (" << flags.join(QStringLiteral(", ")) << "), sep '" << separator << "'"; 1136 if (!extendedData.isEmpty()) { 1137 stream << " ("; 1138 for (QMap<QByteArray,QVariant>::const_iterator it = extendedData.constBegin(); it != extendedData.constEnd(); ++it) { 1139 stream << it.key() << " " << it.value().toString() << " "; 1140 } 1141 stream << ")"; 1142 } 1143 return stream; 1144 } 1145 1146 QTextStream &Flags::dump(QTextStream &stream) const 1147 { 1148 return stream << "FLAGS " << flags.join(QStringLiteral(", ")); 1149 } 1150 1151 QTextStream &Search::dump(QTextStream &stream) const 1152 { 1153 stream << "SEARCH"; 1154 for (auto it = items.begin(); it != items.end(); ++it) 1155 stream << " " << *it; 1156 return stream; 1157 } 1158 1159 QTextStream &ESearch::dump(QTextStream &stream) const 1160 { 1161 stream << "ESEARCH "; 1162 if (!tag.isEmpty()) 1163 stream << "TAG " << tag << " "; 1164 if (seqOrUids == UIDS) 1165 stream << "UID "; 1166 for (ListData_t::const_iterator it = listData.constBegin(); it != listData.constEnd(); ++it) { 1167 stream << it->first << " ("; 1168 Q_FOREACH(const uint number, it->second) { 1169 stream << number << " "; 1170 } 1171 stream << ") "; 1172 } 1173 for (IncrementalContextData_t::const_iterator it = incrementalContextData.constBegin(); 1174 it != incrementalContextData.constEnd(); ++it) { 1175 switch (it->modification) { 1176 case ContextIncrementalItem::ADDTO: 1177 stream << "ADDTO ("; 1178 break; 1179 case ContextIncrementalItem::REMOVEFROM: 1180 stream << "REMOVEFROM ("; 1181 break; 1182 default: 1183 Q_ASSERT(false); 1184 } 1185 stream << it->offset << " "; 1186 Q_FOREACH(const uint num, it->uids) { 1187 stream << num << ' '; 1188 } 1189 stream << ") "; 1190 } 1191 for (IncrementalThreadingData_t::const_iterator it = incThreadData.constBegin(); 1192 it != incThreadData.constEnd(); ++it) { 1193 ThreadingNode node; 1194 node.children = it->thread; 1195 stream << "INCTHREAD " << it->previousThreadRoot << " [THREAD parsed-into-sane-form follows] " << threadDumpHelper(node) << " "; 1196 } 1197 return stream; 1198 } 1199 1200 QTextStream &Status::dump(QTextStream &stream) const 1201 { 1202 stream << "STATUS " << mailbox; 1203 for (stateDataType::const_iterator it = states.begin(); it != states.end(); ++it) { 1204 stream << " " << it.key() << " " << it.value(); 1205 } 1206 return stream; 1207 } 1208 1209 QTextStream &Fetch::dump(QTextStream &stream) const 1210 { 1211 stream << "FETCH " << number << " ("; 1212 for (dataType::const_iterator it = data.begin(); 1213 it != data.end(); ++it) 1214 stream << ' ' << it.key() << " \"" << *it.value() << '"'; 1215 return stream << ')'; 1216 } 1217 1218 QTextStream &Namespace::dump(QTextStream &stream) const 1219 { 1220 stream << "NAMESPACE ("; 1221 QList<NamespaceData>::const_iterator it; 1222 for (it = personal.begin(); it != personal.end(); ++it) 1223 stream << *it << ","; 1224 stream << ") ("; 1225 for (it = users.begin(); it != users.end(); ++it) 1226 stream << *it << ","; 1227 stream << ") ("; 1228 for (it = other.begin(); it != other.end(); ++it) 1229 stream << *it << ","; 1230 return stream << ")"; 1231 } 1232 1233 QTextStream &Sort::dump(QTextStream &stream) const 1234 { 1235 stream << "SORT "; 1236 for (auto it = numbers.begin(); it != numbers.end(); ++it) 1237 stream << " " << *it; 1238 return stream; 1239 } 1240 1241 QTextStream &Thread::dump(QTextStream &stream) const 1242 { 1243 ThreadingNode node; 1244 node.children = rootItems; 1245 return stream << "THREAD {parsed-into-sane-form}" << threadDumpHelper(node); 1246 } 1247 1248 static QString threadDumpHelper(const ThreadingNode &node) 1249 { 1250 if (node.children.isEmpty()) { 1251 return QString::number(node.num); 1252 } else { 1253 QStringList res; 1254 for (QVector<ThreadingNode>::const_iterator it = node.children.begin(); it != node.children.end(); ++it) { 1255 res << threadDumpHelper(*it); 1256 } 1257 return QStringLiteral("%1: {%2}").arg(node.num).arg(res.join(QStringLiteral(", "))); 1258 } 1259 } 1260 1261 QTextStream &Id::dump(QTextStream &s) const 1262 { 1263 if (data.isEmpty()) { 1264 return s << "ID NIL"; 1265 } else { 1266 s << "ID ("; 1267 for (QMap<QByteArray,QByteArray>::const_iterator it = data.constBegin(); it != data.constEnd(); ++it) { 1268 if (it != data.constBegin()) 1269 s << " "; 1270 s << it.key() << " " << it.value(); 1271 } 1272 return s << ")"; 1273 } 1274 } 1275 1276 QTextStream &Enabled::dump(QTextStream &s) const 1277 { 1278 s << "ENABLED "; 1279 Q_FOREACH(const QByteArray &extension, extensions) { 1280 s << extension << ' '; 1281 } 1282 return s; 1283 } 1284 1285 QTextStream &Vanished::dump(QTextStream &s) const 1286 { 1287 s << "VANISHED "; 1288 if (earlier == EARLIER) 1289 s << "(EARLIER) "; 1290 s << "("; 1291 Q_FOREACH(const uint &uid, uids) { 1292 s << " " << uid; 1293 } 1294 return s << ")"; 1295 } 1296 1297 QTextStream &GenUrlAuth::dump(QTextStream &s) const 1298 { 1299 return s << "GENURLAUTH " << url; 1300 } 1301 1302 QTextStream &SocketEncryptedResponse::dump(QTextStream &s) const 1303 { 1304 s << "[Socket is encrypted now; "; 1305 if (sslErrors.isEmpty()) { 1306 s << "no errors]"; 1307 } else { 1308 s << sslErrors.size() << " errors: "; 1309 Q_FOREACH(const QSslError &e, sslErrors) { 1310 if (e.certificate().isNull()) { 1311 s << e.errorString() << " "; 1312 } else { 1313 s << e.errorString() << " (CN: " << e.certificate().subjectInfo(QSslCertificate::CommonName).join(QStringLiteral(", ")) << ") "; 1314 } 1315 } 1316 s << "]"; 1317 } 1318 return s; 1319 } 1320 1321 QTextStream &SocketDisconnectedResponse::dump(QTextStream &s) const 1322 { 1323 return s << "[Socket disconnected: " << message << "]"; 1324 } 1325 1326 QTextStream &ParseErrorResponse::dump(QTextStream &s) const 1327 { 1328 return s << "[Parse error (" << exceptionClass << "): " << message << "\n" << line << "\n" << offset << "]"; 1329 } 1330 1331 1332 template<class T> QTextStream &RespData<T>::dump(QTextStream &stream) const 1333 { 1334 return stream << data; 1335 } 1336 1337 template<> QTextStream &RespData<QStringList>::dump(QTextStream &stream) const 1338 { 1339 return stream << data.join(QStringLiteral(" ")); 1340 } 1341 1342 template<> QTextStream &RespData<QDateTime>::dump(QTextStream &stream) const 1343 { 1344 return stream << data.toString(); 1345 } 1346 1347 template<> QTextStream &RespData<QPair<uint,Sequence> >::dump(QTextStream &stream) const 1348 { 1349 return stream << "UIDVALIDITY " << data.first << " UIDs" << data.second; 1350 } 1351 1352 template<> QTextStream &RespData<QPair<uint,QPair<Sequence, Sequence> > >::dump(QTextStream &stream) const 1353 { 1354 return stream << "UIDVALIDITY " << data.first << " UIDs-1" << data.second.first << " UIDs-2" << data.second.second; 1355 } 1356 1357 bool RespData<void>::eq(const AbstractData &other) const 1358 { 1359 try { 1360 // There's no data involved, so we just want to compare the type 1361 (void) dynamic_cast<const RespData<void>&>(other); 1362 return true; 1363 } catch (std::bad_cast &) { 1364 return false; 1365 } 1366 } 1367 1368 template<class T> bool RespData<T>::eq(const AbstractData &other) const 1369 { 1370 try { 1371 const RespData<T> &r = dynamic_cast<const RespData<T>&>(other); 1372 return data == r.data; 1373 } catch (std::bad_cast &) { 1374 return false; 1375 } 1376 } 1377 1378 bool Capability::eq(const AbstractResponse &other) const 1379 { 1380 try { 1381 const Capability &cap = dynamic_cast<const Capability &>(other); 1382 return capabilities == cap.capabilities; 1383 } catch (std::bad_cast &) { 1384 return false; 1385 } 1386 } 1387 1388 bool NumberResponse::eq(const AbstractResponse &other) const 1389 { 1390 try { 1391 const NumberResponse &num = dynamic_cast<const NumberResponse &>(other); 1392 return kind == num.kind && number == num.number; 1393 } catch (std::bad_cast &) { 1394 return false; 1395 } 1396 } 1397 1398 bool State::eq(const AbstractResponse &other) const 1399 { 1400 try { 1401 const State &s = dynamic_cast<const State &>(other); 1402 if (kind == s.kind && tag == s.tag && message == s.message && respCode == s.respCode) 1403 if (respCode == NONE) 1404 return true; 1405 else 1406 return *respCodeData == *s.respCodeData; 1407 else 1408 return false; 1409 } catch (std::bad_cast &) { 1410 return false; 1411 } 1412 } 1413 1414 bool List::eq(const AbstractResponse &other) const 1415 { 1416 try { 1417 const List &r = dynamic_cast<const List &>(other); 1418 return kind == r.kind && mailbox == r.mailbox && flags == r.flags && separator == r.separator && extendedData == r.extendedData; 1419 } catch (std::bad_cast &) { 1420 return false; 1421 } 1422 } 1423 1424 bool Flags::eq(const AbstractResponse &other) const 1425 { 1426 try { 1427 const Flags &fl = dynamic_cast<const Flags &>(other); 1428 return flags == fl.flags; 1429 } catch (std::bad_cast &) { 1430 return false; 1431 } 1432 } 1433 1434 bool Search::eq(const AbstractResponse &other) const 1435 { 1436 try { 1437 const Search &s = dynamic_cast<const Search &>(other); 1438 return items == s.items; 1439 } catch (std::bad_cast &) { 1440 return false; 1441 } 1442 } 1443 1444 bool ESearch::eq(const AbstractResponse &other) const 1445 { 1446 try { 1447 const ESearch &s = dynamic_cast<const ESearch &>(other); 1448 return tag == s.tag && seqOrUids == s.seqOrUids && listData == s.listData && 1449 incrementalContextData == s.incrementalContextData && incThreadData == s.incThreadData; 1450 } catch (std::bad_cast &) { 1451 return false; 1452 } 1453 } 1454 1455 bool Status::eq(const AbstractResponse &other) const 1456 { 1457 try { 1458 const Status &s = dynamic_cast<const Status &>(other); 1459 return mailbox == s.mailbox && states == s.states; 1460 } catch (std::bad_cast &) { 1461 return false; 1462 } 1463 } 1464 1465 bool Fetch::eq(const AbstractResponse &other) const 1466 { 1467 try { 1468 const Fetch &f = dynamic_cast<const Fetch &>(other); 1469 if (number != f.number) 1470 return false; 1471 if (data.keys() != f.data.keys()) 1472 return false; 1473 for (dataType::const_iterator it = data.begin(); 1474 it != data.end(); ++it) 1475 if (!f.data.contains(it.key()) || *it.value() != *f.data[ it.key() ]) 1476 return false; 1477 return true; 1478 } catch (std::bad_cast &) { 1479 return false; 1480 } 1481 } 1482 1483 bool Namespace::eq(const AbstractResponse &otherResp) const 1484 { 1485 try { 1486 const Namespace &ns = dynamic_cast<const Namespace &>(otherResp); 1487 return ns.personal == personal && ns.users == users && ns.other == other; 1488 } catch (std::bad_cast &) { 1489 return false; 1490 } 1491 } 1492 1493 bool Sort::eq(const AbstractResponse &other) const 1494 { 1495 try { 1496 const Sort &s = dynamic_cast<const Sort &>(other); 1497 return numbers == s.numbers; 1498 } catch (std::bad_cast &) { 1499 return false; 1500 } 1501 } 1502 1503 bool Thread::eq(const AbstractResponse &other) const 1504 { 1505 try { 1506 const Thread &t = dynamic_cast<const Thread &>(other); 1507 return rootItems == t.rootItems; 1508 } catch (std::bad_cast &) { 1509 return false; 1510 } 1511 } 1512 1513 bool Id::eq(const AbstractResponse &other) const 1514 { 1515 try { 1516 const Id &r = dynamic_cast<const Id &>(other); 1517 return data == r.data; 1518 } catch (std::bad_cast &) { 1519 return false; 1520 } 1521 } 1522 1523 bool Enabled::eq(const AbstractResponse &other) const 1524 { 1525 try { 1526 const Enabled &r = dynamic_cast<const Enabled &>(other); 1527 return extensions == r.extensions; 1528 } catch (std::bad_cast &) { 1529 return false; 1530 } 1531 } 1532 1533 bool Vanished::eq(const AbstractResponse &other) const 1534 { 1535 try { 1536 const Vanished &r = dynamic_cast<const Vanished &>(other); 1537 return earlier == r.earlier && uids == r.uids; 1538 } catch (std::bad_cast &) { 1539 return false; 1540 } 1541 } 1542 1543 bool GenUrlAuth::eq(const AbstractResponse &other) const 1544 { 1545 try { 1546 const GenUrlAuth &r = dynamic_cast<const GenUrlAuth &>(other); 1547 return url == r.url; 1548 } catch (std::bad_cast &) { 1549 return false; 1550 } 1551 } 1552 1553 bool SocketEncryptedResponse::eq(const AbstractResponse &other) const 1554 { 1555 try { 1556 const SocketEncryptedResponse &r = dynamic_cast<const SocketEncryptedResponse &>(other); 1557 return sslChain == r.sslChain && sslErrors == r.sslErrors; 1558 } catch (std::bad_cast &) { 1559 return false; 1560 } 1561 } 1562 1563 bool SocketDisconnectedResponse::eq(const AbstractResponse &other) const 1564 { 1565 try { 1566 const SocketDisconnectedResponse &r = dynamic_cast<const SocketDisconnectedResponse &>(other); 1567 return message == r.message; 1568 } catch (std::bad_cast &) { 1569 return false; 1570 } 1571 } 1572 1573 ParseErrorResponse::ParseErrorResponse(const ImapException &e): 1574 message(QString::fromUtf8(e.msg().c_str())), exceptionClass(QString::fromUtf8(e.exceptionClass().c_str())), line(e.line()), offset(e.offset()) 1575 { 1576 } 1577 1578 bool ParseErrorResponse::eq(const AbstractResponse &other) const 1579 { 1580 try { 1581 const ParseErrorResponse &r = dynamic_cast<const ParseErrorResponse &>(other); 1582 return message == r.message && exceptionClass == r.exceptionClass && line == r.line && offset == r.offset; 1583 } catch (std::bad_cast &) { 1584 return false; 1585 } 1586 } 1587 1588 bool NamespaceData::operator==(const NamespaceData &other) const 1589 { 1590 return separator == other.separator && prefix == other.prefix; 1591 } 1592 1593 bool NamespaceData::operator!=(const NamespaceData &other) const 1594 { 1595 return !(*this == other); 1596 } 1597 1598 #define PLUG(X) void X::plug( Imap::Parser* parser, Imap::Mailbox::Model* model ) const \ 1599 { model->handle##X( parser, this ); } \ 1600 bool X::plug( Imap::Mailbox::ImapTask* task ) const \ 1601 { return task->handle##X( this ); } 1602 1603 PLUG(State) 1604 PLUG(Capability) 1605 PLUG(NumberResponse) 1606 PLUG(List) 1607 PLUG(Flags) 1608 PLUG(Search) 1609 PLUG(ESearch) 1610 PLUG(Status) 1611 PLUG(Fetch) 1612 PLUG(Namespace) 1613 PLUG(Sort) 1614 PLUG(Thread) 1615 PLUG(Id) 1616 PLUG(Enabled) 1617 PLUG(Vanished) 1618 PLUG(GenUrlAuth) 1619 PLUG(SocketEncryptedResponse) 1620 PLUG(SocketDisconnectedResponse) 1621 PLUG(ParseErrorResponse) 1622 1623 #undef PLUG 1624 1625 1626 } 1627 } 1628