1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 
3    This file is part of the Trojita Qt IMAP e-mail client,
4    http://trojita.flaska.net/
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License as
8    published by the Free Software Foundation; either version 2 of
9    the License or (at your option) version 3 or any later version
10    accepted by the membership of KDE e.V. (or its successor approved
11    by the membership of KDE e.V.), which shall act as a proxy
12    defined in Section 14 of version 3 of the license.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 #include <algorithm>
23 #include <QDebug>
24 #include <QStringList>
25 #include <QMutexLocker>
26 #include <QProcess>
27 #include <QSslError>
28 #include <QTime>
29 #include <QTimer>
30 #include "Parser.h"
31 #include "Imap/Encoders.h"
32 #include "LowLevelParser.h"
33 #include "../../Streams/IODeviceSocket.h"
34 #include "../Model/Utils.h"
35 
36 //#define PRINT_TRAFFIC 100
37 //#define PRINT_TRAFFIC_TX 500
38 //#define PRINT_TRAFFIC_RX 25
39 //#define PRINT_TRAFFIC_SENSITIVE
40 
41 #ifdef PRINT_TRAFFIC
42 # ifndef PRINT_TRAFFIC_TX
43 #  define PRINT_TRAFFIC_TX PRINT_TRAFFIC
44 # endif
45 # ifndef PRINT_TRAFFIC_RX
46 #  define PRINT_TRAFFIC_RX PRINT_TRAFFIC
47 # endif
48 #endif
49 
50 /*
51  * Parser interface considerations:
52  *
53  * - Parser receives comments and gives back some kind of ID for tracking the
54  *   command state
55  * - "High-level stuff" like "has this command already finished" should be
56  *   implemented on higher level
57  * - Due to command pipelining, there's no way to find out that this untagged
58  *   reply we just received was triggered by FOO command
59  *
60  * Interface:
61  *
62  * - One function per command
63  * - Each received reply emits a signal (Qt-specific stuff now)
64  *
65  *
66  * Usage example FIXME DRAFT:
67  *
68  *  Imap::Parser parser;
69  *  Imap::CommandHandle res = parser.deleteFolder( "foo mailbox/bar/baz" );
70  *
71  *
72  *
73  * How it works under the hood:
74  *
75  * - When there are any data available on the net, process them ASAP
76  * - When user queues a command, process it ASAP
77  * - You can't block the caller of the queueCommand()
78  *
79  * So, how to implement this?
80  *
81  * - Whenever something interesting happens (data/command/exit
82  *   requested/available), we ask the worker thread to do something
83  *
84  * */
85 
86 namespace Imap
87 {
88 
Parser(QObject * parent,Streams::Socket * socket,const uint myId)89 Parser::Parser(QObject *parent, Streams::Socket *socket, const uint myId):
90     QObject(parent), socket(socket), m_lastTagUsed(0), idling(false), waitForInitialIdle(false),
91     m_literalPlus(LiteralPlus::Unsupported), waitingForContinuation(false), startTlsInProgress(false), compressDeflateInProgress(false),
92     waitingForConnection(true), waitingForEncryption(socket->isConnectingEncryptedSinceStart()), waitingForSslPolicy(false),
93     m_expectsInitialGreeting(true), readingMode(ReadingLine), oldLiteralPosition(0), m_parserId(myId)
94 {
95     connect(socket, &Streams::Socket::disconnected, this, &Parser::handleDisconnected);
96     connect(socket, &Streams::Socket::readyRead, this, &Parser::handleReadyRead);
97     connect(socket, &Streams::Socket::stateChanged, this, &Parser::slotSocketStateChanged);
98     connect(socket, &Streams::Socket::encrypted, this, &Parser::handleSocketEncrypted);
99 }
100 
noop()101 CommandHandle Parser::noop()
102 {
103     return queueCommand(Commands::ATOM, "NOOP");
104 }
105 
logout()106 CommandHandle Parser::logout()
107 {
108     return queueCommand(Commands::Command("LOGOUT"));
109 
110     // Queue a request for closing the socket. It'll get closed after a short while.
111     QTimer::singleShot(1000, this, SLOT(closeConnection()));
112 }
113 
114 /** @short Close the underlying conneciton */
closeConnection()115 void Parser::closeConnection()
116 {
117     socket->close();
118 }
119 
capability()120 CommandHandle Parser::capability()
121 {
122     // CAPABILITY should take precedence over LOGIN, because we have to check for LOGINDISABLED
123     return queueCommand(Commands::Command() <<
124                         Commands::PartOfCommand(Commands::ATOM, "CAPABILITY"));
125 }
126 
startTls()127 CommandHandle Parser::startTls()
128 {
129     return queueCommand(Commands::Command() <<
130                         Commands::PartOfCommand(Commands::STARTTLS, "STARTTLS"));
131 }
132 
compressDeflate()133 CommandHandle Parser::compressDeflate()
134 {
135     return queueCommand(Commands::Command() <<
136                         Commands::PartOfCommand(Commands::COMPRESS_DEFLATE, "COMPRESS DEFLATE"));
137 }
138 
139 #if 0
140 CommandHandle Parser::authenticate(/*Authenticator FIXME*/)
141 {
142     // FIXME: needs higher priority
143     return queueCommand(Commands::ATOM, "AUTHENTICATE");
144 }
145 #endif
146 
login(const QString & username,const QString & password)147 CommandHandle Parser::login(const QString &username, const QString &password)
148 {
149     return queueCommand(Commands::Command("LOGIN") <<
150                         Commands::PartOfCommand(username.toUtf8()) << Commands::PartOfCommand(password.toUtf8()));
151 }
152 
select(const QString & mailbox,const QList<QByteArray> & params)153 CommandHandle Parser::select(const QString &mailbox, const QList<QByteArray> &params)
154 {
155     Commands::Command cmd = Commands::Command("SELECT") << encodeImapFolderName(mailbox);
156     if (!params.isEmpty()) {
157         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
158         Q_FOREACH(const QByteArray &param, params) {
159             cmd << Commands::PartOfCommand(Commands::ATOM, param);
160         }
161         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
162     }
163     return queueCommand(cmd);
164 }
165 
selectQresync(const QString & mailbox,const uint uidValidity,const quint64 highestModSeq,const Sequence & knownUids,const Sequence & sequenceSnapshot,const Sequence & uidSnapshot)166 CommandHandle Parser::selectQresync(const QString &mailbox, const uint uidValidity,
167                                     const quint64 highestModSeq, const Sequence &knownUids, const Sequence &sequenceSnapshot,
168                                     const Sequence &uidSnapshot)
169 {
170     Commands::Command cmd = Commands::Command("SELECT") << encodeImapFolderName(mailbox) <<
171            Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (QRESYNC (") <<
172            Commands::PartOfCommand(Commands::ATOM, QByteArray::number(uidValidity)) <<
173            Commands::PartOfCommand(Commands::ATOM, QByteArray::number(highestModSeq));
174     if (knownUids.isValid()) {
175         cmd << Commands::PartOfCommand(Commands::ATOM, knownUids.toByteArray());
176     }
177     Q_ASSERT(uidSnapshot.isValid() == sequenceSnapshot.isValid());
178     if (sequenceSnapshot.isValid()) {
179         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (") <<
180                Commands::PartOfCommand(Commands::ATOM, sequenceSnapshot.toByteArray()) <<
181                Commands::PartOfCommand(Commands::ATOM, uidSnapshot.toByteArray()) <<
182                Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")))");
183     } else {
184         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, "))");
185     }
186     return queueCommand(cmd);
187 }
188 
examine(const QString & mailbox,const QList<QByteArray> & params)189 CommandHandle Parser::examine(const QString &mailbox, const QList<QByteArray> &params)
190 {
191     Commands::Command cmd = Commands::Command("EXAMINE") << encodeImapFolderName(mailbox);
192     if (!params.isEmpty()) {
193         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
194         Q_FOREACH(const QByteArray &param, params) {
195             cmd << Commands::PartOfCommand(Commands::ATOM, param);
196         }
197         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
198     }
199     return queueCommand(cmd);
200 }
201 
deleteMailbox(const QString & mailbox)202 CommandHandle Parser::deleteMailbox(const QString &mailbox)
203 {
204     return queueCommand(Commands::Command("DELETE") << encodeImapFolderName(mailbox));
205 }
206 
create(const QString & mailbox)207 CommandHandle Parser::create(const QString &mailbox)
208 {
209     return queueCommand(Commands::Command("CREATE") << encodeImapFolderName(mailbox));
210 }
211 
rename(const QString & oldName,const QString & newName)212 CommandHandle Parser::rename(const QString &oldName, const QString &newName)
213 {
214     return queueCommand(Commands::Command("RENAME") <<
215                         encodeImapFolderName(oldName) <<
216                         encodeImapFolderName(newName));
217 }
218 
subscribe(const QString & mailbox)219 CommandHandle Parser::subscribe(const QString &mailbox)
220 {
221     return queueCommand(Commands::Command("SUBSCRIBE") << encodeImapFolderName(mailbox));
222 }
223 
unSubscribe(const QString & mailbox)224 CommandHandle Parser::unSubscribe(const QString &mailbox)
225 {
226     return queueCommand(Commands::Command("UNSUBSCRIBE") << encodeImapFolderName(mailbox));
227 }
228 
list(const QString & reference,const QString & mailbox,const QStringList & returnOptions)229 CommandHandle Parser::list(const QString &reference, const QString &mailbox, const QStringList &returnOptions)
230 {
231     Commands::Command cmd("LIST");
232     cmd << reference.toUtf8() << encodeImapFolderName(mailbox);
233     if (!returnOptions.isEmpty()) {
234         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " RETURN (");
235         Q_FOREACH(const QString &option, returnOptions) {
236             cmd << Commands::PartOfCommand(Commands::ATOM, option.toUtf8());
237         }
238         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
239     }
240     return queueCommand(cmd);
241 }
242 
lSub(const QString & reference,const QString & mailbox)243 CommandHandle Parser::lSub(const QString &reference, const QString &mailbox)
244 {
245     return queueCommand(Commands::Command("LSUB") << reference.toUtf8() << encodeImapFolderName(mailbox));
246 }
247 
status(const QString & mailbox,const QStringList & fields)248 CommandHandle Parser::status(const QString &mailbox, const QStringList &fields)
249 {
250     return queueCommand(Commands::Command("STATUS") << encodeImapFolderName(mailbox) <<
251                         Commands::PartOfCommand(Commands::ATOM, "(" + fields.join(QStringLiteral(" ")).toUtf8() + ")")
252                        );
253 }
254 
append(const QString & mailbox,const QByteArray & message,const QStringList & flags,const QDateTime & timestamp)255 CommandHandle Parser::append(const QString &mailbox, const QByteArray &message, const QStringList &flags, const QDateTime &timestamp)
256 {
257     Commands::Command command("APPEND");
258     command << encodeImapFolderName(mailbox);
259     if (flags.count())
260         command << Commands::PartOfCommand(Commands::ATOM, "(" + flags.join(QStringLiteral(" ")).toUtf8() + ")");
261     if (timestamp.isValid())
262         command << Commands::PartOfCommand(Imap::dateTimeToInternalDate(timestamp).toUtf8());
263     command << Commands::PartOfCommand(Commands::LITERAL, message);
264 
265     return queueCommand(command);
266 }
267 
appendCatenate(const QString & mailbox,const QList<Imap::Mailbox::CatenatePair> & data,const QStringList & flags,const QDateTime & timestamp)268 CommandHandle Parser::appendCatenate(const QString &mailbox, const QList<Imap::Mailbox::CatenatePair> &data,
269                                      const QStringList &flags, const QDateTime &timestamp)
270 {
271     Commands::Command command("APPEND");
272     command << encodeImapFolderName(mailbox);
273     if (flags.count())
274         command << Commands::PartOfCommand(Commands::ATOM, "(" + flags.join(QStringLiteral(" ")).toUtf8() + ")");
275     if (timestamp.isValid())
276         command << Commands::PartOfCommand(Imap::dateTimeToInternalDate(timestamp).toUtf8());
277     command << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " CATENATE (");
278     Q_FOREACH(const Imap::Mailbox::CatenatePair &item, data) {
279         switch (item.first) {
280         case Imap::Mailbox::CATENATE_TEXT:
281             command << Commands::PartOfCommand(Commands::ATOM, "TEXT");
282             command << Commands::PartOfCommand(Commands::LITERAL, item.second);
283             break;
284         case Imap::Mailbox::CATENATE_URL:
285             command << Commands::PartOfCommand(Commands::ATOM, "URL");
286             command << Commands::PartOfCommand(item.second);
287             break;
288         }
289     }
290     command << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
291 
292     return queueCommand(command);
293 }
294 
check()295 CommandHandle Parser::check()
296 {
297     return queueCommand(Commands::ATOM, "CHECK");
298 }
299 
close()300 CommandHandle Parser::close()
301 {
302     return queueCommand(Commands::ATOM, "CLOSE");
303 }
304 
expunge()305 CommandHandle Parser::expunge()
306 {
307     return queueCommand(Commands::ATOM, "EXPUNGE");
308 }
309 
searchHelper(const QByteArray & command,const QStringList & criteria,const QByteArray & charset)310 CommandHandle Parser::searchHelper(const QByteArray &command, const QStringList &criteria, const QByteArray &charset)
311 {
312     Commands::Command cmd(command);
313 
314     if (!charset.isEmpty())
315         cmd << "CHARSET" << charset;
316 
317     // FIXME: we don't really support anything else but utf-8 here
318 
319     if (criteria.size() == 1) {
320         // Hack: if it's just a single item, let's assume it's already well-formatted by the caller.
321         // This is required in the current shape of the API if we want to allow the user to type in their queries directly.
322         cmd << Commands::PartOfCommand(Commands::ATOM, criteria.front().toUtf8());
323     } else {
324         for (QStringList::const_iterator it = criteria.begin(); it != criteria.end(); ++it)
325             cmd << it->toUtf8();
326     }
327 
328     return queueCommand(cmd);
329 }
330 
uidSearchUid(const QByteArray & sequence)331 CommandHandle Parser::uidSearchUid(const QByteArray &sequence)
332 {
333     Commands::Command command("UID SEARCH");
334     command << Commands::PartOfCommand(Commands::ATOM, sequence);
335     return queueCommand(command);
336 }
337 
uidESearchUid(const QByteArray & sequence)338 CommandHandle Parser::uidESearchUid(const QByteArray &sequence)
339 {
340     Commands::Command command("UID SEARCH RETURN (ALL)");
341     command << Commands::PartOfCommand(Commands::ATOM, sequence);
342     return queueCommand(command);
343 }
344 
sortHelper(const QByteArray & command,const QStringList & sortCriteria,const QByteArray & charset,const QStringList & searchCriteria)345 CommandHandle Parser::sortHelper(const QByteArray &command, const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
346 {
347     Q_ASSERT(! sortCriteria.isEmpty());
348     Commands::Command cmd;
349 
350     cmd << Commands::PartOfCommand(Commands::ATOM, command) <<
351         Commands::PartOfCommand(Commands::ATOM, "(" + sortCriteria.join(QStringLiteral(" ")).toUtf8() + ")" ) <<
352         charset;
353 
354     if (searchCriteria.size() == 1) {
355         // Hack: if it's just a single item, let's assume it's already well-formatted by the caller.
356         // This is required in the current shape of the API if we want to allow the user to type in their queries directly.
357         cmd << Commands::PartOfCommand(Commands::ATOM, searchCriteria.front().toUtf8());
358     } else {
359         for (QStringList::const_iterator it = searchCriteria.begin(); it != searchCriteria.end(); ++it)
360             cmd << it->toUtf8();
361     }
362 
363     return queueCommand(cmd);
364 }
365 
sort(const QStringList & sortCriteria,const QByteArray & charset,const QStringList & searchCriteria)366 CommandHandle Parser::sort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
367 {
368     return sortHelper("SORT", sortCriteria, charset, searchCriteria);
369 }
370 
uidSort(const QStringList & sortCriteria,const QByteArray & charset,const QStringList & searchCriteria)371 CommandHandle Parser::uidSort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria)
372 {
373     return sortHelper("UID SORT", sortCriteria, charset, searchCriteria);
374 }
375 
uidESort(const QStringList & sortCriteria,const QByteArray & charset,const QStringList & searchCriteria,const QStringList & returnOptions)376 CommandHandle Parser::uidESort(const QStringList &sortCriteria, const QByteArray &charset, const QStringList &searchCriteria,
377                                const QStringList &returnOptions)
378 {
379     return sortHelper("UID SORT RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
380                       sortCriteria, charset, searchCriteria);
381 }
382 
uidESearch(const QByteArray & charset,const QStringList & searchCriteria,const QStringList & returnOptions)383 CommandHandle Parser::uidESearch(const QByteArray &charset, const QStringList &searchCriteria, const QStringList &returnOptions)
384 {
385     return searchHelper("UID SEARCH RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
386                         searchCriteria, charset);
387 }
388 
cancelUpdate(const CommandHandle & tag)389 CommandHandle Parser::cancelUpdate(const CommandHandle &tag)
390 {
391     Commands::Command command("CANCELUPDATE");
392     command << Commands::PartOfCommand(Commands::QUOTED_STRING, tag);
393     return queueCommand(command);
394 }
395 
threadHelper(const QByteArray & command,const QByteArray & algo,const QByteArray & charset,const QStringList & searchCriteria)396 CommandHandle Parser::threadHelper(const QByteArray &command, const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
397 {
398     Commands::Command cmd;
399 
400     cmd << Commands::PartOfCommand(Commands::ATOM, command) << algo << charset;
401 
402     for (QStringList::const_iterator it = searchCriteria.begin(); it != searchCriteria.end(); ++it) {
403         // FIXME: this is another place which needs proper structure for this searching stuff...
404         cmd << Commands::PartOfCommand(Commands::ATOM, it->toUtf8());
405     }
406 
407     return queueCommand(cmd);
408 }
409 
thread(const QByteArray & algo,const QByteArray & charset,const QStringList & searchCriteria)410 CommandHandle Parser::thread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
411 {
412     return threadHelper("THREAD", algo, charset, searchCriteria);
413 }
414 
uidThread(const QByteArray & algo,const QByteArray & charset,const QStringList & searchCriteria)415 CommandHandle Parser::uidThread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria)
416 {
417     return threadHelper("UID THREAD", algo, charset, searchCriteria);
418 }
419 
uidEThread(const QByteArray & algo,const QByteArray & charset,const QStringList & searchCriteria,const QStringList & returnOptions)420 CommandHandle Parser::uidEThread(const QByteArray &algo, const QByteArray &charset, const QStringList &searchCriteria,
421                                  const QStringList &returnOptions)
422 {
423     return threadHelper("UID THREAD RETURN (" + returnOptions.join(QStringLiteral(" ")).toUtf8() + ")",
424                         algo, charset, searchCriteria);
425 }
426 
fetch(const Sequence & seq,const QStringList & items,const QMap<QByteArray,quint64> & uint64Modifiers)427 CommandHandle Parser::fetch(const Sequence &seq, const QStringList &items, const QMap<QByteArray, quint64> &uint64Modifiers)
428 {
429     Commands::Command cmd = Commands::Command("FETCH") <<
430                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
431                         Commands::PartOfCommand(Commands::ATOM, '(' + items.join(QStringLiteral(" ")).toUtf8() + ')');
432     if (!uint64Modifiers.isEmpty()) {
433         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
434         for (QMap<QByteArray, quint64>::const_iterator it = uint64Modifiers.constBegin(); it != uint64Modifiers.constEnd(); ++it) {
435             cmd << Commands::PartOfCommand(Commands::ATOM, it.key()) <<
436                    Commands::PartOfCommand(Commands::ATOM, QByteArray::number(it.value()));
437         }
438         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
439     }
440     return queueCommand(cmd);
441 }
442 
store(const Sequence & seq,const QString & item,const QString & value)443 CommandHandle Parser::store(const Sequence &seq, const QString &item, const QString &value)
444 {
445     return queueCommand(Commands::Command("STORE") <<
446                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
447                         Commands::PartOfCommand(Commands::ATOM, item.toUtf8()) <<
448                         Commands::PartOfCommand(Commands::ATOM, value.toUtf8())
449                        );
450 }
451 
copy(const Sequence & seq,const QString & mailbox)452 CommandHandle Parser::copy(const Sequence &seq, const QString &mailbox)
453 {
454     return queueCommand(Commands::Command("COPY") <<
455                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
456                         encodeImapFolderName(mailbox));
457 }
458 
uidFetch(const Sequence & seq,const QList<QByteArray> & items)459 CommandHandle Parser::uidFetch(const Sequence &seq, const QList<QByteArray> &items)
460 {
461     QByteArray buf;
462     Q_FOREACH(const QByteArray &item, items) {
463         buf += ' ' + item;
464     }
465     buf += ')';
466     buf[0] = '(';
467     return queueCommand(Commands::Command("UID FETCH") <<
468                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
469                         Commands::PartOfCommand(Commands::ATOM, buf));
470 }
471 
uidStore(const Sequence & seq,const QString & item,const QString & value)472 CommandHandle Parser::uidStore(const Sequence &seq, const QString &item, const QString &value)
473 {
474     return queueCommand(Commands::Command("UID STORE") <<
475                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
476                         Commands::PartOfCommand(Commands::ATOM, item.toUtf8()) <<
477                         Commands::PartOfCommand(Commands::ATOM, value.toUtf8()));
478 }
479 
uidCopy(const Sequence & seq,const QString & mailbox)480 CommandHandle Parser::uidCopy(const Sequence &seq, const QString &mailbox)
481 {
482     return queueCommand(Commands::Command("UID COPY") <<
483                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
484                         encodeImapFolderName(mailbox));
485 }
486 
uidMove(const Sequence & seq,const QString & mailbox)487 CommandHandle Parser::uidMove(const Sequence &seq, const QString &mailbox)
488 {
489     return queueCommand(Commands::Command("UID MOVE") <<
490                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()) <<
491                         encodeImapFolderName(mailbox));
492 }
493 
uidExpunge(const Sequence & seq)494 CommandHandle Parser::uidExpunge(const Sequence &seq)
495 {
496     return queueCommand(Commands::Command("UID EXPUNGE") <<
497                         Commands::PartOfCommand(Commands::ATOM, seq.toByteArray()));
498 }
499 
xAtom(const Commands::Command & cmd)500 CommandHandle Parser::xAtom(const Commands::Command &cmd)
501 {
502     return queueCommand(cmd);
503 }
504 
unSelect()505 CommandHandle Parser::unSelect()
506 {
507     return queueCommand(Commands::ATOM, "UNSELECT");
508 }
509 
idle()510 CommandHandle Parser::idle()
511 {
512     return queueCommand(Commands::IDLE, "IDLE");
513 }
514 
idleDone()515 void Parser::idleDone()
516 {
517     // This is not a new "command", so we don't go via queueCommand()
518     // which would allocate a new tag for us, but submit directly
519     Commands::Command cmd;
520     cmd << Commands::PartOfCommand(Commands::IDLE_DONE, "DONE");
521     cmdQueue.append(cmd);
522     QTimer::singleShot(0, this, SLOT(executeCommands()));
523 }
524 
idleContinuationWontCome()525 void Parser::idleContinuationWontCome()
526 {
527     Q_ASSERT(waitForInitialIdle);
528     waitForInitialIdle = false;
529     idling = false;
530     QTimer::singleShot(0, this, SLOT(executeCommands()));
531 }
532 
idleMagicallyTerminatedByServer()533 void Parser::idleMagicallyTerminatedByServer()
534 {
535     Q_ASSERT(! waitForInitialIdle);
536     Q_ASSERT(idling);
537     idling = false;
538 }
539 
namespaceCommand()540 CommandHandle Parser::namespaceCommand()
541 {
542     return queueCommand(Commands::ATOM, "NAMESPACE");
543 }
544 
idCommand()545 CommandHandle Parser::idCommand()
546 {
547     return queueCommand(Commands::Command("ID NIL"));
548 }
549 
idCommand(const QMap<QByteArray,QByteArray> & args)550 CommandHandle Parser::idCommand(const QMap<QByteArray,QByteArray> &args)
551 {
552     Commands::Command cmd("ID ");
553     cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, "(");
554     for (QMap<QByteArray,QByteArray>::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) {
555         cmd << Commands::PartOfCommand(Commands::QUOTED_STRING, it.key()) << Commands::PartOfCommand(Commands::QUOTED_STRING, it.value());
556     }
557     cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
558     return queueCommand(cmd);
559 }
560 
enable(const QList<QByteArray> & extensions)561 CommandHandle Parser::enable(const QList<QByteArray> &extensions)
562 {
563     Commands::Command cmd("ENABLE");
564     Q_FOREACH(const QByteArray &item, extensions) {
565         cmd << Commands::PartOfCommand(Commands::ATOM, item);
566     }
567     return queueCommand(cmd);
568 }
569 
genUrlAuth(const QByteArray & url,const QByteArray mechanism)570 CommandHandle Parser::genUrlAuth(const QByteArray &url, const QByteArray mechanism)
571 {
572     Commands::Command cmd("GENURLAUTH");
573     cmd << Commands::PartOfCommand(Commands::QUOTED_STRING, url);
574     cmd << Commands::PartOfCommand(Commands::ATOM, mechanism);
575     return queueCommand(cmd);
576 }
577 
uidSendmail(const uint uid,const Mailbox::UidSubmitOptionsList & submissionOptions)578 CommandHandle Parser::uidSendmail(const uint uid, const Mailbox::UidSubmitOptionsList &submissionOptions)
579 {
580     Commands::Command cmd("UID SENDMAIL");
581     cmd << Commands::PartOfCommand(QByteArray::number(uid));
582     if (!submissionOptions.isEmpty()) {
583         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
584         for (Mailbox::UidSubmitOptionsList::const_iterator it = submissionOptions.begin(); it != submissionOptions.end(); ++it) {
585             cmd << Commands::PartOfCommand(Commands::ATOM, it->first);
586             switch (it->second.type()) {
587             case QVariant::ByteArray:
588                 cmd << Commands::PartOfCommand(it->second.toByteArray());
589                 break;
590             case QVariant::List:
591                 cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, " (");
592                 Q_FOREACH(const QVariant &item, it->second.toList()) {
593                     cmd << Commands::PartOfCommand(Commands::ATOM, item.toByteArray());
594                 }
595                 cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
596                 break;
597             case QVariant::Invalid:
598                 cmd << Commands::PartOfCommand(Commands::ATOM, "NIL");
599                 break;
600             default:
601                 throw InvalidArgument("Internal error: Malformed data for the UID SEND command.");
602             }
603         }
604         cmd << Commands::PartOfCommand(Commands::ATOM_NO_SPACE_AROUND, ")");
605     }
606     return queueCommand(cmd);
607 }
608 
queueCommand(Commands::Command command)609 CommandHandle Parser::queueCommand(Commands::Command command)
610 {
611     CommandHandle tag = generateTag();
612     command.addTag(tag);
613     cmdQueue.append(command);
614     QTimer::singleShot(0, this, SLOT(executeCommands()));
615     return tag;
616 }
617 
queueResponse(const QSharedPointer<Responses::AbstractResponse> & resp)618 void Parser::queueResponse(const QSharedPointer<Responses::AbstractResponse> &resp)
619 {
620     respQueue.push_back(resp);
621     // Try to limit the signal rate -- when there are multiple items in the queue, there's no point in sending more signals
622     if (respQueue.size() == 1) {
623         emit responseReceived(this);
624     }
625 
626     if (waitingForContinuation) {
627         // Check whether this is the server's way of informing us that the continuation request is not going to arrive
628         QSharedPointer<Responses::State> stateResponse = resp.dynamicCast<Responses::State>();
629         Q_ASSERT(!literalCommandTag.isEmpty());
630         if (stateResponse && stateResponse->tag == literalCommandTag) {
631             literalCommandTag.clear();
632             waitingForContinuation = false;
633             cmdQueue.pop_front();
634             QTimer::singleShot(0, this, SLOT(executeCommands()));
635             if (stateResponse->kind != Responses::NO && stateResponse->kind != Responses::BAD) {
636                 // FIXME: use parserWarning when it's adapted throughout the code
637                 qDebug() << "Synchronized literal rejected but response is neither NO nor BAD";
638             }
639         }
640     }
641 }
642 
hasResponse() const643 bool Parser::hasResponse() const
644 {
645     return ! respQueue.empty();
646 }
647 
getResponse()648 QSharedPointer<Responses::AbstractResponse> Parser::getResponse()
649 {
650     QSharedPointer<Responses::AbstractResponse> ptr;
651     if (respQueue.empty())
652         return ptr;
653     ptr = respQueue.front();
654     respQueue.pop_front();
655     return ptr;
656 }
657 
generateTag()658 QByteArray Parser::generateTag()
659 {
660     return QStringLiteral("y%1").arg(m_lastTagUsed++).toUtf8();
661 }
662 
handleReadyRead()663 void Parser::handleReadyRead()
664 {
665     while (!waitingForEncryption && !waitingForSslPolicy) {
666         switch (readingMode) {
667         case ReadingLine:
668             if (socket->canReadLine()) {
669                 reallyReadLine();
670             } else {
671                 // Not enough data yet, let's try again later
672                 return;
673             }
674             break;
675         case ReadingNumberOfBytes:
676         {
677             QByteArray buf = socket->read(readingBytes);
678             readingBytes -= buf.size();
679             currentLine += buf;
680             if (readingBytes == 0) {
681                 // we've read the literal
682                 readingMode = ReadingLine;
683             } else {
684                 return;
685             }
686         }
687         break;
688         }
689     }
690 }
691 
reallyReadLine()692 void Parser::reallyReadLine()
693 {
694     try {
695         currentLine += socket->readLine();
696         if (currentLine.endsWith("}\r\n")) {
697             int offset = currentLine.lastIndexOf('{');
698             if (offset < oldLiteralPosition)
699                 throw ParseError("Got unmatched '}'", currentLine, currentLine.size() - 3);
700             bool ok;
701             int number = currentLine.mid(offset + 1, currentLine.size() - offset - 4).toInt(&ok);
702             if (!ok)
703                 throw ParseError("Can't parse numeric literal size", currentLine, offset);
704             if (number < 0)
705                 throw ParseError("Negative literal size", currentLine, offset);
706             oldLiteralPosition = offset;
707             readingMode = ReadingNumberOfBytes;
708             readingBytes = number;
709         } else if (currentLine.endsWith("\r\n")) {
710             // it's complete
711             if (startTlsInProgress && currentLine.startsWith(startTlsCommand)) {
712                 startTlsCommand.clear();
713                 startTlsReply = currentLine;
714                 currentLine.clear();
715                 oldLiteralPosition = 0;
716                 QTimer::singleShot(0, this, SLOT(finishStartTls()));
717                 return;
718             }
719             processLine(currentLine);
720             currentLine.clear();
721             oldLiteralPosition = 0;
722         } else {
723             throw ParseError("Received line doesn't end with any of \"}\\r\\n\" and \"\\r\\n\"", currentLine, 0);
724         }
725     } catch (ParserException &e) {
726         queueResponse(QSharedPointer<Responses::AbstractResponse>(new Responses::ParseErrorResponse(e)));
727     }
728 }
729 
executeCommands()730 void Parser::executeCommands()
731 {
732     while (! waitingForContinuation && ! waitForInitialIdle &&
733            ! waitingForConnection && ! waitingForEncryption && ! waitingForSslPolicy &&
734            ! cmdQueue.isEmpty() && ! startTlsInProgress && !compressDeflateInProgress)
735         executeACommand();
736 }
737 
finishStartTls()738 void Parser::finishStartTls()
739 {
740     emit lineSent(this, "*** STARTTLS");
741 #ifdef PRINT_TRAFFIC_TX
742     qDebug() << m_parserId << "*** STARTTLS";
743 #endif
744     cmdQueue.pop_front();
745     socket->startTls(); // warn: this might invoke event loop
746     startTlsInProgress = false;
747     waitingForEncryption = true;
748     processLine(startTlsReply);
749 }
750 
handleSocketEncrypted()751 void Parser::handleSocketEncrypted()
752 {
753     waitingForEncryption = false;
754     waitingForConnection = false;
755     waitingForSslPolicy = true;
756     QSharedPointer<Responses::AbstractResponse> resp(
757                 new Responses::SocketEncryptedResponse(socket->sslChain(), socket->sslErrors()));
758     QByteArray buf;
759     QTextStream ss(&buf);
760     ss << "*** " << *resp;
761     ss.flush();
762 #ifdef PRINT_TRAFFIC_RX
763     qDebug() << m_parserId << "***" << buf;
764 #endif
765     emit lineReceived(this, buf);
766     handleReadyRead();
767     queueResponse(resp);
768     executeCommands();
769 }
770 
771 /** @short We've previously frozen the command queue, so it's time to kick it a bit and keep the usual sending/receiving again */
handleCompressionPossibleActivated()772 void Parser::handleCompressionPossibleActivated()
773 {
774     handleReadyRead();
775     executeCommands();
776 }
777 
unfreezeAfterEncryption()778 void Parser::unfreezeAfterEncryption()
779 {
780     Q_ASSERT(waitingForSslPolicy);
781     waitingForSslPolicy = false;
782     handleReadyRead();
783     executeCommands();
784 }
785 
executeACommand()786 void Parser::executeACommand()
787 {
788     Q_ASSERT(! cmdQueue.isEmpty());
789     Commands::Command &cmd = cmdQueue.first();
790 
791     QByteArray buf;
792 
793     bool sensitiveCommand = (cmd.cmds.size() > 2 && cmd.cmds[1].text == "LOGIN");
794     QByteArray privateMessage = sensitiveCommand ? QByteArray("[LOGIN command goes here]") : QByteArray();
795 
796 #ifdef PRINT_TRAFFIC_TX
797 #ifdef PRINT_TRAFFIC_SENSITIVE
798     bool printThisCommand = true;
799 #else
800     bool printThisCommand = ! sensitiveCommand;
801 #endif
802 #endif
803 
804     if (cmd.cmds[ cmd.currentPart ].kind == Commands::IDLE_DONE) {
805         // Handling of the IDLE_DONE is a bit special, as we have to check and update the idling flag...
806         Q_ASSERT(idling);
807         buf.append("DONE\r\n");
808 #ifdef PRINT_TRAFFIC_TX
809         qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
810 #endif
811         socket->write(buf);
812         idling = false;
813         cmdQueue.pop_front();
814         emit lineSent(this, buf);
815         buf.clear();
816         return;
817     }
818 
819     Q_ASSERT(! idling);
820 
821     while (1) {
822         Commands::PartOfCommand &part = cmd.cmds[ cmd.currentPart ];
823         switch (part.kind) {
824         case Commands::ATOM:
825         case Commands::ATOM_NO_SPACE_AROUND:
826             buf.append(part.text);
827             break;
828         case Commands::QUOTED_STRING:
829         {
830             QByteArray item = part.text;
831             item.replace('\\', "\\\\");
832             buf.append('"');
833             buf.append(item);
834             buf.append('"');
835         }
836         break;
837         case Commands::LITERAL:
838             if (m_literalPlus == LiteralPlus::Plus || (m_literalPlus == LiteralPlus::Minus && part.text.size() <= 4096)) {
839                 buf.append('{');
840                 buf.append(QByteArray::number(part.text.size()));
841                 buf.append("+}\r\n");
842                 buf.append(part.text);
843             } else if (part.numberSent) {
844                 buf.append(part.text);
845             } else {
846                 buf.append('{');
847                 buf.append(QByteArray::number(part.text.size()));
848                 buf.append("}\r\n");
849 #ifdef PRINT_TRAFFIC_TX
850                 if (printThisCommand)
851                     qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
852                 else
853                     qDebug() << m_parserId << ">>> [sensitive command] -- added literal";
854 #endif
855                 socket->write(buf);
856                 part.numberSent = true;
857                 waitingForContinuation = true;
858                 Q_ASSERT(literalCommandTag.isEmpty());
859                 literalCommandTag = cmd.cmds.first().text;
860                 Q_ASSERT(!literalCommandTag.isEmpty());
861                 emit lineSent(this, sensitiveCommand ? privateMessage : buf);
862                 return; // and wait for continuation request
863             }
864             break;
865         case Commands::IDLE_DONE:
866             Q_ASSERT(false); // is handled above
867             break;
868         case Commands::IDLE:
869             buf.append("IDLE\r\n");
870 #ifdef PRINT_TRAFFIC_TX
871             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
872 #endif
873             socket->write(buf);
874             idling = true;
875             waitForInitialIdle = true;
876             cmdQueue.pop_front();
877             emit lineSent(this, buf);
878             return;
879             break;
880         case Commands::STARTTLS:
881             startTlsCommand = buf;
882             buf.append("STARTTLS\r\n");
883 #ifdef PRINT_TRAFFIC_TX
884             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
885 #endif
886             socket->write(buf);
887             startTlsInProgress = true;
888             emit lineSent(this, buf);
889             return;
890             break;
891         case Commands::COMPRESS_DEFLATE:
892             compressDeflateCommand = buf;
893             buf.append("COMPRESS DEFLATE\r\n");
894 #ifdef PRINT_TRAFFIC_TX
895             qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
896 #endif
897             socket->write(buf);
898             compressDeflateInProgress = true;
899             cmdQueue.pop_front();
900             emit lineSent(this, buf);
901             return;
902             break;
903         }
904         if (cmd.currentPart == cmd.cmds.size() - 1) {
905             // finalize
906             buf.append("\r\n");
907 #ifdef PRINT_TRAFFIC_TX
908             if (printThisCommand)
909                 qDebug() << m_parserId << ">>>" << buf.left(PRINT_TRAFFIC_TX).trimmed();
910             else
911                 qDebug() << m_parserId << ">>> [sensitive command]";
912 #endif
913             socket->write(buf);
914             cmdQueue.pop_front();
915             emit lineSent(this, sensitiveCommand ? privateMessage : buf);
916             break;
917         } else {
918             if (part.kind == Commands::ATOM_NO_SPACE_AROUND || cmd.cmds[cmd.currentPart + 1].kind == Commands::ATOM_NO_SPACE_AROUND) {
919                 // Skip the extra space if asked to do so
920             } else {
921                 buf.append(' ');
922             }
923             ++cmd.currentPart;
924         }
925     }
926 }
927 
928 /** @short Process a line from IMAP server */
processLine(QByteArray line)929 void Parser::processLine(QByteArray line)
930 {
931 #ifdef PRINT_TRAFFIC_RX
932     QByteArray debugLine = line.trimmed();
933     if (debugLine.size() > PRINT_TRAFFIC_RX)
934         qDebug() << m_parserId << "<<<" << debugLine.left(PRINT_TRAFFIC_RX) << "...";
935     else
936         qDebug() << m_parserId << "<<<" << debugLine;
937 #endif
938     emit lineReceived(this, line);
939     if (m_expectsInitialGreeting && !line.startsWith("* ")) {
940         throw NotAnImapServerError(std::string(), line, -1);
941     } else if (line.startsWith("* ")) {
942         m_expectsInitialGreeting = false;
943         queueResponse(parseUntagged(line));
944     } else if (line.startsWith("+ ")) {
945         if (waitingForContinuation) {
946             waitingForContinuation = false;
947             literalCommandTag.clear();
948             QTimer::singleShot(0, this, SLOT(executeCommands()));
949         } else if (waitForInitialIdle) {
950             waitForInitialIdle = false;
951             QTimer::singleShot(0, this, SLOT(executeCommands()));
952         } else {
953             throw ContinuationRequest(line.constData());
954         }
955     } else {
956         queueResponse(parseTagged(line));
957     }
958 }
959 
parseUntagged(const QByteArray & line)960 QSharedPointer<Responses::AbstractResponse> Parser::parseUntagged(const QByteArray &line)
961 {
962     int pos = 2;
963     uint number;
964     try {
965         number = LowLevelParser::getUInt(line, pos);
966         ++pos;
967     } catch (ParseError &) {
968         return parseUntaggedText(line, pos);
969     }
970     return parseUntaggedNumber(line, pos, number);
971 }
972 
parseUntaggedNumber(const QByteArray & line,int & start,const uint number)973 QSharedPointer<Responses::AbstractResponse> Parser::parseUntaggedNumber(
974     const QByteArray &line, int &start, const uint number)
975 {
976     if (start == line.size())
977         // number and nothing else
978         throw NoData(line, start);
979 
980     QByteArray kindStr = LowLevelParser::getAtom(line, start);
981     Responses::Kind kind;
982     try {
983         kind = Responses::kindFromString(kindStr);
984     } catch (UnrecognizedResponseKind &e) {
985         throw UnrecognizedResponseKind(e.what(), line, start);
986     }
987 
988     switch (kind) {
989     case Responses::EXISTS:
990     case Responses::RECENT:
991     case Responses::EXPUNGE:
992         // no more data should follow
993         if (start >= line.size())
994             throw TooMuchData(line, start);
995         else if (line.mid(start) != QByteArray("\r\n"))
996             throw UnexpectedHere(line, start);   // expected CRLF
997         else
998             try {
999                 return QSharedPointer<Responses::AbstractResponse>(
1000                            new Responses::NumberResponse(kind, number));
1001             } catch (UnexpectedHere &e) {
1002                 throw UnexpectedHere(e.what(), line, start);
1003             }
1004         break;
1005 
1006     case Responses::FETCH:
1007         return QSharedPointer<Responses::AbstractResponse>(
1008                    new Responses::Fetch(number, line, start));
1009         break;
1010 
1011     default:
1012         break;
1013     }
1014     throw UnexpectedHere(line, start);
1015 }
1016 
parseUntaggedText(const QByteArray & line,int & start)1017 QSharedPointer<Responses::AbstractResponse> Parser::parseUntaggedText(
1018     const QByteArray &line, int &start)
1019 {
1020     Responses::Kind kind;
1021     try {
1022         kind = Responses::kindFromString(LowLevelParser::getAtom(line, start));
1023     } catch (UnrecognizedResponseKind &e) {
1024         throw UnrecognizedResponseKind(e.what(), line, start);
1025     }
1026     ++start;
1027     if (start == line.size() && kind != Responses::SEARCH && kind != Responses::SORT)
1028         throw NoData(line, start);
1029     switch (kind) {
1030     case Responses::CAPABILITY:
1031     {
1032         QStringList capabilities;
1033         QList<QByteArray> list = line.mid(start).split(' ');
1034         for (QList<QByteArray>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) {
1035             QByteArray str = *it;
1036             if (str.endsWith("\r\n"))
1037                 str.chop(2);
1038             capabilities << QString::fromUtf8(str);
1039         }
1040         if (!capabilities.count())
1041             throw NoData(line, start);
1042         return QSharedPointer<Responses::AbstractResponse>(
1043                    new Responses::Capability(capabilities));
1044     }
1045     case Responses::OK:
1046     case Responses::NO:
1047     case Responses::BAD:
1048     case Responses::PREAUTH:
1049     case Responses::BYE:
1050         return QSharedPointer<Responses::AbstractResponse>(
1051                    new Responses::State(QByteArray(), kind, line, start));
1052     case Responses::LIST:
1053     case Responses::LSUB:
1054         return QSharedPointer<Responses::AbstractResponse>(
1055                    new Responses::List(kind, line, start));
1056     case Responses::FLAGS:
1057         return QSharedPointer<Responses::AbstractResponse>(
1058                    new Responses::Flags(line, start));
1059     case Responses::SEARCH:
1060         return QSharedPointer<Responses::AbstractResponse>(
1061                    new Responses::Search(line, start));
1062     case Responses::ESEARCH:
1063         return QSharedPointer<Responses::AbstractResponse>(
1064                    new Responses::ESearch(line, start));
1065     case Responses::STATUS:
1066         return QSharedPointer<Responses::AbstractResponse>(
1067                    new Responses::Status(line, start));
1068     case Responses::NAMESPACE:
1069         return QSharedPointer<Responses::AbstractResponse>(
1070                    new Responses::Namespace(line, start));
1071     case Responses::SORT:
1072         return QSharedPointer<Responses::AbstractResponse>(
1073                    new Responses::Sort(line, start));
1074     case Responses::THREAD:
1075         return QSharedPointer<Responses::AbstractResponse>(
1076                    new Responses::Thread(line, start));
1077     case Responses::ID:
1078         return QSharedPointer<Responses::AbstractResponse>(
1079                    new Responses::Id(line, start));
1080     case Responses::ENABLED:
1081         return QSharedPointer<Responses::AbstractResponse>(
1082                     new Responses::Enabled(line, start));
1083     case Responses::VANISHED:
1084         return QSharedPointer<Responses::AbstractResponse>(
1085                     new Responses::Vanished(line, start));
1086     case Responses::GENURLAUTH:
1087         return QSharedPointer<Responses::AbstractResponse>(
1088                     new Responses::GenUrlAuth(line, start));
1089 
1090 
1091         // Those already handled above follow here
1092     case Responses::EXPUNGE:
1093     case Responses::FETCH:
1094     case Responses::EXISTS:
1095     case Responses::RECENT:
1096         throw UnexpectedHere("Malformed response: the number should go first", line, start);
1097     }
1098     throw UnexpectedHere(line, start);
1099 }
1100 
parseTagged(const QByteArray & line)1101 QSharedPointer<Responses::AbstractResponse> Parser::parseTagged(const QByteArray &line)
1102 {
1103     int pos = 0;
1104     const QByteArray tag = LowLevelParser::getAtom(line, pos);
1105     ++pos;
1106     const Responses::Kind kind = Responses::kindFromString(LowLevelParser::getAtom(line, pos));
1107     ++pos;
1108 
1109     if (compressDeflateInProgress && compressDeflateCommand == tag + ' ') {
1110         switch (kind) {
1111         case Responses::OK:
1112             socket->startDeflate();
1113             compressDeflateInProgress = false;
1114             compressDeflateCommand.clear();
1115             break;
1116         default:
1117             // do nothing
1118             break;
1119         }
1120         compressDeflateInProgress = false;
1121         compressDeflateCommand.clear();
1122         QTimer::singleShot(0, this, SLOT(handleCompressionPossibleActivated()));
1123     }
1124 
1125     return QSharedPointer<Responses::AbstractResponse>(
1126                new Responses::State(tag, kind, line, pos));
1127 }
1128 
enableLiteralPlus(const LiteralPlus mode)1129 void Parser::enableLiteralPlus(const LiteralPlus mode)
1130 {
1131     m_literalPlus = mode;
1132 }
1133 
handleDisconnected(const QString & reason)1134 void Parser::handleDisconnected(const QString &reason)
1135 {
1136     emit lineReceived(this, "*** Socket disconnected: " + reason.toUtf8());
1137 #ifdef PRINT_TRAFFIC_TX
1138     qDebug() << m_parserId << "*** Socket disconnected";
1139 #endif
1140     queueResponse(QSharedPointer<Responses::AbstractResponse>(new Responses::SocketDisconnectedResponse(reason)));
1141 }
1142 
~Parser()1143 Parser::~Parser()
1144 {
1145     // We want to prevent nasty signals from the underlying socket from
1146     // interfering with this object -- some of our local data might have
1147     // been already destroyed!
1148     socket->disconnect(this);
1149     socket->close();
1150     socket->deleteLater();
1151 }
1152 
parserId() const1153 uint Parser::parserId() const
1154 {
1155     return m_parserId;
1156 }
1157 
slotSocketStateChanged(const Imap::ConnectionState connState,const QString & message)1158 void Parser::slotSocketStateChanged(const Imap::ConnectionState connState, const QString &message)
1159 {
1160     if (connState == CONN_STATE_CONNECTED_PRETLS_PRECAPS) {
1161 #ifdef PRINT_TRAFFIC_TX
1162         qDebug() << m_parserId << "*** Connection established";
1163 #endif
1164         emit lineReceived(this, "*** Connection established");
1165         waitingForConnection = false;
1166         QTimer::singleShot(0, this, SLOT(executeCommands()));
1167     } else if (connState == CONN_STATE_AUTHENTICATED) {
1168         // unit tests: don't wait for the initial untagged response greetings
1169         m_expectsInitialGreeting = false;
1170     }
1171     emit lineReceived(this, "*** " + message.toUtf8());
1172     emit connectionStateChanged(this, connState);
1173 }
1174 
1175 }
1176