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 <ctype.h>
14 #include <QIODevice>
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 != ' ' &&
73  dataChar != '(' &&
74  dataChar != ')' &&
75  dataChar != '[' &&
76  dataChar != ']' &&
77  dataChar != '\n' &&
78  dataChar != '\r') {
79  return true; //unquoted string
80  }
81 
82  return false; //something else, not a string
83 }
84 
86 {
87  if (!waitForMoreData(m_position >= m_data.length())) {
88  throw ImapParserException("Unable to read more data");
89  }
90  int savedPos = m_position;
91  stripLeadingSpaces();
92  if (m_data.at(m_position) == '{') {
93  int end = -1;
94  do {
95  end = m_data.indexOf('}', m_position);
96  if (!waitForMoreData(end == -1)) {
97  throw ImapParserException("Unable to read more data");
98  }
99  } while (end == -1);
100  Q_ASSERT(end > m_position);
101  m_literalSize = m_data.mid(m_position + 1, end - m_position - 1).toInt();
102  // strip CRLF
103  m_position = end + 1;
104  // ensure that the CRLF is available
105  if (!waitForMoreData(m_position + 1 >= m_data.length())) {
106  throw ImapParserException("Unable to read more data");
107  }
108  if (m_position < m_data.length() && m_data.at(m_position) == '\r') {
109  ++m_position;
110  }
111  if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
112  ++m_position;
113  }
114 
115  //FIXME: Makes sense only on the server side?
116  if (m_isServerModeEnabled && m_literalSize > 0) {
117  sendContinuationResponse(m_literalSize);
118  }
119  return true;
120  } else {
121  m_position = savedPos;
122  return false;
123  }
124 }
125 
127 {
128  return (m_literalSize == 0);
129 }
130 
132 {
133  static const qint64 maxLiteralPartSize = 4096;
134  int size = qMin(maxLiteralPartSize, m_literalSize);
135 
136  if (!waitForMoreData(m_data.length() < m_position + size)) {
137  throw ImapParserException("Unable to read more data");
138  }
139 
140  if (m_data.length() < m_position + size) { // Still not enough data
141  // Take what's already there
142  size = m_data.length() - m_position;
143  }
144 
145  QByteArray result = m_data.mid(m_position, size);
146  m_position += size;
147  m_literalSize -= size;
148  Q_ASSERT(m_literalSize >= 0);
149  trimBuffer();
150 
151  return result;
152 }
153 
155 {
156  if (!waitForMoreData(m_position >= m_data.length())) {
157  throw ImapParserException("Unable to read more data");
158  }
159  int savedPos = m_position;
160  stripLeadingSpaces();
161  int pos = m_position;
162  m_position = savedPos;
163  if (m_data.at(pos) == '(') {
164  return true;
165  }
166  return false;
167 }
168 
170 {
171  if (!waitForMoreData(m_position >= m_data.length())) {
172  throw ImapParserException("Unable to read more data");
173  }
174  int savedPos = m_position;
175  stripLeadingSpaces();
176  int pos = m_position;
177  m_position = savedPos;
178  if (m_data.at(pos) == ')') {
179  m_position = pos + 1;
180  return true;
181  }
182  return false;
183 }
184 
186 {
187  QList<QByteArray> result;
188  if (!waitForMoreData(m_data.length() <= m_position)) {
189  throw ImapParserException("Unable to read more data");
190  }
191 
192  stripLeadingSpaces();
193  if (m_data.at(m_position) != '(') {
194  return result; //no list found
195  }
196 
197  bool concatToLast = false;
198  int count = 0;
199  int sublistbegin = m_position;
200  int i = m_position + 1;
201  Q_FOREVER {
202  if (!waitForMoreData(m_data.length() <= i))
203  {
204  m_position = i;
205  throw ImapParserException("Unable to read more data");
206  }
207  if (m_data.at(i) == '(')
208  {
209  ++count;
210  if (count == 1) {
211  sublistbegin = i;
212  }
213  ++i;
214  continue;
215  }
216  if (m_data.at(i) == ')')
217  {
218  if (count <= 0) {
219  m_position = i + 1;
220  return result;
221  }
222  if (count == 1) {
223  result.append(m_data.mid(sublistbegin, i - sublistbegin + 1));
224  }
225  --count;
226  ++i;
227  continue;
228  }
229  if (m_data.at(i) == ' ')
230  {
231  ++i;
232  continue;
233  }
234  if (m_data.at(i) == '"')
235  {
236  if (count > 0) {
237  m_position = i;
238  parseQuotedString();
239  i = m_position;
240  continue;
241  }
242  }
243  if (m_data.at(i) == '[')
244  {
245  concatToLast = true;
246  if (result.isEmpty()) {
247  result.append(QByteArray());
248  }
249  result.last() += '[';
250  ++i;
251  continue;
252  }
253  if (m_data.at(i) == ']')
254  {
255  concatToLast = false;
256  result.last() += ']';
257  ++i;
258  continue;
259  }
260  if (count == 0)
261  {
262  m_position = i;
263  QByteArray ba;
264  if (hasLiteral()) {
265  while (!atLiteralEnd()) {
266  ba += readLiteralPart();
267  }
268  } else {
269  ba = readString();
270  }
271 
272  // We might sometime get some unwanted CRLF, but we're still not at the end
273  // of the list, would make further string reads fail so eat the CRLFs.
274  while ((m_position < m_data.size()) &&
275  (m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n')) {
276  m_position++;
277  }
278 
279  i = m_position - 1;
280  if (concatToLast) {
281  result.last() += ba;
282  } else {
283  result.append(ba);
284  }
285  }
286  ++i;
287  }
288 
289  throw ImapParserException("Something went very very wrong!");
290 }
291 
293 {
294  if (!waitForMoreData(m_position >= m_data.length())) {
295  throw ImapParserException("Unable to read more data");
296  }
297  int savedPos = m_position;
298  stripLeadingSpaces();
299  int pos = m_position;
300  m_position = savedPos;
301  if (m_data.at(pos) == '[') {
302  m_position = pos + 1;
303  return true;
304  }
305  return false;
306 }
307 
309 {
310  if (!waitForMoreData(m_position >= m_data.length())) {
311  throw ImapParserException("Unable to read more data");
312  }
313  int savedPos = m_position;
314  stripLeadingSpaces();
315  int pos = m_position;
316  m_position = savedPos;
317  if (m_data.at(pos) == ']') {
318  m_position = pos + 1;
319  return true;
320  }
321  return false;
322 }
323 
324 QByteArray ImapStreamParser::parseQuotedString()
325 {
326  QByteArray result;
327  if (!waitForMoreData(m_data.length() == 0)) {
328  throw ImapParserException("Unable to read more data");
329  }
330  stripLeadingSpaces();
331  int end = m_position;
332  result.clear();
333  if (!waitForMoreData(m_position >= m_data.length())) {
334  throw ImapParserException("Unable to read more data");
335  }
336  if (!waitForMoreData(m_position >= m_data.length())) {
337  throw ImapParserException("Unable to read more data");
338  }
339 
340  bool foundSlash = false;
341  // quoted string
342  if (m_data.at(m_position) == '"') {
343  ++m_position;
344  int i = m_position;
345  Q_FOREVER {
346  if (!waitForMoreData(m_data.length() <= i))
347  {
348  m_position = i;
349  throw ImapParserException("Unable to read more data");
350  }
351  if (m_data.at(i) == '\\')
352  {
353  i += 2;
354  foundSlash = true;
355  continue;
356  }
357  if (m_data.at(i) == '"')
358  {
359  result = m_data.mid(m_position, i - m_position);
360  end = i + 1; // skip the '"'
361  break;
362  }
363  ++i;
364  }
365  }
366 
367  // unquoted string
368  else {
369  bool reachedInputEnd = true;
370  int i = m_position;
371  Q_FOREVER {
372  if (!waitForMoreData(m_data.length() <= i))
373  {
374  m_position = i;
375  throw ImapParserException("Unable to read more data");
376  }
377  if (m_data.at(i) == ' ' ||
378  m_data.at(i) == '(' ||
379  m_data.at(i) == ')' ||
380  m_data.at(i) == '[' ||
381  m_data.at(i) == ']' ||
382  m_data.at(i) == '\n' ||
383  m_data.at(i) == '\r' ||
384  m_data.at(i) == '"')
385  {
386  end = i;
387  reachedInputEnd = false;
388  break;
389  }
390  if (m_data.at(i) == '\\')
391  {
392  foundSlash = true;
393  }
394  i++;
395  }
396  if (reachedInputEnd) { //FIXME: how can it get here?
397  end = m_data.length();
398  }
399 
400  result = m_data.mid(m_position, end - m_position);
401  }
402 
403  // strip quotes
404  if (foundSlash) {
405  while (result.contains("\\\"")) {
406  result.replace("\\\"", "\"");
407  }
408  while (result.contains("\\\\")) {
409  result.replace("\\\\", "\\");
410  }
411  }
412  m_position = end;
413  return result;
414 }
415 
417 {
418  qint64 result;
419  if (ok) {
420  *ok = false;
421  }
422  if (!waitForMoreData(m_data.length() == 0)) {
423  throw ImapParserException("Unable to read more data");
424  }
425  stripLeadingSpaces();
426  if (!waitForMoreData(m_position >= m_data.length())) {
427  throw ImapParserException("Unable to read more data");
428  }
429  if (m_position >= m_data.length()) {
430  throw ImapParserException("Unable to read more data");
431  }
432  int i = m_position;
433  Q_FOREVER {
434  if (!waitForMoreData(m_data.length() <= i))
435  {
436  m_position = i;
437  throw ImapParserException("Unable to read more data");
438  }
439  if (!isdigit(m_data.at(i)))
440  {
441  break;
442  }
443  ++i;
444  }
445  const QByteArray tmp = m_data.mid(m_position, i - m_position);
446  result = tmp.toLongLong(ok);
447  m_position = i;
448  return result;
449 }
450 
451 void ImapStreamParser::stripLeadingSpaces()
452 {
453  for (int i = m_position; i < m_data.length(); ++i) {
454  if (m_data.at(i) != ' ') {
455  m_position = i;
456  return;
457  }
458  }
459  m_position = m_data.length();
460 }
461 
462 bool ImapStreamParser::waitForMoreData(bool wait)
463 {
464  if (wait) {
465  if (m_socket->bytesAvailable() > 0 ||
466  m_socket->waitForReadyRead(30000)) {
467  m_data.append(m_socket->readAll());
468  } else {
469  return false;
470  }
471  }
472  return true;
473 }
474 
475 void ImapStreamParser::setData(const QByteArray &data)
476 {
477  m_data = data;
478 }
479 
481 {
482  return m_data.mid(m_position);
483 }
484 
485 int ImapStreamParser::availableDataSize() const
486 {
487  return m_socket->bytesAvailable() + m_data.size() - m_position;
488 }
489 
491 {
492  int savedPos = m_position;
493  do {
494  if (!waitForMoreData(m_position >= m_data.length())) {
495  throw ImapParserException("Unable to read more data");
496  }
497  stripLeadingSpaces();
498  } while (m_position >= m_data.size());
499 
500  if (m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') {
501  if (m_data.at(m_position) == '\r') {
502  ++m_position;
503  }
504  if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
505  ++m_position;
506  }
507 
508  // We'd better empty m_data from time to time before it grows out of control
509  trimBuffer();
510 
511  return true; //command end
512  }
513  m_position = savedPos;
514  return false; //something else
515 }
516 
518 {
519  QByteArray result;
520  int i = m_position;
521  int paranthesisBalance = 0;
522  Q_FOREVER {
523  if (!waitForMoreData(m_data.length() <= i))
524  {
525  m_position = i;
526  throw ImapParserException("Unable to read more data");
527  }
528  if (m_data.at(i) == '{')
529  {
530  m_position = i - 1;
531  hasLiteral(); //init literal size
532  result.append( m_data.mid( i, m_position + 1 ) );
533  while (!atLiteralEnd()) {
534  result.append(readLiteralPart());
535  }
536  i = m_position;
537  }
538  if (m_data.at(i) == '(')
539  {
540  paranthesisBalance++;
541  }
542  if (m_data.at(i) == ')')
543  {
544  paranthesisBalance--;
545  }
546  if ((i == m_data.length() && paranthesisBalance == 0) ||
547  m_data.at(i) == '\n' || m_data.at(i) == '\r')
548  {
549  break; //command end
550  }
551  result.append(m_data.at(i));
552  ++i;
553  }
554  m_position = i;
555  atCommandEnd();
556  return result;
557 }
558 
559 void ImapStreamParser::sendContinuationResponse(qint64 size)
560 {
561  QByteArray block = "+ Ready for literal data (expecting " +
562  QByteArray::number(size) + " bytes)\r\n";
563  m_socket->write(block);
564  m_socket->waitForBytesWritten(30000);
565 }
566 
567 void ImapStreamParser::trimBuffer()
568 {
569  if (m_position < 4096) { // right() is expensive, so don't do it for every line
570  return;
571  }
572  m_data = m_data.right(m_data.size() - m_position);
573  m_position = 0;
574 }
QByteArray readUntilCommandEnd()
Return everything that remained from the command.
void clear()
int toInt(bool *ok, int base) const const
char at(int i) const const
QByteArray readLiteralPart()
Read the next literal sequence.
bool isEmpty() const const
bool atLiteralEnd() const
Check if the literal data end was reached.
bool hasList()
Check if the next data is a parenthesized list.
int length() const const
bool hasString()
Check if the next data is a string or not.
bool atCommandEnd()
Check if the command end was reached.
int indexOf(char ch, int from) const const
qint64 readNumber(bool *ok=nullptr)
Get the next data as a number.
virtual bool waitForBytesWritten(int msecs)
void append(const T &value)
QString fromUtf8(const char *str, int size)
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.
bool hasLiteral()
Check if the next data is a literal data or not.
bool isEmpty() const const
QByteArray number(int n, int base)
QByteArray readAll()
QByteArray right(int len) const const
QByteArray & replace(int pos, int len, const char *after)
virtual bool waitForReadyRead(int msecs)
QByteArray readString()
Same as above, but without decoding it to utf8.
QByteArray mid(int pos, int len) const const
virtual qint64 bytesAvailable() const const
QByteArray & append(char ch)
bool atListEnd()
Check if the next data is a parenthesized list end.
qlonglong toLongLong(bool *ok, int base) const const
QList< QByteArray > readParenthesizedList()
Get he next parenthesized list.
T & last()
bool contains(char ch) const const
bool atResponseCodeEnd()
Check if the next data is a response code end.
qint64 write(const char *data, qint64 maxSize)
QString readUtf8String()
Get a string from the message.
Definition: acl.cpp:12
int size() const const
ImapStreamParser(QIODevice *socket, bool serverModeEnabled=false)
Construct the parser.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sat Oct 24 2020 23:16:51 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.