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