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> ¶ms)
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 ¶m, 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> ¶ms)
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 ¶m, 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 ×tamp)
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 ×tamp)
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