Akonadi

imapparser.cpp
1 /*
2  Copyright (c) 2006 - 2007 Volker Krause <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "imapparser_p.h"
21 
22 #include <QDateTime>
23 
24 #include <ctype.h>
25 
26 using namespace Akonadi;
27 
28 class ImapParser::Private
29 {
30 public:
31  QByteArray tagBuffer;
32  QByteArray dataBuffer;
33  int parenthesesCount;
34  qint64 literalSize;
35  bool continuation;
36 
37  // returns true if readBuffer contains a literal start and sets
38  // parser state accordingly
39  bool checkLiteralStart(const QByteArray &readBuffer, int pos = 0)
40  {
41  if (readBuffer.trimmed().endsWith('}')) {
42  const int begin = readBuffer.lastIndexOf('{');
43  const int end = readBuffer.lastIndexOf('}');
44 
45  // new literal in previous literal data block
46  if (begin < pos) {
47  return false;
48  }
49 
50  // TODO error handling
51  literalSize = readBuffer.mid(begin + 1, end - begin - 1).toLongLong();
52 
53  // empty literal
54  if (literalSize == 0) {
55  return false;
56  }
57 
58  continuation = true;
59  dataBuffer.reserve(dataBuffer.size() + literalSize + 1);
60  return true;
61  }
62  return false;
63  }
64 };
65 
66 namespace
67 {
68 
69 template <typename T>
70 int parseParenthesizedListHelper(const QByteArray &data, T &result, int start)
71 {
72  result.clear();
73  if (start >= data.length()) {
74  return data.length();
75  }
76 
77  const int begin = data.indexOf('(', start);
78  if (begin < 0) {
79  return start;
80  }
81 
82  result.reserve(16);
83 
84  int count = 0;
85  int sublistBegin = start;
86  bool insideQuote = false;
87  for (int i = begin + 1; i < data.length(); ++i) {
88  const char currentChar = data[i];
89  if (currentChar == '(' && !insideQuote) {
90  ++count;
91  if (count == 1) {
92  sublistBegin = i;
93  }
94 
95  continue;
96  }
97 
98  if (currentChar == ')' && !insideQuote) {
99  if (count <= 0) {
100  return i + 1;
101  }
102 
103  if (count == 1) {
104  result.append(data.mid(sublistBegin, i - sublistBegin + 1));
105  }
106 
107  --count;
108  continue;
109  }
110 
111  if (currentChar == ' ' || currentChar == '\n' || currentChar == '\r') {
112  continue;
113  }
114 
115  if (count == 0) {
116  QByteArray ba;
117  const int consumed = ImapParser::parseString(data, ba, i);
118  i = consumed - 1; // compensate for the for loop increment
119  result.append(ba);
120  } else if (count > 0) {
121  if (currentChar == '"') {
122  insideQuote = !insideQuote;
123  } else if (currentChar == '\\' && insideQuote) {
124  ++i;
125  continue;
126  }
127  }
128  }
129 
130  return data.length();
131 }
132 
133 }
134 
135 int ImapParser::parseParenthesizedList(const QByteArray &data, QVarLengthArray<QByteArray, 16> &result, int start)
136 {
137  return parseParenthesizedListHelper(data, result, start);
138 }
139 
140 int ImapParser::parseParenthesizedList(const QByteArray &data, QList<QByteArray> &result, int start)
141 {
142  return parseParenthesizedListHelper(data, result, start);
143 }
144 
145 int ImapParser::parseString(const QByteArray &data, QByteArray &result, int start)
146 {
147  int begin = stripLeadingSpaces(data, start);
148  result.clear();
149  if (begin >= data.length()) {
150  return data.length();
151  }
152 
153  // literal string
154  // TODO: error handling
155  if (data[begin] == '{') {
156  int end = data.indexOf('}', begin);
157  Q_ASSERT(end > begin);
158  int size = data.mid(begin + 1, end - begin - 1).toInt();
159 
160  // strip CRLF
161  begin = end + 1;
162  if (begin < data.length() && data[begin] == '\r') {
163  ++begin;
164  }
165  if (begin < data.length() && data[begin] == '\n') {
166  ++begin;
167  }
168 
169  end = begin + size;
170  result = data.mid(begin, end - begin);
171  return end;
172  }
173 
174  // quoted string
175  return parseQuotedString(data, result, begin);
176 }
177 
178 int ImapParser::parseQuotedString(const QByteArray &data, QByteArray &result, int start)
179 {
180  int begin = stripLeadingSpaces(data, start);
181  int end = begin;
182  result.clear();
183  if (begin >= data.length()) {
184  return data.length();
185  }
186 
187  bool foundSlash = false;
188  // quoted string
189  if (data[begin] == '"') {
190  ++begin;
191  result.reserve(qMin(32, data.size() - begin));
192  for (int i = begin; i < data.length(); ++i) {
193  const char ch = data.at(i);
194  if (foundSlash) {
195  foundSlash = false;
196  if (ch == 'r') {
197  result += '\r';
198  } else if (ch == 'n') {
199  result += '\n';
200  } else if (ch == '\\') {
201  result += '\\';
202  } else if (ch == '\"') {
203  result += '\"';
204  } else {
205  //TODO: this is actually an error
206  result += ch;
207  }
208  continue;
209  }
210  if (ch == '\\') {
211  foundSlash = true;
212  continue;
213  }
214  if (ch == '"') {
215  end = i + 1; // skip the '"'
216  break;
217  }
218  result += ch;
219  }
220  } else {
221  // unquoted string
222  bool reachedInputEnd = true;
223  for (int i = begin; i < data.length(); ++i) {
224  const char ch = data.at(i);
225  if (ch == ' ' || ch == '(' || ch == ')' || ch == '\n' || ch == '\r') {
226  end = i;
227  reachedInputEnd = false;
228  break;
229  }
230  if (ch == '\\') {
231  foundSlash = true;
232  }
233  }
234  if (reachedInputEnd) {
235  end = data.length();
236  }
237  result = data.mid(begin, end - begin);
238 
239  // transform unquoted NIL
240  if (result == "NIL") {
241  result.clear();
242  }
243 
244  // strip quotes
245  if (foundSlash) {
246  while (result.contains("\\\"")) {
247  result.replace("\\\"", "\"");
248  }
249  while (result.contains("\\\\")) {
250  result.replace("\\\\", "\\");
251  }
252  }
253  }
254 
255  return end;
256 }
257 
258 int ImapParser::stripLeadingSpaces(const QByteArray &data, int start)
259 {
260  for (int i = start; i < data.length(); ++i) {
261  if (data[i] != ' ') {
262  return i;
263  }
264  }
265 
266  return data.length();
267 }
268 
269 int ImapParser::parenthesesBalance(const QByteArray &data, int start)
270 {
271  int count = 0;
272  bool insideQuote = false;
273  for (int i = start; i < data.length(); ++i) {
274  const char ch = data[i];
275  if (ch == '"') {
276  insideQuote = !insideQuote;
277  continue;
278  }
279  if (ch == '\\' && insideQuote) {
280  ++i;
281  continue;
282  }
283  if (ch == '(' && !insideQuote) {
284  ++count;
285  continue;
286  }
287  if (ch == ')' && !insideQuote) {
288  --count;
289  continue;
290  }
291  }
292  return count;
293 }
294 
295 QByteArray ImapParser::join(const QList<QByteArray> &list, const QByteArray &separator)
296 {
297  // shortcuts for the easy cases
298  if (list.isEmpty()) {
299  return QByteArray();
300  }
301  if (list.size() == 1) {
302  return list.first();
303  }
304 
305  // avoid expensive realloc's by determining the size beforehand
307  const QList<QByteArray>::const_iterator endIt = list.constEnd();
308  int resultSize = (list.size() - 1) * separator.size();
309  for (; it != endIt; ++it) {
310  resultSize += (*it).size();
311  }
312 
313  QByteArray result;
314  result.reserve(resultSize);
315  it = list.constBegin();
316  result += (*it);
317  ++it;
318  for (; it != endIt; ++it) {
319  result += separator;
320  result += (*it);
321  }
322 
323  return result;
324 }
325 
326 QByteArray ImapParser::join(const QSet<QByteArray> &set, const QByteArray &separator)
327 {
329 
330  return ImapParser::join(list, separator);
331 }
332 
333 int ImapParser::parseString(const QByteArray &data, QString &result, int start)
334 {
335  QByteArray tmp;
336  const int end = parseString(data, tmp, start);
337  result = QString::fromUtf8(tmp);
338  return end;
339 }
340 
341 int ImapParser::parseNumber(const QByteArray &data, qint64 &result, bool *ok, int start)
342 {
343  if (ok) {
344  *ok = false;
345  }
346 
347  int pos = stripLeadingSpaces(data, start);
348  if (pos >= data.length()) {
349  return data.length();
350  }
351 
352  int begin = pos;
353  for (; pos < data.length(); ++pos) {
354  if (!isdigit(data.at(pos))) {
355  break;
356  }
357  }
358 
359  const QByteArray tmp = data.mid(begin, pos - begin);
360  result = tmp.toLongLong(ok);
361 
362  return pos;
363 }
364 
365 QByteArray ImapParser::quote(const QByteArray &data)
366 {
367  if (data.isEmpty()) {
368  static const QByteArray empty("\"\"");
369  return empty;
370  }
371 
372  const int inputLength = data.length();
373  int stuffToQuote = 0;
374  for (int i = 0; i < inputLength; ++i) {
375  const char ch = data.at(i);
376  if (ch == '"' || ch == '\\' || ch == '\n' || ch == '\r') {
377  ++stuffToQuote;
378  }
379  }
380 
381  QByteArray result;
382  result.reserve(inputLength + stuffToQuote + 2);
383  result += '"';
384 
385  // shortcut for the case that we don't need to quote anything at all
386  if (stuffToQuote == 0) {
387  result += data;
388  } else {
389  for (int i = 0; i < inputLength; ++i) {
390  const char ch = data.at(i);
391  if (ch == '\n') {
392  result += "\\n";
393  continue;
394  }
395 
396  if (ch == '\r') {
397  result += "\\r";
398  continue;
399  }
400 
401  if (ch == '"' || ch == '\\') {
402  result += '\\';
403  }
404 
405  result += ch;
406  }
407  }
408 
409  result += '"';
410  return result;
411 }
412 
413 int ImapParser::parseSequenceSet(const QByteArray &data, ImapSet &result, int start)
414 {
415  int begin = stripLeadingSpaces(data, start);
416  qint64 value = -1, lower = -1, upper = -1;
417  for (int i = begin; i < data.length(); ++i) {
418  if (data[i] == '*') {
419  value = 0;
420  } else if (data[i] == ':') {
421  lower = value;
422  } else if (isdigit(data[i])) {
423  bool ok = false;
424  i = parseNumber(data, value, &ok, i);
425  Q_ASSERT(ok); // TODO handle error
426  --i;
427  } else {
428  upper = value;
429  if (lower < 0) {
430  lower = value;
431  }
432  result.add(ImapInterval(lower, upper));
433  lower = -1;
434  upper = -1;
435  value = -1;
436  if (data[i] != ',') {
437  return i;
438  }
439  }
440  }
441  // take care of left-overs at input end
442  upper = value;
443  if (lower < 0) {
444  lower = value;
445  }
446 
447  if (lower >= 0 && upper >= 0) {
448  result.add(ImapInterval(lower, upper));
449  }
450 
451  return data.length();
452 }
453 
454 int ImapParser::parseDateTime(const QByteArray &data, QDateTime &dateTime, int start)
455 {
456  // Syntax:
457  // date-time = DQUOTE date-day-fixed "-" date-month "-" date-year
458  // SP time SP zone DQUOTE
459  // date-day-fixed = (SP DIGIT) / 2DIGIT
460  // ; Fixed-format version of date-day
461  // date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
462  // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
463  // date-year = 4DIGIT
464  // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
465  // ; Hours minutes seconds
466  // zone = ("+" / "-") 4DIGIT
467  // ; Signed four-digit value of hhmm representing
468  // ; hours and minutes east of Greenwich (that is,
469  // ; the amount that the given time differs from
470  // ; Universal Time). Subtracting the timezone
471  // ; from the given time will give the UT form.
472  // ; The Universal Time zone is "+0000".
473  // Example : "28-May-2006 01:03:35 +0200"
474  // Position: 0123456789012345678901234567
475  // 1 2
476 
477  int pos = stripLeadingSpaces(data, start);
478  if (data.length() <= pos) {
479  return pos;
480  }
481 
482  bool quoted = false;
483  if (data[pos] == '"') {
484  quoted = true;
485  ++pos;
486 
487  if (data.length() <= pos + 26) {
488  return start;
489  }
490  } else {
491  if (data.length() < pos + 26) {
492  return start;
493  }
494  }
495 
496  bool ok = true;
497  const int day = (data[pos] == ' ' ? data[pos + 1] - '0' // single digit day
498  : data.mid(pos, 2).toInt(&ok));
499  if (!ok) {
500  return start;
501  }
502 
503  pos += 3;
504  static const QByteArray shortMonthNames("janfebmaraprmayjunjulaugsepoctnovdec");
505  int month = shortMonthNames.indexOf(data.mid(pos, 3).toLower());
506  if (month == -1) {
507  return start;
508  }
509 
510  month = month / 3 + 1;
511  pos += 4;
512  const int year = data.mid(pos, 4).toInt(&ok);
513  if (!ok) {
514  return start;
515  }
516 
517  pos += 5;
518  const int hours = data.mid(pos, 2).toInt(&ok);
519  if (!ok) {
520  return start;
521  }
522 
523  pos += 3;
524  const int minutes = data.mid(pos, 2).toInt(&ok);
525  if (!ok) {
526  return start;
527  }
528 
529  pos += 3;
530  const int seconds = data.mid(pos, 2).toInt(&ok);
531  if (!ok) {
532  return start;
533  }
534 
535  pos += 4;
536  const int tzhh = data.mid(pos, 2).toInt(&ok);
537  if (!ok) {
538  return start;
539  }
540 
541  pos += 2;
542  const int tzmm = data.mid(pos, 2).toInt(&ok);
543  if (!ok) {
544  return start;
545  }
546 
547  int tzsecs = tzhh * 60 * 60 + tzmm * 60;
548  if (data[pos - 3] == '-') {
549  tzsecs = -tzsecs;
550  }
551 
552  const QDate date(year, month, day);
553  const QTime time(hours, minutes, seconds);
554  dateTime = QDateTime(date, time, Qt::UTC);
555  if (!dateTime.isValid()) {
556  return start;
557  }
558 
559  dateTime = dateTime.addSecs(-tzsecs);
560 
561  pos += 2;
562  if (data.length() <= pos || !quoted) {
563  return pos;
564  }
565 
566  if (data[pos] == '"') {
567  ++pos;
568  }
569 
570  return pos;
571 }
572 
573 void ImapParser::splitVersionedKey(const QByteArray &data, QByteArray &key, int &version)
574 {
575  const int startPos = data.indexOf('[');
576  const int endPos = data.indexOf(']');
577  if (startPos != -1 && endPos != -1) {
578  if (endPos > startPos) {
579  bool ok = false;
580 
581  version = data.mid(startPos + 1, endPos - startPos - 1).toInt(&ok);
582  if (!ok) {
583  version = 0;
584  }
585 
586  key = data.left(startPos);
587  }
588  } else {
589  key = data;
590  version = 0;
591  }
592 }
593 
594 ImapParser::ImapParser()
595  : d(new Private)
596 {
597  reset();
598 }
599 
600 ImapParser::~ImapParser()
601 {
602  delete d;
603 }
604 
605 bool ImapParser::parseNextLine(const QByteArray &readBuffer)
606 {
607  d->continuation = false;
608 
609  // first line, get the tag
610  if (d->tagBuffer.isEmpty()) {
611  const int startOfData = ImapParser::parseString(readBuffer, d->tagBuffer);
612  if (startOfData < readBuffer.length() && startOfData >= 0) {
613  d->dataBuffer = readBuffer.mid(startOfData + 1);
614  }
615 
616  } else {
617  d->dataBuffer += readBuffer;
618  }
619 
620  // literal read in progress
621  if (d->literalSize > 0) {
622  d->literalSize -= readBuffer.size();
623 
624  // still not everything read
625  if (d->literalSize > 0) {
626  return false;
627  }
628 
629  // check the remaining (non-literal) part for parentheses
630  if (d->literalSize < 0) {
631  // the following looks strange but works since literalSize can be negative here
632  d->parenthesesCount += ImapParser::parenthesesBalance(readBuffer, readBuffer.length() + d->literalSize);
633 
634  // check if another literal read was started
635  if (d->checkLiteralStart(readBuffer, readBuffer.length() + d->literalSize)) {
636  return false;
637  }
638  }
639 
640  // literal string finished but still open parentheses
641  if (d->parenthesesCount > 0) {
642  return false;
643  }
644 
645  } else {
646 
647  // open parentheses
648  d->parenthesesCount += ImapParser::parenthesesBalance(readBuffer);
649 
650  // start new literal read
651  if (d->checkLiteralStart(readBuffer)) {
652  return false;
653  }
654 
655  // still open parentheses
656  if (d->parenthesesCount > 0) {
657  return false;
658  }
659 
660  // just a normal response, fall through
661  }
662 
663  return true;
664 }
665 
666 void ImapParser::parseBlock(const QByteArray &data)
667 {
668  Q_ASSERT(d->literalSize >= data.size());
669  d->literalSize -= data.size();
670  d->dataBuffer += data;
671 }
672 
673 QByteArray ImapParser::tag() const
674 {
675  return d->tagBuffer;
676 }
677 
678 QByteArray ImapParser::data() const
679 {
680  return d->dataBuffer;
681 }
682 
683 void ImapParser::reset()
684 {
685  d->dataBuffer.clear();
686  d->tagBuffer.clear();
687  d->parenthesesCount = 0;
688  d->literalSize = 0;
689  d->continuation = false;
690 }
691 
692 bool ImapParser::continuationStarted() const
693 {
694  return d->continuation;
695 }
696 
697 qint64 ImapParser::continuationSize() const
698 {
699  return d->literalSize;
700 }
void clear()
int toInt(bool *ok, int base) const const
QByteArray toLower() const const
QByteArray trimmed() const const
void reserve(int size)
char at(int i) const const
int lastIndexOf(char ch, int from) const const
time_t date() const
bool isEmpty() const const
int length() const const
const QList< QKeySequence > & begin()
int size() const const
QList< T > fromSet(const QSet< T > &set)
int indexOf(char ch, int from) const const
QString fromUtf8(const char *str, int size)
bool isEmpty() const const
QByteArray & replace(int pos, int len, const char *after)
T & first()
QByteArray mid(int pos, int len) const const
KDEGAMES_EXPORT QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QByteArray & append(char ch)
qlonglong toLongLong(bool *ok, int base) const const
bool isValid() const const
QByteArray left(int len) const const
Helper integration between Akonadi and Qt.
bool contains(char ch) const const
KGuiItem reset()
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
QDateTime addSecs(qint64 s) const const
int size() const const
bool endsWith(const QByteArray &ba) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 23:08:55 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.