KIMAP

imapstreamparser.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2009 Andras Mantia <amantia@kde.org>
4
5 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "imapstreamparser.h"
12
13#include <QIODevice>
14#include <ctype.h>
15
16using namespace KIMAP;
17
18ImapStreamParser::ImapStreamParser(QIODevice *socket, bool serverModeEnabled)
19 : m_position(0)
20 , m_literalSize(0)
21{
22 m_socket = socket;
23 m_isServerModeEnabled = serverModeEnabled;
24}
25
27{
28 QByteArray tmp;
29 tmp = readString();
30 QString result = QString::fromUtf8(tmp);
31 return result;
32}
33
35{
36 QByteArray result;
37 if (!waitForMoreData(m_data.isEmpty())) {
38 throw ImapParserException("Unable to read more data");
39 }
40 stripLeadingSpaces();
41 if (!waitForMoreData(m_position >= m_data.length())) {
42 throw ImapParserException("Unable to read more data");
43 }
44
45 // literal string
46 // TODO: error handling
47 if (hasLiteral()) {
48 while (!atLiteralEnd()) {
49 result += readLiteralPart();
50 }
51 return result;
52 }
53
54 // quoted string
55 return parseQuotedString();
56}
57
59{
60 if (!waitForMoreData(m_position >= m_data.length())) {
61 throw ImapParserException("Unable to read more data");
62 }
63 int savedPos = m_position;
64 stripLeadingSpaces();
65 int pos = m_position;
66 m_position = savedPos;
67 const char dataChar = m_data.at(pos);
68 if (dataChar == '{') {
69 return true; // literal string
70 } else if (dataChar == '"') {
71 return true; // quoted string
72 } else if (dataChar != ' ' && dataChar != '(' && dataChar != ')' && dataChar != '[' && dataChar != ']' && dataChar != '\n' && dataChar != '\r') {
73 return true; // unquoted string
74 }
75
76 return false; // something else, not a string
77}
78
80{
81 if (!waitForMoreData(m_position >= m_data.length())) {
82 throw ImapParserException("Unable to read more data");
83 }
84 int savedPos = m_position;
85 stripLeadingSpaces();
86 if (m_data.at(m_position) == '{') {
87 int end = -1;
88 do {
89 end = m_data.indexOf('}', m_position);
90 if (!waitForMoreData(end == -1)) {
91 throw ImapParserException("Unable to read more data");
92 }
93 } while (end == -1);
94 Q_ASSERT(end > m_position);
95 m_literalSize = m_data.mid(m_position + 1, end - m_position - 1).toInt();
96 // strip CRLF
97 m_position = end + 1;
98 // ensure that the CRLF is available
99 if (!waitForMoreData(m_position + 1 >= m_data.length())) {
100 throw ImapParserException("Unable to read more data");
101 }
102 if (m_position < m_data.length() && m_data.at(m_position) == '\r') {
103 ++m_position;
104 }
105 if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
106 ++m_position;
107 }
108
109 // FIXME: Makes sense only on the server side?
110 if (m_isServerModeEnabled && m_literalSize > 0) {
111 sendContinuationResponse(m_literalSize);
112 }
113 return true;
114 } else {
115 m_position = savedPos;
116 return false;
117 }
118}
119
121{
122 return (m_literalSize == 0);
123}
124
126{
127 static const qint64 maxLiteralPartSize = 4096;
128 int size = qMin(maxLiteralPartSize, m_literalSize);
129
130 if (!waitForMoreData(m_data.length() < m_position + size)) {
131 throw ImapParserException("Unable to read more data");
132 }
133
134 if (m_data.length() < m_position + size) { // Still not enough data
135 // Take what's already there
136 size = m_data.length() - m_position;
137 }
138
139 QByteArray result = m_data.mid(m_position, size);
140 m_position += size;
141 m_literalSize -= size;
142 Q_ASSERT(m_literalSize >= 0);
143 trimBuffer();
144
145 return result;
146}
147
149{
150 if (!waitForMoreData(m_position >= m_data.length())) {
151 throw ImapParserException("Unable to read more data");
152 }
153 int savedPos = m_position;
154 stripLeadingSpaces();
155 int pos = m_position;
156 m_position = savedPos;
157 if (m_data.at(pos) == '(') {
158 return true;
159 }
160 return false;
161}
162
164{
165 if (!waitForMoreData(m_position >= m_data.length())) {
166 throw ImapParserException("Unable to read more data");
167 }
168 int savedPos = m_position;
169 stripLeadingSpaces();
170 int pos = m_position;
171 m_position = savedPos;
172 if (m_data.at(pos) == ')') {
173 m_position = pos + 1;
174 return true;
175 }
176 return false;
177}
178
180{
181 QList<QByteArray> result;
182 if (!waitForMoreData(m_data.length() <= m_position)) {
183 throw ImapParserException("Unable to read more data");
184 }
185
186 stripLeadingSpaces();
187 if (m_data.at(m_position) != '(') {
188 return result; // no list found
189 }
190
191 bool concatToLast = false;
192 int count = 0;
193 int sublistbegin = m_position;
194 int i = m_position + 1;
195 for (;;) {
196 if (!waitForMoreData(m_data.length() <= i)) {
197 m_position = i;
198 throw ImapParserException("Unable to read more data");
199 }
200 if (m_data.at(i) == '(') {
201 ++count;
202 if (count == 1) {
203 sublistbegin = i;
204 }
205 ++i;
206 continue;
207 }
208 if (m_data.at(i) == ')') {
209 if (count <= 0) {
210 m_position = i + 1;
211 return result;
212 }
213 if (count == 1) {
214 result.append(m_data.mid(sublistbegin, i - sublistbegin + 1));
215 }
216 --count;
217 ++i;
218 continue;
219 }
220 if (m_data.at(i) == ' ') {
221 ++i;
222 continue;
223 }
224 if (m_data.at(i) == '"') {
225 if (count > 0) {
226 m_position = i;
227 parseQuotedString();
228 i = m_position;
229 continue;
230 }
231 }
232 if (m_data.at(i) == '[') {
233 concatToLast = true;
234 if (result.isEmpty()) {
235 result.append(QByteArray());
236 }
237 result.last() += '[';
238 ++i;
239 continue;
240 }
241 if (m_data.at(i) == ']') {
242 concatToLast = false;
243 result.last() += ']';
244 ++i;
245 continue;
246 }
247 if (count == 0) {
248 m_position = i;
249 QByteArray ba;
250 if (hasLiteral()) {
251 while (!atLiteralEnd()) {
252 ba += readLiteralPart();
253 }
254 } else {
255 ba = readString();
256 }
257
258 // We might sometime get some unwanted CRLF, but we're still not at the end
259 // of the list, would make further string reads fail so eat the CRLFs.
260 while ((m_position < m_data.size()) && (m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n')) {
261 m_position++;
262 }
263
264 i = m_position - 1;
265 if (concatToLast) {
266 result.last() += ba;
267 } else {
268 result.append(ba);
269 }
270 }
271 ++i;
272 }
273
274 throw ImapParserException("Something went very very wrong!");
275}
276
278{
279 if (!waitForMoreData(m_position >= m_data.length())) {
280 throw ImapParserException("Unable to read more data");
281 }
282 int savedPos = m_position;
283 stripLeadingSpaces();
284 int pos = m_position;
285 m_position = savedPos;
286 if (m_data.at(pos) == '[') {
287 m_position = pos + 1;
288 return true;
289 }
290 return false;
291}
292
294{
295 if (!waitForMoreData(m_position >= m_data.length())) {
296 throw ImapParserException("Unable to read more data");
297 }
298 int savedPos = m_position;
299 stripLeadingSpaces();
300 int pos = m_position;
301 m_position = savedPos;
302 if (m_data.at(pos) == ']') {
303 m_position = pos + 1;
304 return true;
305 }
306 return false;
307}
308
309QByteArray ImapStreamParser::parseQuotedString()
310{
311 QByteArray result;
312 if (!waitForMoreData(m_data.length() == 0)) {
313 throw ImapParserException("Unable to read more data");
314 }
315 stripLeadingSpaces();
316 int end = m_position;
317 result.clear();
318 if (!waitForMoreData(m_position >= m_data.length())) {
319 throw ImapParserException("Unable to read more data");
320 }
321 if (!waitForMoreData(m_position >= m_data.length())) {
322 throw ImapParserException("Unable to read more data");
323 }
324
325 bool foundSlash = false;
326 // quoted string
327 if (m_data.at(m_position) == '"') {
328 ++m_position;
329 int i = m_position;
330 for (;;) {
331 if (!waitForMoreData(m_data.length() <= i)) {
332 m_position = i;
333 throw ImapParserException("Unable to read more data");
334 }
335 if (m_data.at(i) == '\\') {
336 i += 2;
337 foundSlash = true;
338 continue;
339 }
340 if (m_data.at(i) == '"') {
341 result = m_data.mid(m_position, i - m_position);
342 end = i + 1; // skip the '"'
343 break;
344 }
345 ++i;
346 }
347 }
348
349 // unquoted string
350 else {
351 bool reachedInputEnd = true;
352 int i = m_position;
353 for (;;) {
354 if (!waitForMoreData(m_data.length() <= i)) {
355 m_position = i;
356 throw ImapParserException("Unable to read more data");
357 }
358 if (m_data.at(i) == ' ' || m_data.at(i) == '(' || m_data.at(i) == ')' || m_data.at(i) == '[' || m_data.at(i) == ']' || m_data.at(i) == '\n'
359 || m_data.at(i) == '\r' || m_data.at(i) == '"') {
360 end = i;
361 reachedInputEnd = false;
362 break;
363 }
364 if (m_data.at(i) == '\\') {
365 foundSlash = true;
366 }
367 i++;
368 }
369 if (reachedInputEnd) { // FIXME: how can it get here?
370 end = m_data.length();
371 }
372
373 result = m_data.mid(m_position, end - m_position);
374 }
375
376 // strip quotes
377 if (foundSlash) {
378 while (result.contains("\\\"")) {
379 result.replace("\\\"", "\"");
380 }
381 while (result.contains("\\\\")) {
382 result.replace("\\\\", "\\");
383 }
384 }
385 m_position = end;
386 return result;
387}
388
390{
391 qint64 result;
392 if (ok) {
393 *ok = false;
394 }
395 if (!waitForMoreData(m_data.length() == 0)) {
396 throw ImapParserException("Unable to read more data");
397 }
398 stripLeadingSpaces();
399 if (!waitForMoreData(m_position >= m_data.length())) {
400 throw ImapParserException("Unable to read more data");
401 }
402 if (m_position >= m_data.length()) {
403 throw ImapParserException("Unable to read more data");
404 }
405 int i = m_position;
406 for (;;) {
407 if (!waitForMoreData(m_data.length() <= i)) {
408 m_position = i;
409 throw ImapParserException("Unable to read more data");
410 }
411 if (!isdigit(m_data.at(i))) {
412 break;
413 }
414 ++i;
415 }
416 const auto tmp = QByteArrayView(m_data).mid(m_position, i - m_position);
417 result = tmp.toLongLong(ok);
418 m_position = i;
419 return result;
420}
421
422void ImapStreamParser::stripLeadingSpaces()
423{
424 for (int i = m_position; i < m_data.length(); ++i) {
425 if (m_data.at(i) != ' ') {
426 m_position = i;
427 return;
428 }
429 }
430 m_position = m_data.length();
431}
432
433bool ImapStreamParser::waitForMoreData(bool wait)
434{
435 if (wait) {
436 if (m_socket->bytesAvailable() > 0 || m_socket->waitForReadyRead(30000)) {
437 m_data.append(m_socket->readAll());
438 } else {
439 return false;
440 }
441 }
442 return true;
443}
444
445void ImapStreamParser::setData(const QByteArray &data)
446{
447 m_data = data;
448}
449
451{
452 return m_data.mid(m_position);
453}
454
455int ImapStreamParser::availableDataSize() const
456{
457 return m_socket->bytesAvailable() + m_data.size() - m_position;
458}
459
461{
462 int savedPos = m_position;
463 do {
464 if (!waitForMoreData(m_position >= m_data.length())) {
465 throw ImapParserException("Unable to read more data");
466 }
467 stripLeadingSpaces();
468 } while (m_position >= m_data.size());
469
470 if (m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') {
471 if (m_data.at(m_position) == '\r') {
472 ++m_position;
473 }
474 if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
475 ++m_position;
476 }
477
478 // We'd better empty m_data from time to time before it grows out of control
479 trimBuffer();
480
481 return true; // command end
482 }
483 m_position = savedPos;
484 return false; // something else
485}
486
488{
489 QByteArray result;
490 int i = m_position;
491 int paranthesisBalance = 0;
492 for (;;) {
493 if (!waitForMoreData(m_data.length() <= i)) {
494 m_position = i;
495 throw ImapParserException("Unable to read more data");
496 }
497 if (m_data.at(i) == '{') {
498 m_position = i - 1;
499 hasLiteral(); // init literal size
500 result.append(QByteArrayView(m_data).mid(i, m_position + 1));
501 while (!atLiteralEnd()) {
502 result.append(readLiteralPart());
503 }
504 i = m_position;
505 }
506 if (m_data.at(i) == '(') {
507 paranthesisBalance++;
508 }
509 if (m_data.at(i) == ')') {
510 paranthesisBalance--;
511 }
512 if ((i == m_data.length() && paranthesisBalance == 0) || m_data.at(i) == '\n' || m_data.at(i) == '\r') {
513 break; // command end
514 }
515 result.append(m_data.at(i));
516 ++i;
517 }
518 m_position = i;
519 atCommandEnd();
520 return result;
521}
522
523void ImapStreamParser::sendContinuationResponse(qint64 size)
524{
525 QByteArray block = "+ Ready for literal data (expecting " + QByteArray::number(size) + " bytes)\r\n";
526 m_socket->write(block);
527 m_socket->waitForBytesWritten(30000);
528}
529
530void ImapStreamParser::trimBuffer()
531{
532 if (m_position < 4096) { // right() is expensive, so don't do it for every line
533 return;
534 }
535 m_data = std::move(m_data).right(m_data.size() - m_position);
536 m_position = 0;
537}
qint64 readNumber(bool *ok=nullptr)
Get the next data as a number.
ImapStreamParser(QIODevice *socket, bool serverModeEnabled=false)
Construct the parser.
QList< QByteArray > readParenthesizedList()
Get he next parenthesized list.
bool atListEnd()
Check if the next data is a parenthesized list end.
bool hasList()
Check if the next data is a parenthesized list.
bool atResponseCodeEnd()
Check if the next data is a response code end.
bool atCommandEnd()
Check if the command end was reached.
bool atLiteralEnd() const
Check if the literal data end was reached.
QByteArray readRemainingData()
Return all the data that was read from the socket, but not processed yet.
bool hasLiteral()
Check if the next data is a literal data or not.
QByteArray readString()
Same as above, but without decoding it to utf8.
bool hasString()
Check if the next data is a string or not.
QByteArray readUntilCommandEnd()
Return everything that remained from the command.
bool hasResponseCode()
Check if the next data is a response code.
QString readUtf8String()
Get a string from the message.
QByteArray readLiteralPart()
Read the next literal sequence.
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QByteArray & append(QByteArrayView data)
char at(qsizetype i) const const
void clear()
bool contains(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QByteArray right(qsizetype len) const const
qsizetype size() const const
int toInt(bool *ok, int base) const const
QByteArrayView mid(qsizetype start, qsizetype length) const const
virtual qint64 bytesAvailable() const const
QByteArray readAll()
virtual bool waitForBytesWritten(int msecs)
virtual bool waitForReadyRead(int msecs)
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
bool isEmpty() const const
T & last()
QString fromUtf8(QByteArrayView str)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 12:00:39 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.