KIMAP

imapstreamparser.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2007 Volker Krause <[email protected]>
3  SPDX-FileCopyrightText: 2009 Andras Mantia <[email protected]>
4 
5  SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
6  SPDX-FileContributor: Kevin Ottens <[email protected]>
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 
16 using namespace KIMAP;
17 
18 ImapStreamParser::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 
309 QByteArray 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 QByteArray tmp = m_data.mid(m_position, i - m_position);
417  result = tmp.toLongLong(ok);
418  m_position = i;
419  return result;
420 }
421 
422 void 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 
433 bool 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 
445 void ImapStreamParser::setData(const QByteArray &data)
446 {
447  m_data = data;
448 }
449 
451 {
452  return m_data.mid(m_position);
453 }
454 
455 int 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(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 
523 void 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 
530 void 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 = m_data.right(m_data.size() - m_position);
536  m_position = 0;
537 }
QString readUtf8String()
Get a string from the message.
void append(const T &value)
QByteArray readRemainingData()
Return all the data that was read from the socket, but not processed yet.
bool hasResponseCode()
Check if the next data is a response code.
QByteArray right(int len) const const
QString fromUtf8(const char *str, int size)
int indexOf(char ch, int from) const const
QByteArray & append(char ch)
QByteArray readLiteralPart()
Read the next literal sequence.
QByteArray number(int n, int base)
void clear()
char at(int i) const const
virtual bool waitForReadyRead(int msecs)
qlonglong toLongLong(bool *ok, int base) const const
bool atCommandEnd()
Check if the command end was reached.
bool hasList()
Check if the next data is a parenthesized list.
QByteArray mid(int pos, int len) const const
bool hasString()
Check if the next data is a string or not.
bool isEmpty() const const
qint64 readNumber(bool *ok=nullptr)
Get the next data as a number.
bool atResponseCodeEnd()
Check if the next data is a response code end.
bool contains(char ch) const const
QByteArray & replace(int pos, int len, const char *after)
T & last()
QByteArray readUntilCommandEnd()
Return everything that remained from the command.
bool hasLiteral()
Check if the next data is a literal data or not.
int toInt(bool *ok, int base) const const
bool isEmpty() const const
bool atListEnd()
Check if the next data is a parenthesized list end.
int size() const const
virtual qint64 bytesAvailable() const const
bool atLiteralEnd() const
Check if the literal data end was reached.
QByteArray readAll()
int length() const const
QByteArray readString()
Same as above, but without decoding it to utf8.
const QList< QKeySequence > & end()
virtual bool waitForBytesWritten(int msecs)
ImapStreamParser(QIODevice *socket, bool serverModeEnabled=false)
Construct the parser.
qint64 write(const char *data, qint64 maxSize)
QList< QByteArray > readParenthesizedList()
Get he next parenthesized list.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Dec 3 2023 03:51:44 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.