File indexing completed on 2024-09-22 04:52:50

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 <algorithm>
0023 #include <QDebug>
0024 #include <QStringList>
0025 #include <QMutexLocker>
0026 #include <QProcess>
0027 #include <QSslError>
0028 #include <QTime>
0029 #include <QTimer>
0030 #include "Parser.h"
0031 #include "Imap/Encoders.h"
0032 #include "LowLevelParser.h"
0033 #include "../../Streams/IODeviceSocket.h"
0034 #include "../Model/Utils.h"
0035 
0036 //#define PRINT_TRAFFIC 100
0037 //#define PRINT_TRAFFIC_TX 500
0038 //#define PRINT_TRAFFIC_RX 25
0039 //#define PRINT_TRAFFIC_SENSITIVE
0040 
0041 #ifdef PRINT_TRAFFIC
0042 # ifndef PRINT_TRAFFIC_TX
0043 #  define PRINT_TRAFFIC_TX PRINT_TRAFFIC
0044 # endif
0045 # ifndef PRINT_TRAFFIC_RX
0046 #  define PRINT_TRAFFIC_RX PRINT_TRAFFIC
0047 # endif
0048 #endif
0049 
0050 /*
0051  * Parser interface considerations:
0052  *
0053  * - Parser receives comments and gives back some kind of ID for tracking the
0054  *   command state
0055  * - "High-level stuff" like "has this command already finished" should be
0056  *   implemented on higher level
0057  * - Due to command pipelining, there's no way to find out that this untagged
0058  *   reply we just received was triggered by FOO command
0059  *
0060  * Interface:
0061  *
0062  * - One function per command
0063  * - Each received reply emits a signal (Qt-specific stuff now)
0064  *
0065  *
0066  * Usage example FIXME DRAFT:
0067  *
0068  *  Imap::Parser parser;
0069  *  Imap::CommandHandle res = parser.deleteFolder( "foo mailbox/bar/baz" );
0070  *
0071  *
0072  *
0073  * How it works under the hood:
0074  *
0075  * - When there are any data available on the net, process them ASAP
0076  * - When user queues a command, process it ASAP
0077  * - You can't block the caller of the queueCommand()
0078  *
0079  * So, how to implement this?
0080  *
0081  * - Whenever something interesting happens (data/command/exit
0082  *   requested/available), we ask the worker thread to do something
0083  *
0084  * */
0085 
0086 namespace Imap
0087 {
0088 
0089 Parser::Parser(QObject *parent, Streams::Socket *socket, const uint myId):
0090     QObject(parent), socket(socket), m_lastTagUsed(0), idling(false), waitForInitialIdle(false),
0091     m_literalPlus(LiteralPlus::Unsupported), waitingForContinuation(false), startTlsInProgress(false), compressDeflateInProgress(false),
0092     waitingForConnection(true), waitingForEncryption(socket->isConnectingEncryptedSinceStart()), waitingForSslPolicy(false),
0093     m_expectsInitialGreeting(true), readingMode(ReadingLine), oldLiteralPosition(0), m_parserId(myId)
0094 {
0095     socket->setParent(this);
0096     connect(socket, &Streams::Socket::disconnected, this, &Parser::handleDisconnected);
0097     connect(socket, &Streams::Socket::readyRead, this, &Parser::handleReadyRead);
0098     connect(socket, &Streams::Socket::stateChanged, this, &Parser::slotSocketStateChanged);
0099     connect(socket, &Streams::Socket::encrypted, this, &Parser::handleSocketEncrypted);
0100 }
0101 
0102 CommandHandle Parser::noop()
0103 {
0104     return queueCommand(Commands::ATOM, "NOOP");
0105 }
0106 
0107 CommandHandle Parser::logout()
0108 {
0109     return queueCommand(Commands::Command("LOGOUT"));
0110 
0111     // Queue a request for closing the socket. It'll get closed after a short while.
0112     QTimer::singleShot(1000, this, SLOT(closeConnection()));
0113 }
0114 
0115 /** @short Close the underlying conneciton */
0116 void Parser::closeConnection()
0117 {
0118     socket->close();
0119 }
0120 
0121 CommandHandle Parser::capability()
0122 {
0123     // CAPABILITY should take precedence over LOGIN, because we have to check for LOGINDISABLED
0124     return queueCommand(Commands::Command() <<
0125                         Commands::PartOfCommand(Commands::ATOM, "CAPABILITY"));
0126 }
0127 
0128 CommandHandle Parser::startTls()
0129 {
0130     return queueCommand(Commands::Command() <<
0131                         Commands::PartOfCommand(Commands::STARTTLS, "STARTTLS"));
0132 }
0133 
0134 CommandHandle Parser::compressDeflate()
0135 {
0136     return queueCommand(Commands::Command() <<
0137                         Commands::PartOfCommand(Commands::COMPRESS_DEFLATE, "COMPRESS DEFLATE"));
0138 }
0139 
0140 #if 0
0141 CommandHandle Parser::authenticate(/*Authenticator FIXME*/)
0142 {
0143     // FIXME: needs higher priority
0144     return queueCommand(Commands::ATOM, "AUTHENTICATE");
0145 }
0146 #endif
0147 
0148 CommandHandle Parser::login(const QString &username, const QString &password)
0149 {
0150     return queueCommand(Commands::Command("LOGIN") <<
0151                         Commands::PartOfCommand(username.toUtf8()) << Commands::PartOfCommand(password.toUtf8()));
0152 }
0153 
0154 CommandHandle Parser::select(const QString &mailbox, const QList<QByteArray> &params)
0155 {
0156     Commands::Command cmd = Commands::Command("SELECT") << encodeImapFolderName(mailbox);
0157     if (!params.isEmpty()) {
0158         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
0159         Q_FOREACH(const QByteArray &param, params) {
0160             cmd << Commands::PartOfCommand(Commands::ATOM, param);
0161         }
0162         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0163     }
0164     return queueCommand(cmd);
0165 }
0166 
0167 CommandHandle Parser::selectQresync(const QString &mailbox, const uint uidValidity,
0168                                     const quint64 highestModSeq, const Sequence &knownUids, const Sequence &sequenceSnapshot,
0169                                     const Sequence &uidSnapshot)
0170 {
0171     Commands::Command cmd = Commands::Command("SELECT") << encodeImapFolderName(mailbox) <<
0172            Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (QRESYNC (") <<
0173            Commands::PartOfCommand(Commands::ATOM, QByteArray::number(uidValidity)) <<
0174            Commands::PartOfCommand(Commands::ATOM, QByteArray::number(highestModSeq));
0175     if (knownUids.isValid()) {
0176         cmd << Commands::PartOfCommand(Commands::ATOM, knownUids.toByteArray());
0177     }
0178     Q_ASSERT(uidSnapshot.isValid() == sequenceSnapshot.isValid());
0179     if (sequenceSnapshot.isValid()) {
0180         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (") <<
0181                Commands::PartOfCommand(Commands::ATOM, sequenceSnapshot.toByteArray()) <<
0182                Commands::PartOfCommand(Commands::ATOM, uidSnapshot.toByteArray()) <<
0183                Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")))");
0184     } else {
0185         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, "))");
0186     }
0187     return queueCommand(cmd);
0188 }
0189 
0190 CommandHandle Parser::examine(const QString &mailbox, const QList<QByteArray> &params)
0191 {
0192     Commands::Command cmd = Commands::Command("EXAMINE") << encodeImapFolderName(mailbox);
0193     if (!params.isEmpty()) {
0194         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
0195         Q_FOREACH(const QByteArray &param, params) {
0196             cmd << Commands::PartOfCommand(Commands::ATOM, param);
0197         }
0198         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0199     }
0200     return queueCommand(cmd);
0201 }
0202 
0203 CommandHandle Parser::deleteMailbox(const QString &mailbox)
0204 {
0205     return queueCommand(Commands::Command("DELETE") << encodeImapFolderName(mailbox));
0206 }
0207 
0208 CommandHandle Parser::create(const QString &mailbox)
0209 {
0210     return queueCommand(Commands::Command("CREATE") << encodeImapFolderName(mailbox));
0211 }
0212 
0213 CommandHandle Parser::rename(const QString &oldName, const QString &newName)
0214 {
0215     return queueCommand(Commands::Command("RENAME") <<
0216                         encodeImapFolderName(oldName) <<
0217                         encodeImapFolderName(newName));
0218 }
0219 
0220 CommandHandle Parser::subscribe(const QString &mailbox)
0221 {
0222     return queueCommand(Commands::Command("SUBSCRIBE") << encodeImapFolderName(mailbox));
0223 }
0224 
0225 CommandHandle Parser::unSubscribe(const QString &mailbox)
0226 {
0227     return queueCommand(Commands::Command("UNSUBSCRIBE") << encodeImapFolderName(mailbox));
0228 }
0229 
0230 CommandHandle Parser::list(const QString &reference, const QString &mailbox, const QStringList &returnOptions)
0231 {
0232     Commands::Command cmd("LIST");
0233     cmd << reference.toUtf8() << encodeImapFolderName(mailbox);
0234     if (!returnOptions.isEmpty()) {
0235         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " RETURN (");
0236         Q_FOREACH(const QString &option, returnOptions) {
0237             cmd << Commands::PartOfCommand(Commands::ATOM, option.toUtf8());
0238         }
0239         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0240     }
0241     return queueCommand(cmd);
0242 }
0243 
0244 CommandHandle Parser::lSub(const QString &reference, const QString &mailbox)
0245 {
0246     return queueCommand(Commands::Command("LSUB") << reference.toUtf8() << encodeImapFolderName(mailbox));
0247 }
0248 
0249 CommandHandle Parser::status(const QString &mailbox, const QStringList &fields)
0250 {
0251     return queueCommand(Commands::Command("STATUS") << encodeImapFolderName(mailbox) <<
0252                         Commands::PartOfCommand(Commands::ATOM, "(" + fields.join(QStringLiteral(" ")).toUtf8() + ")")
0253                        );
0254 }
0255 
0256 CommandHandle Parser::append(const QString &mailbox, const QByteArray &message, const QStringList &flags, const QDateTime &timestamp)
0257 {
0258     Commands::Command command("APPEND");
0259     command << encodeImapFolderName(mailbox);
0260     if (flags.count())
0261         command << Commands::PartOfCommand(Commands::ATOM, "(" + flags.join(QStringLiteral(" ")).toUtf8() + ")");
0262     if (timestamp.isValid())
0263         command << Commands::PartOfCommand(Imap::dateTimeToInternalDate(timestamp).toUtf8());
0264     command << Commands::PartOfCommand(Commands::LITERAL, message);
0265 
0266     return queueCommand(command);
0267 }
0268 
0269 CommandHandle Parser::appendCatenate(const QString &mailbox, const QList<Imap::Mailbox::CatenatePair> &data,
0270                                      const QStringList &flags, const QDateTime &timestamp)
0271 {
0272     Commands::Command command("APPEND");
0273     command << encodeImapFolderName(mailbox);
0274     if (flags.count())
0275         command << Commands::PartOfCommand(Commands::ATOM, "(" + flags.join(QStringLiteral(" ")).toUtf8() + ")");
0276     if (timestamp.isValid())
0277         command << Commands::PartOfCommand(Imap::dateTimeToInternalDate(timestamp).toUtf8());
0278     command << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " CATENATE (");
0279     Q_FOREACH(const Imap::Mailbox::CatenatePair &item, data) {
0280         switch (item.first) {
0281         case Imap::Mailbox::CATENATE_TEXT:
0282             command << Commands::PartOfCommand(Commands::ATOM, "TEXT");
0283             command << Commands::PartOfCommand(Commands::LITERAL, item.second);
0284             break;
0285         case Imap::Mailbox::CATENATE_URL:
0286             command << Commands::PartOfCommand(Commands::ATOM, "URL");
0287             command << Commands::PartOfCommand(item.second);
0288             break;
0289         }
0290     }
0291     command << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0292 
0293     return queueCommand(command);
0294 }
0295 
0296 CommandHandle Parser::check()
0297 {
0298     return queueCommand(Commands::ATOM, "CHECK");
0299 }
0300 
0301 CommandHandle Parser::close()
0302 {
0303     return queueCommand(Commands::ATOM, "CLOSE");
0304 }
0305 
0306 CommandHandle Parser::expunge()
0307 {
0308     return queueCommand(Commands::ATOM, "EXPUNGE");
0309 }
0310 
0311 CommandHandle Parser::searchHelper(const QByteArray &command, const QStringList &criteria, const QByteArray &charset)
0312 {
0313     Commands::Command cmd(command);
0314 
0315     if (!charset.isEmpty())
0316         cmd << "CHARSET" << charset;
0317 
0318     // FIXME: we don't really support anything else but utf-8 here
0319 
0320     if (criteria.size() == 1) {
0321         // Hack: if it's just a single item, let's assume it's already well-formatted by the caller.
0322         // This is required in the current shape of the API if we want to allow the user to type in their queries directly.
0323         cmd << Commands::PartOfCommand(Commands::ATOM, criteria.front().toUtf8());
0324     } else {
0325         for (QStringList::const_iterator it = criteria.begin(); it != criteria.end(); ++it)
0326             cmd << it->toUtf8();
0327     }
0328 
0329     return queueCommand(cmd);
0330 }
0331 
0332 CommandHandle Parser::uidSearchUid(const QByteArray &sequence)
0333 {
0334     Commands::Command command("UID SEARCH");
0335     command << Commands::PartOfCommand(Commands::ATOM, sequence);
0336     return queueCommand(command);
0337 }
0338 
0339 CommandHandle Parser::uidESearchUid(const QByteArray &sequence)
0340 {
0341     Commands::Command command("UID SEARCH RETURN (ALL)");
0342     command << Commands::PartOfCommand(Commands::ATOM, sequence);
0343     return queueCommand(command);
0344 }
0345 
0346 CommandHandle Parser::sortHelper(const QByteArray &command, const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
0347 {
0348     Q_ASSERT(! sortCriteria.isEmpty());
0349     Commands::Command cmd;
0350 
0351     cmd << Commands::PartOfCommand(Commands::ATOM, command) <<
0352         Commands::PartOfCommand(Commands::ATOM, "(" + sortCriteria.join(QStringLiteral(" ")).toUtf8() + ")" ) <<
0353         charset;
0354 
0355     if (searchCriteria.size() == 1) {
0356         // Hack: if it's just a single item, let's assume it's already well-formatted by the caller.
0357         // This is required in the current shape of the API if we want to allow the user to type in their queries directly.
0358         cmd << Commands::PartOfCommand(Commands::ATOM, searchCriteria.front().toUtf8());
0359     } else {
0360         for (QStringList::const_iterator it = searchCriteria.begin(); it != searchCriteria.end(); ++it)
0361             cmd << it->toUtf8();
0362     }
0363 
0364     return queueCommand(cmd);
0365 }
0366 
0367 CommandHandle Parser::sort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
0368 {
0369     return sortHelper("SORT", sortCriteria, charset, searchCriteria);
0370 }
0371 
0372 CommandHandle Parser::uidSort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
0373 {
0374     return sortHelper("UID SORT", sortCriteria, charset, searchCriteria);
0375 }
0376 
0377 CommandHandle Parser::uidESort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria,
0378                                const QStringList &returnOptions)
0379 {
0380     return sortHelper("UID SORT RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
0381                       sortCriteria, charset, searchCriteria);
0382 }
0383 
0384 CommandHandle Parser::uidESearch(const QByteArray &charset, const QStringList &searchCriteria, const QStringList &returnOptions)
0385 {
0386     return searchHelper("UID SEARCH RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
0387                         searchCriteria, charset);
0388 }
0389 
0390 CommandHandle Parser::cancelUpdate(const CommandHandle &tag)
0391 {
0392     Commands::Command command("CANCELUPDATE");
0393     command << Commands::PartOfCommand(Commands::QUOTED_STRING, tag);
0394     return queueCommand(command);
0395 }
0396 
0397 CommandHandle Parser::threadHelper(const QByteArray &command, const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
0398 {
0399     Commands::Command cmd;
0400 
0401     cmd << Commands::PartOfCommand(Commands::ATOM, command) << algo << charset;
0402 
0403     for (QStringList::const_iterator it = searchCriteria.begin(); it != searchCriteria.end(); ++it) {
0404         // FIXME: this is another place which needs proper structure for this searching stuff...
0405         cmd << Commands::PartOfCommand(Commands::ATOM, it->toUtf8());
0406     }
0407 
0408     return queueCommand(cmd);
0409 }
0410 
0411 CommandHandle Parser::thread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
0412 {
0413     return threadHelper("THREAD", algo, charset, searchCriteria);
0414 }
0415 
0416 CommandHandle Parser::uidThread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
0417 {
0418     return threadHelper("UID THREAD", algo, charset, searchCriteria);
0419 }
0420 
0421 CommandHandle Parser::uidEThread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria,
0422                                  const QStringList &returnOptions)
0423 {
0424     return threadHelper("UID THREAD RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
0425                         algo, charset, searchCriteria);
0426 }
0427 
0428 CommandHandle Parser::fetch(const Sequence &seq, const QStringList &items, const QMap<QByteArray, quint64> &uint64Modifiers)
0429 {
0430     Commands::Command cmd = Commands::Command("FETCH") <<
0431                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0432                         Commands::PartOfCommand(Commands::ATOM, '(' + items.join(QStringLiteral(" ")).toUtf8() + ')');
0433     if (!uint64Modifiers.isEmpty()) {
0434         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
0435         for (QMap<QByteArray, quint64>::const_iterator it = uint64Modifiers.constBegin(); it != uint64Modifiers.constEnd(); ++it) {
0436             cmd << Commands::PartOfCommand(Commands::ATOM, it.key()) <<
0437                    Commands::PartOfCommand(Commands::ATOM, QByteArray::number(it.value()));
0438         }
0439         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0440     }
0441     return queueCommand(cmd);
0442 }
0443 
0444 CommandHandle Parser::store(const Sequence &seq, const QString &item, const QString &value)
0445 {
0446     return queueCommand(Commands::Command("STORE") <<
0447                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0448                         Commands::PartOfCommand(Commands::ATOM, item.toUtf8()) <<
0449                         Commands::PartOfCommand(Commands::ATOM, value.toUtf8())
0450                        );
0451 }
0452 
0453 CommandHandle Parser::copy(const Sequence &seq, const QString &mailbox)
0454 {
0455     return queueCommand(Commands::Command("COPY") <<
0456                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0457                         encodeImapFolderName(mailbox));
0458 }
0459 
0460 CommandHandle Parser::uidFetch(const Sequence &seq, const QList<QByteArray> &items)
0461 {
0462     QByteArray buf;
0463     Q_FOREACH(const QByteArray &item, items) {
0464         buf += ' ' + item;
0465     }
0466     buf += ')';
0467     buf[0] = '(';
0468     return queueCommand(Commands::Command("UID FETCH") <<
0469                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0470                         Commands::PartOfCommand(Commands::ATOM, buf));
0471 }
0472 
0473 CommandHandle Parser::uidStore(const Sequence &seq, const QString &item, const QString &value)
0474 {
0475     return queueCommand(Commands::Command("UID STORE") <<
0476                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0477                         Commands::PartOfCommand(Commands::ATOM, item.toUtf8()) <<
0478                         Commands::PartOfCommand(Commands::ATOM, value.toUtf8()));
0479 }
0480 
0481 CommandHandle Parser::uidCopy(const Sequence &seq, const QString &mailbox)
0482 {
0483     return queueCommand(Commands::Command("UID COPY") <<
0484                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0485                         encodeImapFolderName(mailbox));
0486 }
0487 
0488 CommandHandle Parser::uidMove(const Sequence &seq, const QString &mailbox)
0489 {
0490     return queueCommand(Commands::Command("UID MOVE") <<
0491                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
0492                         encodeImapFolderName(mailbox));
0493 }
0494 
0495 CommandHandle Parser::uidExpunge(const Sequence &seq)
0496 {
0497     return queueCommand(Commands::Command("UID EXPUNGE") <<
0498                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()));
0499 }
0500 
0501 CommandHandle Parser::xAtom(const Commands::Command &cmd)
0502 {
0503     return queueCommand(cmd);
0504 }
0505 
0506 CommandHandle Parser::unSelect()
0507 {
0508     return queueCommand(Commands::ATOM, "UNSELECT");
0509 }
0510 
0511 CommandHandle Parser::idle()
0512 {
0513     return queueCommand(Commands::IDLE, "IDLE");
0514 }
0515 
0516 void Parser::idleDone()
0517 {
0518     // This is not a new "command", so we don't go via queueCommand()
0519     // which would allocate a new tag for us, but submit directly
0520     Commands::Command cmd;
0521     cmd << Commands::PartOfCommand(Commands::IDLE_DONE, "DONE");
0522     cmdQueue.push_back(cmd);
0523     QTimer::singleShot(0, this, SLOT(executeCommands()));
0524 }
0525 
0526 void Parser::idleContinuationWontCome()
0527 {
0528     Q_ASSERT(waitForInitialIdle);
0529     waitForInitialIdle = false;
0530     idling = false;
0531     QTimer::singleShot(0, this, SLOT(executeCommands()));
0532 }
0533 
0534 void Parser::idleMagicallyTerminatedByServer()
0535 {
0536     Q_ASSERT(! waitForInitialIdle);
0537     Q_ASSERT(idling);
0538     idling = false;
0539 }
0540 
0541 CommandHandle Parser::namespaceCommand()
0542 {
0543     return queueCommand(Commands::ATOM, "NAMESPACE");
0544 }
0545 
0546 CommandHandle Parser::idCommand()
0547 {
0548     return queueCommand(Commands::Command("ID NIL"));
0549 }
0550 
0551 CommandHandle Parser::idCommand(const QMap<QByteArray,QByteArray> &args)
0552 {
0553     Commands::Command cmd("ID ");
0554     cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, "(");
0555     for (QMap<QByteArray,QByteArray>::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) {
0556         cmd << Commands::PartOfCommand(Commands::QUOTED_STRING, it.key()) << Commands::PartOfCommand(Commands::QUOTED_STRING, it.value());
0557     }
0558     cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0559     return queueCommand(cmd);
0560 }
0561 
0562 CommandHandle Parser::enable(const QList<QByteArray> &extensions)
0563 {
0564     Commands::Command cmd("ENABLE");
0565     Q_FOREACH(const QByteArray &item, extensions) {
0566         cmd << Commands::PartOfCommand(Commands::ATOM, item);
0567     }
0568     return queueCommand(cmd);
0569 }
0570 
0571 CommandHandle Parser::genUrlAuth(const QByteArray &url, const QByteArray mechanism)
0572 {
0573     Commands::Command cmd("GENURLAUTH");
0574     cmd << Commands::PartOfCommand(Commands::QUOTED_STRING, url);
0575     cmd << Commands::PartOfCommand(Commands::ATOM, mechanism);
0576     return queueCommand(cmd);
0577 }
0578 
0579 CommandHandle Parser::uidSendmail(const uint uid, const Mailbox::UidSubmitOptionsList &submissionOptions)
0580 {
0581     Commands::Command cmd("UID SENDMAIL");
0582     cmd << Commands::PartOfCommand(QByteArray::number(uid));
0583     if (!submissionOptions.isEmpty()) {
0584         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
0585         for (Mailbox::UidSubmitOptionsList::const_iterator it = submissionOptions.begin(); it != submissionOptions.end(); ++it) {
0586             cmd << Commands::PartOfCommand(Commands::ATOM, it->first);
0587             switch (it->second.type()) {
0588             case QVariant::ByteArray:
0589                 cmd << Commands::PartOfCommand(it->second.toByteArray());
0590                 break;
0591             case QVariant::List:
0592                 cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
0593                 Q_FOREACH(const QVariant &item, it->second.toList()) {
0594                     cmd << Commands::PartOfCommand(Commands::ATOM, item.toByteArray());
0595                 }
0596                 cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0597                 break;
0598             case QVariant::Invalid:
0599                 cmd << Commands::PartOfCommand(Commands::ATOM, "NIL");
0600                 break;
0601             default:
0602                 throw InvalidArgument("Internal error: Malformed data for the UID SEND command.");
0603             }
0604         }
0605         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
0606     }
0607     return queueCommand(cmd);
0608 }
0609 
0610 CommandHandle Parser::queueCommand(Commands::Command command)
0611 {
0612     CommandHandle tag = generateTag();
0613     command.addTag(tag);
0614     cmdQueue.push_back(command);
0615     QTimer::singleShot(0, this, SLOT(executeCommands()));
0616     return tag;
0617 }
0618 
0619 void Parser::queueResponse(const QSharedPointer<Responses::AbstractResponse> &resp)
0620 {
0621     respQueue.push_back(resp);
0622     // Try to limit the signal rate -- when there are multiple items in the queue, there's no point in sending more signals
0623     if (respQueue.size() == 1) {
0624         emit responseReceived(this);
0625     }
0626 
0627     if (waitingForContinuation) {
0628         // Check whether this is the server's way of informing us that the continuation request is not going to arrive
0629         QSharedPointer<Responses::State> stateResponse = resp.dynamicCast<Responses::State>();
0630         Q_ASSERT(!literalCommandTag.isEmpty());
0631         if (stateResponse && stateResponse->tag == literalCommandTag) {
0632             literalCommandTag.clear();
0633             waitingForContinuation = false;
0634             cmdQueue.pop_front();
0635             QTimer::singleShot(0, this, SLOT(executeCommands()));
0636             if (stateResponse->kind != Responses::NO && stateResponse->kind != Responses::BAD) {
0637                 // FIXME: use parserWarning when it's adapted throughout the code
0638                 qDebug() << "Synchronized literal rejected but response is neither NO nor BAD";
0639             }
0640         }
0641     }
0642 }
0643 
0644 bool Parser::hasResponse() const
0645 {
0646     return ! respQueue.empty();
0647 }
0648 
0649 QSharedPointer<Responses::AbstractResponse> Parser::getResponse()
0650 {
0651     QSharedPointer<Responses::AbstractResponse> ptr;
0652     if (respQueue.empty())
0653         return ptr;
0654     ptr = respQueue.front();
0655     respQueue.pop_front();
0656     return ptr;
0657 }
0658 
0659 QByteArray Parser::generateTag()
0660 {
0661     return QStringLiteral("y%1").arg(m_lastTagUsed++).toUtf8();
0662 }
0663 
0664 void Parser::handleReadyRead()
0665 {
0666     while (!waitingForEncryption && !waitingForSslPolicy) {
0667         switch (readingMode) {
0668         case ReadingLine:
0669             if (socket->canReadLine()) {
0670                 reallyReadLine();
0671             } else {
0672                 // Not enough data yet, let's try again later
0673                 return;
0674             }
0675             break;
0676         case ReadingNumberOfBytes:
0677         {
0678             QByteArray buf = socket->read(readingBytes);
0679             readingBytes -= buf.size();
0680             currentLine += buf;
0681             if (readingBytes == 0) {
0682                 // we've read the literal
0683                 readingMode = ReadingLine;
0684             } else {
0685                 return;
0686             }
0687         }
0688         break;
0689         }
0690     }
0691 }
0692 
0693 void Parser::reallyReadLine()
0694 {
0695     try {
0696         currentLine += socket->readLine();
0697         if (currentLine.endsWith("}\r\n")) {
0698             int offset = currentLine.lastIndexOf('{');
0699             if (offset < oldLiteralPosition)
0700                 throw ParseError("Got unmatched '}'", currentLine, currentLine.size() - 3);
0701             bool ok;
0702             int number = currentLine.mid(offset + 1, currentLine.size() - offset - 4).toInt(&ok);
0703             if (!ok)
0704                 throw ParseError("Can't parse numeric literal size", currentLine, offset);
0705             if (number < 0)
0706                 throw ParseError("Negative literal size", currentLine, offset);
0707             oldLiteralPosition = offset;
0708             readingMode = ReadingNumberOfBytes;
0709             readingBytes = number;
0710         } else if (currentLine.endsWith("\r\n")) {
0711             // it's complete
0712             if (startTlsInProgress && currentLine.startsWith(startTlsCommand)) {
0713                 startTlsCommand.clear();
0714                 startTlsReply = currentLine;
0715                 currentLine.clear();
0716                 oldLiteralPosition = 0;
0717                 QTimer::singleShot(0, this, SLOT(finishStartTls()));
0718                 return;
0719             }
0720             processLine(currentLine);
0721             currentLine.clear();
0722             oldLiteralPosition = 0;
0723         } else {
0724             throw ParseError("Received line doesn't end with any of \"}\\r\\n\" and \"\\r\\n\"", currentLine, 0);
0725         }
0726     } catch (ParserException &e) {
0727         queueResponse(QSharedPointer<Responses::AbstractResponse>(new Responses::ParseErrorResponse(e)));
0728     }
0729 }
0730 
0731 void Parser::executeCommands()
0732 {
0733     while (! waitingForContinuation && ! waitForInitialIdle &&
0734            ! waitingForConnection && ! waitingForEncryption && ! waitingForSslPolicy &&
0735            ! cmdQueue.empty() && ! startTlsInProgress && !compressDeflateInProgress)
0736         executeACommand();
0737 }
0738 
0739 void Parser::finishStartTls()
0740 {
0741     emit lineSent(this, "*** STARTTLS");
0742 #ifdef PRINT_TRAFFIC_TX
0743     qDebug() << m_parserId << "*** STARTTLS";
0744 #endif
0745     cmdQueue.pop_front();
0746     socket->startTls(); // warn: this might invoke event loop
0747     startTlsInProgress = false;
0748     waitingForEncryption = true;
0749     processLine(startTlsReply);
0750 }
0751 
0752 void Parser::handleSocketEncrypted()
0753 {
0754     waitingForEncryption = false;
0755     waitingForConnection = false;
0756     waitingForSslPolicy = true;
0757     QSharedPointer<Responses::AbstractResponse> resp(
0758                 new Responses::SocketEncryptedResponse(socket->sslChain(), socket->sslErrors()));
0759     QByteArray buf;
0760     QTextStream ss(&buf);
0761     ss << "*** " << *resp;
0762     ss.flush();
0763 #ifdef PRINT_TRAFFIC_RX
0764     qDebug() << m_parserId << "***" << buf;
0765 #endif
0766     emit lineReceived(this, buf);
0767     handleReadyRead();
0768     queueResponse(resp);
0769     executeCommands();
0770 }
0771 
0772 /** @short We've previously frozen the command queue, so it's time to kick it a bit and keep the usual sending/receiving again */
0773 void Parser::handleCompressionPossibleActivated()
0774 {
0775     handleReadyRead();
0776     executeCommands();
0777 }
0778 
0779 void Parser::unfreezeAfterEncryption()
0780 {
0781     Q_ASSERT(waitingForSslPolicy);
0782     waitingForSslPolicy = false;
0783     handleReadyRead();
0784     executeCommands();
0785 }
0786 
0787 void Parser::executeACommand()
0788 {
0789     Q_ASSERT(! cmdQueue.empty());
0790     Commands::Command &cmd = cmdQueue.front();
0791 
0792     QByteArray buf;
0793 
0794     bool sensitiveCommand = (cmd.cmds.size() > 2 && cmd.cmds[1].text == "LOGIN");
0795     QByteArray privateMessage = sensitiveCommand ? QByteArray("[LOGIN command goes here]") : QByteArray();
0796 
0797 #ifdef PRINT_TRAFFIC_TX
0798 #ifdef PRINT_TRAFFIC_SENSITIVE
0799     bool printThisCommand = true;
0800 #else
0801     bool printThisCommand = ! sensitiveCommand;
0802 #endif
0803 #endif
0804 
0805     if (cmd.cmds[ cmd.currentPart ].kind == Commands::IDLE_DONE) {
0806         // Handling of the IDLE_DONE is a bit special, as we have to check and update the idling flag...
0807         Q_ASSERT(idling);
0808         buf.append("DONE\r\n");
0809 #ifdef PRINT_TRAFFIC_TX
0810         qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0811 #endif
0812         socket->write(buf);
0813         idling = false;
0814         cmdQueue.pop_front();
0815         emit lineSent(this, buf);
0816         buf.clear();
0817         return;
0818     }
0819 
0820     Q_ASSERT(! idling);
0821 
0822     while (1) {
0823         Commands::PartOfCommand &part = cmd.cmds[ cmd.currentPart ];
0824         switch (part.kind) {
0825         case Commands::ATOM:
0826         case Commands::ATOM_NO_SPACE_AROUND:
0827             buf.append(part.text);
0828             break;
0829         case Commands::QUOTED_STRING:
0830         {
0831             QByteArray item = part.text;
0832             item.replace('\\', "\\\\");
0833             buf.append('"');
0834             buf.append(item);
0835             buf.append('"');
0836         }
0837         break;
0838         case Commands::LITERAL:
0839             if (m_literalPlus == LiteralPlus::Plus || (m_literalPlus == LiteralPlus::Minus && part.text.size() <= 4096)) {
0840                 buf.append('{');
0841                 buf.append(QByteArray::number(part.text.size()));
0842                 buf.append("+}\r\n");
0843                 buf.append(part.text);
0844             } else if (part.numberSent) {
0845                 buf.append(part.text);
0846             } else {
0847                 buf.append('{');
0848                 buf.append(QByteArray::number(part.text.size()));
0849                 buf.append("}\r\n");
0850 #ifdef PRINT_TRAFFIC_TX
0851                 if (printThisCommand)
0852                     qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0853                 else
0854                     qDebug() << m_parserId << ">>> [sensitive command] -- added literal";
0855 #endif
0856                 socket->write(buf);
0857                 part.numberSent = true;
0858                 waitingForContinuation = true;
0859                 Q_ASSERT(literalCommandTag.isEmpty());
0860                 literalCommandTag = cmd.cmds.first().text;
0861                 Q_ASSERT(!literalCommandTag.isEmpty());
0862                 emit lineSent(this, sensitiveCommand ? privateMessage : buf);
0863                 return; // and wait for continuation request
0864             }
0865             break;
0866         case Commands::IDLE_DONE:
0867             Q_ASSERT(false); // is handled above
0868             break;
0869         case Commands::IDLE:
0870             buf.append("IDLE\r\n");
0871 #ifdef PRINT_TRAFFIC_TX
0872             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0873 #endif
0874             socket->write(buf);
0875             idling = true;
0876             waitForInitialIdle = true;
0877             cmdQueue.pop_front();
0878             emit lineSent(this, buf);
0879             return;
0880             break;
0881         case Commands::STARTTLS:
0882             startTlsCommand = buf;
0883             buf.append("STARTTLS\r\n");
0884 #ifdef PRINT_TRAFFIC_TX
0885             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0886 #endif
0887             socket->write(buf);
0888             startTlsInProgress = true;
0889             emit lineSent(this, buf);
0890             return;
0891             break;
0892         case Commands::COMPRESS_DEFLATE:
0893             compressDeflateCommand = buf;
0894             buf.append("COMPRESS DEFLATE\r\n");
0895 #ifdef PRINT_TRAFFIC_TX
0896             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0897 #endif
0898             socket->write(buf);
0899             compressDeflateInProgress = true;
0900             cmdQueue.pop_front();
0901             emit lineSent(this, buf);
0902             return;
0903             break;
0904         }
0905         if (cmd.currentPart == cmd.cmds.size() - 1) {
0906             // finalize
0907             buf.append("\r\n");
0908 #ifdef PRINT_TRAFFIC_TX
0909             if (printThisCommand)
0910                 qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
0911             else
0912                 qDebug() << m_parserId << ">>> [sensitive command]";
0913 #endif
0914             socket->write(buf);
0915             cmdQueue.pop_front();
0916             emit lineSent(this, sensitiveCommand ? privateMessage : buf);
0917             break;
0918         } else {
0919             if (part.kind == Commands::ATOM_NO_SPACE_AROUND || cmd.cmds[cmd.currentPart + 1].kind == Commands::ATOM_NO_SPACE_AROUND) {
0920                 // Skip the extra space if asked to do so
0921             } else {
0922                 buf.append(' ');
0923             }
0924             ++cmd.currentPart;
0925         }
0926     }
0927 }
0928 
0929 /** @short Process a line from IMAP server */
0930 void Parser::processLine(QByteArray line)
0931 {
0932 #ifdef PRINT_TRAFFIC_RX
0933     QByteArray debugLine = line.trimmed();
0934     if (debugLine.size() > PRINT_TRAFFIC_RX)
0935         qDebug() << m_parserId << "<<<" << debugLine.left(PRINT_TRAFFIC_RX) << "...";
0936     else
0937         qDebug() << m_parserId << "<<<" << debugLine;
0938 #endif
0939     emit lineReceived(this, line);
0940     if (m_expectsInitialGreeting && !line.startsWith("* ")) {
0941         throw NotAnImapServerError(std::string(), line, -1);
0942     } else if (line.startsWith("* ")) {
0943         m_expectsInitialGreeting = false;
0944         queueResponse(parseUntagged(line));
0945     } else if (line.startsWith("+ ")) {
0946         if (waitingForContinuation) {
0947             waitingForContinuation = false;
0948             literalCommandTag.clear();
0949             QTimer::singleShot(0, this, SLOT(executeCommands()));
0950         } else if (waitForInitialIdle) {
0951             waitForInitialIdle = false;
0952             QTimer::singleShot(0, this, SLOT(executeCommands()));
0953         } else {
0954             throw ContinuationRequest(line.constData());
0955         }
0956     } else {
0957         queueResponse(parseTagged(line));
0958     }
0959 }
0960 
0961 QSharedPointer<Responses::AbstractResponse> Parser::parseUntagged(const QByteArray &line)
0962 {
0963     int pos = 2;
0964     LowLevelParser::eatSpaces(line, pos);
0965     uint number;
0966     try {
0967         number = LowLevelParser::getUInt(line, pos);
0968         ++pos;
0969     } catch (ParseError &) {
0970         return parseUntaggedText(line, pos);
0971     }
0972     return parseUntaggedNumber(line, pos, number);
0973 }
0974 
0975 QSharedPointer<Responses::AbstractResponse> Parser::parseUntaggedNumber(
0976     const QByteArray &line, int &start, const uint number)
0977 {
0978     if (start == line.size())
0979         // number and nothing else
0980         throw NoData(line, start);
0981 
0982     QByteArray kindStr = LowLevelParser::getAtom(line, start);
0983     Responses::Kind kind;
0984     try {
0985         kind = Responses::kindFromString(kindStr);
0986     } catch (UnrecognizedResponseKind &e) {
0987         throw UnrecognizedResponseKind(e.what(), line, start);
0988     }
0989 
0990     switch (kind) {
0991     case Responses::EXISTS:
0992     case Responses::RECENT:
0993     case Responses::EXPUNGE:
0994         // no more data should follow
0995         if (start >= line.size())
0996             throw TooMuchData(line, start);
0997         else if (line.mid(start) != QByteArray("\r\n"))
0998             throw UnexpectedHere(line, start);   // expected CRLF
0999         else
1000             try {
1001                 return QSharedPointer<Responses::AbstractResponse>(
1002                            new Responses::NumberResponse(kind, number));
1003             } catch (UnexpectedHere &e) {
1004                 throw UnexpectedHere(e.what(), line, start);
1005             }
1006         break;
1007 
1008     case Responses::FETCH:
1009         return QSharedPointer<Responses::AbstractResponse>(
1010                    new Responses::Fetch(number, line, start));
1011         break;
1012 
1013     default:
1014         break;
1015     }
1016     throw UnexpectedHere(line, start);
1017 }
1018 
1019 QSharedPointer<Responses::AbstractResponse> Parser::parseUntaggedText(
1020     const QByteArray &line, int &start)
1021 {
1022     Responses::Kind kind;
1023     try {
1024         kind = Responses::kindFromString(LowLevelParser::getAtom(line, start));
1025     } catch (UnrecognizedResponseKind &e) {
1026         throw UnrecognizedResponseKind(e.what(), line, start);
1027     }
1028     ++start;
1029     if (start == line.size() && kind != Responses::SEARCH && kind != Responses::SORT)
1030         throw NoData(line, start);
1031     switch (kind) {
1032     case Responses::CAPABILITY:
1033     {
1034         QStringList capabilities;
1035         QList<QByteArray> list = line.mid(start).split(' ');
1036         for (QList<QByteArray>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) {
1037             QByteArray str = *it;
1038             if (str.endsWith("\r\n"))
1039                 str.chop(2);
1040             capabilities << QString::fromUtf8(str);
1041         }
1042         if (!capabilities.count())
1043             throw NoData(line, start);
1044         return QSharedPointer<Responses::AbstractResponse>(
1045                    new Responses::Capability(capabilities));
1046     }
1047     case Responses::OK:
1048     case Responses::NO:
1049     case Responses::BAD:
1050     case Responses::PREAUTH:
1051     case Responses::BYE:
1052         return QSharedPointer<Responses::AbstractResponse>(
1053                    new Responses::State(QByteArray(), kind, line, start));
1054     case Responses::LIST:
1055     case Responses::LSUB:
1056         return QSharedPointer<Responses::AbstractResponse>(
1057                    new Responses::List(kind, line, start));
1058     case Responses::FLAGS:
1059         return QSharedPointer<Responses::AbstractResponse>(
1060                    new Responses::Flags(line, start));
1061     case Responses::SEARCH:
1062         return QSharedPointer<Responses::AbstractResponse>(
1063                    new Responses::Search(line, start));
1064     case Responses::ESEARCH:
1065         return QSharedPointer<Responses::AbstractResponse>(
1066                    new Responses::ESearch(line, start));
1067     case Responses::STATUS:
1068         return QSharedPointer<Responses::AbstractResponse>(
1069                    new Responses::Status(line, start));
1070     case Responses::NAMESPACE:
1071         return QSharedPointer<Responses::AbstractResponse>(
1072                    new Responses::Namespace(line, start));
1073     case Responses::SORT:
1074         return QSharedPointer<Responses::AbstractResponse>(
1075                    new Responses::Sort(line, start));
1076     case Responses::THREAD:
1077         return QSharedPointer<Responses::AbstractResponse>(
1078                    new Responses::Thread(line, start));
1079     case Responses::ID:
1080         return QSharedPointer<Responses::AbstractResponse>(
1081                    new Responses::Id(line, start));
1082     case Responses::ENABLED:
1083         return QSharedPointer<Responses::AbstractResponse>(
1084                     new Responses::Enabled(line, start));
1085     case Responses::VANISHED:
1086         return QSharedPointer<Responses::AbstractResponse>(
1087                     new Responses::Vanished(line, start));
1088     case Responses::GENURLAUTH:
1089         return QSharedPointer<Responses::AbstractResponse>(
1090                     new Responses::GenUrlAuth(line, start));
1091 
1092 
1093         // Those already handled above follow here
1094     case Responses::EXPUNGE:
1095     case Responses::FETCH:
1096     case Responses::EXISTS:
1097     case Responses::RECENT:
1098         throw UnexpectedHere("Malformed response: the number should go first", line, start);
1099     }
1100     throw UnexpectedHere(line, start);
1101 }
1102 
1103 QSharedPointer<Responses::AbstractResponse> Parser::parseTagged(const QByteArray &line)
1104 {
1105     int pos = 0;
1106     const QByteArray tag = LowLevelParser::getAtom(line, pos);
1107     ++pos;
1108     const Responses::Kind kind = Responses::kindFromString(LowLevelParser::getAtom(line, pos));
1109     ++pos;
1110 
1111     if (compressDeflateInProgress && compressDeflateCommand == tag + ' ') {
1112         switch (kind) {
1113         case Responses::OK:
1114             socket->startDeflate();
1115             compressDeflateInProgress = false;
1116             compressDeflateCommand.clear();
1117             break;
1118         default:
1119             // do nothing
1120             break;
1121         }
1122         compressDeflateInProgress = false;
1123         compressDeflateCommand.clear();
1124         QTimer::singleShot(0, this, SLOT(handleCompressionPossibleActivated()));
1125     }
1126 
1127     return QSharedPointer<Responses::AbstractResponse>(
1128                new Responses::State(tag, kind, line, pos));
1129 }
1130 
1131 void Parser::enableLiteralPlus(const LiteralPlus mode)
1132 {
1133     m_literalPlus = mode;
1134 }
1135 
1136 void Parser::handleDisconnected(const QString &reason)
1137 {
1138     emit lineReceived(this, "*** Socket disconnected: " + reason.toUtf8());
1139 #ifdef PRINT_TRAFFIC_TX
1140     qDebug() << m_parserId << "*** Socket disconnected";
1141 #endif
1142     queueResponse(QSharedPointer<Responses::AbstractResponse>(new Responses::SocketDisconnectedResponse(reason)));
1143 }
1144 
1145 Parser::~Parser()
1146 {
1147     // We want to prevent nasty signals from the underlying socket from
1148     // interfering with this object -- some of our local data might have
1149     // been already destroyed!
1150     socket->disconnect(this);
1151     socket->close();
1152 }
1153 
1154 uint Parser::parserId() const
1155 {
1156     return m_parserId;
1157 }
1158 
1159 void Parser::slotSocketStateChanged(const Imap::ConnectionState connState, const QString &message)
1160 {
1161     if (connState == CONN_STATE_CONNECTED_PRETLS_PRECAPS) {
1162 #ifdef PRINT_TRAFFIC_TX
1163         qDebug() << m_parserId << "*** Connection established";
1164 #endif
1165         emit lineReceived(this, "*** Connection established");
1166         waitingForConnection = false;
1167         QTimer::singleShot(0, this, SLOT(executeCommands()));
1168     } else if (connState == CONN_STATE_AUTHENTICATED) {
1169         // unit tests: don't wait for the initial untagged response greetings
1170         m_expectsInitialGreeting = false;
1171     }
1172     emit lineReceived(this, "*** " + message.toUtf8());
1173     emit connectionStateChanged(this, connState);
1174 }
1175 
1176 }