KIMAP2

imapstreamparser.cpp
1 /*
2  Copyright (c) 2006 - 2007 Volker Krause <[email protected]>
3  Copyright (c) 2009 Andras Mantia <[email protected]>
4  Copyright (c) 2017 Christian Mollekopf <[email protected]>
5 
6  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
7  Author: Kevin Ottens <[email protected]>
8  Copyright (c) 2016 Christian Mollekopf <[email protected]>
9 
10  This library is free software; you can redistribute it and/or modify it
11  under the terms of the GNU Library General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or (at your
13  option) any later version.
14 
15  This library is distributed in the hope that it will be useful, but WITHOUT
16  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
18  License for more details.
19 
20  You should have received a copy of the GNU Library General Public License
21  along with this library; see the file COPYING.LIB. If not, write to the
22  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23  02110-1301, USA.
24 */
25 
26 #include "imapstreamparser.h"
27 
28 #include <QIODevice>
29 #include <QDebug>
30 
31 using namespace KIMAP2;
32 
33 ImapStreamParser::ImapStreamParser(QIODevice *socket, bool serverModeEnabled)
34  : m_socket(socket),
35  m_isServerModeEnabled(serverModeEnabled),
36  m_processing(false),
37  m_position(0),
38  m_readPosition(0),
39  m_literalSize(0),
40  m_bufferSize(16000),
41  m_currentState(InitState),
42  m_listCounter(0),
43  m_stringStartPos(0),
44  m_readingLiteral(false),
45  m_error(false),
46  m_list(nullptr)
47 {
48  m_data1.resize(m_bufferSize);
49  m_data2.resize(m_bufferSize);
50  m_current = &m_data1;
51  setupCallbacks();
52 }
53 
54 QByteArray &ImapStreamParser::buffer()
55 {
56  return *m_current;
57 }
58 
59 const QByteArray &ImapStreamParser::buffer() const
60 {
61  return *m_current;
62 }
63 
64 char ImapStreamParser::at(int pos) const
65 {
66  return m_current->constData()[pos];
67 }
68 
69 QByteArray ImapStreamParser::mid(int start, int len) const
70 {
71  return buffer().mid(start, len);
72 }
73 
74 QByteArray ImapStreamParser::midRef(int start, int len) const
75 {
76  return QByteArray::fromRawData(buffer().constData() + start, len);
77 }
78 
79 int ImapStreamParser::length() const
80 {
81  return m_readPosition;
82 }
83 
84 int ImapStreamParser::readFromSocket()
85 {
86  if (m_readingLiteral && !m_isServerModeEnabled) {
87  Q_ASSERT(m_currentState == LiteralStringState);
88  Q_ASSERT(m_literalSize > 0);
89  const auto amountToRead = qMin(m_socket->bytesAvailable(), m_literalSize);
90  Q_ASSERT(amountToRead > 0);
91  auto pos = m_literalData.size();
92  m_literalData.resize(m_literalData.size() + amountToRead);
93  const auto readBytes = m_socket->read(m_literalData.data() + pos, amountToRead);
94  if (readBytes < 0) {
95  qWarning() << "Failed to read data";
96  return 0;
97  }
98  // qDebug() << "Read literal data: " << readBytes << m_literalSize;
99  m_literalSize -= readBytes;
100  Q_ASSERT(m_literalSize >= 0);
101  return readBytes;
102  } else {
103  if (m_readPosition == m_bufferSize) {
104  // qDebug() << "Buffer is full, trimming";
105  trimBuffer();
106  }
107  const auto amountToRead = qMin(m_socket->bytesAvailable(), qint64(m_bufferSize - m_readPosition));
108  Q_ASSERT(amountToRead > 0);
109  const auto readBytes = m_socket->read(buffer().data() + m_readPosition, amountToRead);
110  if (readBytes < 0) {
111  qWarning() << "Failed to read data";
112  return 0;
113  }
114  m_readPosition += readBytes;
115  // qDebug() << "Buffer: " << buffer().mid(0, m_readPosition);
116  // qDebug() << "Read data: " << readBytes;
117  return readBytes;
118  }
119 }
120 
121 void ImapStreamParser::setupCallbacks()
122 {
123  onString([&](const char *data, const int size) {
124  if (!m_message) {
125  //We just assume that we always get a string first
126  m_message.reset(new Message);
127  m_currentPayload = &m_message->content;
128  }
129  if (m_list) {
130  *m_list << QByteArray(data, size);
131  } else {
132  *m_currentPayload << Message::Part(QByteArray(data, size));
133  }
134  });
135 
136  onListStart([&]() {
137  m_listCounter++;
138  if (m_listCounter > 1) {
139  //Parse sublists as string
140  setState(SublistString);
141  m_stringStartPos = m_position;
142  } else {
143  if (!m_list) {
144  m_list = new QList<QByteArray>;
145  }
146  }
147  });
148 
149  onListEnd([&]() {
150  if (m_listCounter <= 0) {
151  qWarning() << "Brackets are off";
152  m_error = true;
153  return;
154  }
155  m_listCounter--;
156  if (m_listCounter == 0) {
157  Q_ASSERT(m_currentPayload);
158  Q_ASSERT(m_list);
159  *m_currentPayload << Message::Part(*m_list);
160  delete m_list;
161  m_list = nullptr;
162  }
163  });
164 
165  onResponseCodeStart([&]() {
166  m_currentPayload = &m_message->responseCode;
167  });
168 
169  onResponseCodeEnd([&]() {
170  m_currentPayload = &m_message->content;
171  });
172 
173  onLiteralStart([&](const int size) {
174  m_literalData.clear();
175  m_literalData.reserve(size);
176  });
177 
178  onLiteralPart([&](const char *data, const int size) {
179  m_literalData.append(QByteArray::fromRawData(data, size));
180  });
181 
182  onLiteralEnd([&]() {
183  string(m_literalData.constData(), m_literalData.size());
184  });
185 
186  onLineEnd([&]() {
187  if (m_list || m_listCounter != 0) {
188  qWarning() << "List parsing in progress: " << m_listCounter;
189  m_error = true;
190  }
191  if (m_literalSize || m_readingLiteral) {
192  qWarning() << "Literal parsing in progress: " << m_literalSize;
193  m_error = true;
194  }
195  Q_ASSERT(responseReceived);
196  if (m_message) {
197  responseReceived(*m_message);
198  m_message.reset(nullptr);
199  }
200  m_currentPayload = nullptr;
201  });
202 }
203 
204 void ImapStreamParser::setState(States state)
205 {
206  m_lastState = m_currentState;
207  m_currentState = state;
208 }
209 
210 void ImapStreamParser::forwardToState(States state)
211 {
212  m_currentState = state;
213 }
214 
215 void ImapStreamParser::resetState()
216 {
217  m_currentState = m_lastState;
218 }
219 
220 void ImapStreamParser::processBuffer()
221 {
222  if (m_error) {
223  qWarning() << "An error occurred";
224  return;
225  }
226  if (m_currentState == LiteralStringState && m_literalSize == 0 && m_readingLiteral) {
227  literalEnd();
228  resetState();
229  m_readingLiteral = false;
230  }
231 
232  while (m_position < m_readPosition) {
233  Q_ASSERT(m_position < length());
234  const char c = buffer()[m_position];
235  // qDebug() << "Checking :" << c << m_position << m_readPosition << m_currentState << m_listCounter;
236  switch (m_currentState) {
237  case InitState:
238  if (c == '(') {
239  listStart();
240  } else if (c == ')') {
241  listEnd();
242  } else if (c == '[') {
243  if (m_listCounter >= 1) {
244  //Inside lists angle brackets are parsed as strings
245  setState(AngleBracketStringState);
246  m_stringStartPos = m_position;
247  } else {
248  responseCodeStart();
249  }
250  } else if (c == ']') {
251  responseCodeEnd();
252  } else if (c == ' ') {
253  //Skip whitespace
254  setState(WhitespaceState);
255  } else if (c == '\r') {
256  setState(CRLFState);
257  } else if (c == '{') {
258  setState(LiteralStringState);
259  m_stringStartPos = m_position + 1;
260  } else if (c == '\"') {
261  setState(QuotedStringState);
262  m_stringStartPos = m_position + 1;
263  } else {
264  setState(StringState);
265  m_stringStartPos = m_position;
266  }
267  break;
268  case QuotedStringState:
269  if (c == '\"' && buffer().at(m_position - 1) != '\\') {
270  //Unescaped quote
271  resetState();
272  const auto endPos = m_position;
273  string(buffer().constData() + m_stringStartPos, endPos - m_stringStartPos);
274  m_stringStartPos = 0;
275  }
276  break;
277  case LiteralStringState:
278  if (c == '}') {
279  m_literalSize = strtol(buffer().constData() + m_stringStartPos, nullptr, 10);
280  // qDebug() << "Found literal size: " << m_literalSize;
281  literalStart(m_literalSize);
282  m_readingLiteral = false;
283  m_stringStartPos = 0;
284  break;
285  }
286  if (!m_readingLiteral) {
287  //Skip CRLF after literal size
288  if (c == '\n') {
289  m_readingLiteral = true;
290  if (m_isServerModeEnabled && m_literalSize > 0) {
291  sendContinuationResponse(m_literalSize);
292  }
293  }
294  } else {
295  Q_ASSERT(m_position < length());
296  if (m_literalSize) {
297  int size = m_literalSize;
298  if (length() < m_position + size) {
299  //If the literal is not complete we take what is available
300  size = length() - m_position;
301  }
302  literalPart(buffer().constData() + m_position, size);
303  m_position += size;
304  m_literalSize -= size;
305  }
306  if (m_literalSize <= 0) {
307  Q_ASSERT(m_literalSize == 0);
308  literalEnd();
309  resetState();
310  m_readingLiteral = false;
311  }
312  continue;
313  }
314  break;
315  case StringState:
316  if (c == ' ' ||
317  c == ')' || //End of list
318  c == '(' || //New list
319  //FIXME because we want to concat in sublists.
320  // c == '[' ||
321  c == ']' ||
322  c == '\r' || //CRLF
323  c == '\"') {
324  resetState();
325  string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos);
326  m_stringStartPos = 0;
327  continue;
328  }
329  //Inside lists we want to parse the angle brackets as part of the string.
330  if (c == '[') {
331  if (m_listCounter >= 1) {
332  // qDebug() << "Switching to angle bracket state";
333  forwardToState(AngleBracketStringState);
334  break;
335  }
336  }
337  break;
338  case AngleBracketStringState:
339  if (c == ']') {
340  resetState();
341  string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos + 1);
342  m_stringStartPos = 0;
343  }
344  break;
345  case SublistString:
346  if (c == '(') {
347  m_listCounter++;
348  } else if (c == ')') {
349  m_listCounter--;
350  if (m_listCounter <= 1) {
351  resetState();
352  string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos + 1);
353  m_stringStartPos = 0;
354  }
355  }
356  break;
357  case WhitespaceState:
358  if (c != ' ') {
359  //Skip whitespace
360  resetState();
361  continue;
362  }
363  break;
364  case CRLFState:
365  if (c == '\n') {
366  lineEnd();
367  resetState();
368  } else {
369  //Skip over the \r that isn't part of the CRLF
370  resetState();
371  continue;
372  }
373  break;
374  }
375  m_position++;
376  }
377 }
378 
379 void ImapStreamParser::parseStream()
380 {
381  if (m_processing) {
382  return;
383  }
384  if (m_error) {
385  qWarning() << "An error occurred";
386  return;
387  }
388  m_processing = true;
389  while (m_socket->bytesAvailable()) {
390  if (readFromSocket() <= 0) {
391  //If we're not making progress we could loop forever,
392  //and given that we check beforehand if there is data,
393  //this should never happen.
394  qWarning() << "Read nothing from the socket.";
395  m_error = true;
396  Q_ASSERT(false);
397  return;
398  };
399  processBuffer();
400  }
401  m_processing = false;
402 }
403 
404 void ImapStreamParser::trimBuffer()
405 {
406  int offset = m_position;
407  if (m_stringStartPos) {
408  offset = qMin(m_stringStartPos, m_position);
409  }
410 
411  auto remainderSize = m_readPosition - offset;
412  Q_ASSERT( remainderSize >= 0);
413  QByteArray *otherBuffer;
414  if (m_current == &m_data1) {
415  otherBuffer = &m_data2;
416  } else {
417  otherBuffer = &m_data1;
418  }
419  if (remainderSize) {
420  otherBuffer->replace(0, remainderSize, buffer().constData() + offset, remainderSize);
421  }
422  m_current = otherBuffer;
423  m_readPosition = remainderSize;
424  m_position -= offset;
425  if (m_stringStartPos) {
426  m_stringStartPos -= offset;
427  }
428  // qDebug() << "Buffer after trim: " << mid(0, m_readPosition);
429 }
430 
431 int ImapStreamParser::availableDataSize() const
432 {
433  return m_socket->bytesAvailable() + length() - m_position;
434 }
435 
437 {
438  QByteArray result;
439  auto startPos = m_position;
440  onLineEnd([&result, this, startPos]() {
441  result = mid(startPos, m_position - startPos - 1);
442  });
443  Q_FOREVER {
444  if (!m_socket->bytesAvailable()) {
445  if (!m_socket->waitForReadyRead(10000)) {
446  qWarning() << "No data available";
447  return result;
448  }
449  }
450  parseStream();
451  if (!result.isEmpty() && m_currentState == InitState) {
452  // qDebug() << "Got a result: " << m_readingLiteral;
453  // result.append(m_literalData);
454  break;
455  }
456  }
457  qDebug() << "Read until command end: " << result;
458  return result;
459 }
460 
461 void ImapStreamParser::sendContinuationResponse(qint64 size)
462 {
463  QByteArray block = "+ Ready for literal data (expecting " +
464  QByteArray::number(size) + " bytes)\r\n";
465  m_socket->write(block);
466  m_socket->waitForBytesWritten(30000);
467 }
468 
469 void ImapStreamParser::onResponseReceived(std::function<void(const Message &)> f)
470 {
471  responseReceived = f;
472 }
473 
474 bool ImapStreamParser::error() const
475 {
476  return m_error;
477 }
478 
479 QByteArray ImapStreamParser::currentBuffer() const
480 {
481  return mid(0, m_readPosition);
482 }
void clear()
void reserve(int size)
bool isEmpty() const const
QByteArray fromRawData(const char *data, int size)
void reset(T *other)
void resize(int size)
virtual bool waitForBytesWritten(int msecs)
QByteArray readUntilCommandEnd()
Return everything that remained from the command.
QByteArray number(int n, int base)
const char * constData() const const
QByteArray & replace(int pos, int len, const char *after)
virtual bool waitForReadyRead(int msecs)
qint64 read(char *data, qint64 maxSize)
Definition: acl.cpp:25
QByteArray mid(int pos, int len) const const
virtual qint64 bytesAvailable() const const
QByteArray & append(char ch)
char * data()
qint64 write(const char *data, qint64 maxSize)
int size() const const
ImapStreamParser(QIODevice *socket, bool serverModeEnabled=false)
Construct the parser.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Dec 6 2021 23:00:24 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.