KIMAP2

imapstreamparser.cpp
1/*
2 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
3 Copyright (c) 2009 Andras Mantia <amantia@kde.org>
4 Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
5
6 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
7 Author: Kevin Ottens <kevin@kdab.com>
8 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
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
31using namespace KIMAP2;
32
33ImapStreamParser::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
54QByteArray &ImapStreamParser::buffer()
55{
56 return *m_current;
57}
58
59const QByteArray &ImapStreamParser::buffer() const
60{
61 return *m_current;
62}
63
64char ImapStreamParser::at(int pos) const
65{
66 return m_current->constData()[pos];
67}
68
69QByteArray ImapStreamParser::mid(int start, int len) const
70{
71 return buffer().mid(start, len);
72}
73
74QByteArray ImapStreamParser::midRef(int start, int len) const
75{
76 return QByteArray::fromRawData(buffer().constData() + start, len);
77}
78
79int ImapStreamParser::length() const
80{
81 return m_readPosition;
82}
83
84int 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
121void 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
204void ImapStreamParser::setState(States state)
205{
206 m_lastState = m_currentState;
207 m_currentState = state;
208}
209
210void ImapStreamParser::forwardToState(States state)
211{
212 m_currentState = state;
213}
214
215void ImapStreamParser::resetState()
216{
217 m_currentState = m_lastState;
218}
219
220void 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
379void 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
404void 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
431int 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
461void 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
469void ImapStreamParser::onResponseReceived(std::function<void(const Message &)> f)
470{
471 responseReceived = f;
472}
473
474bool ImapStreamParser::error() const
475{
476 return m_error;
477}
478
479QByteArray ImapStreamParser::currentBuffer() const
480{
481 return mid(0, m_readPosition);
482}
ImapStreamParser(QIODevice *socket, bool serverModeEnabled=false)
Construct the parser.
QByteArray readUntilCommandEnd()
Return everything that remained from the command.
Q_SCRIPTABLE Q_NOREPLY void start()
QByteArray & append(QByteArrayView data)
void clear()
const char * constData() const const
char * data()
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
virtual qint64 bytesAvailable() const const
QByteArray read(qint64 maxSize)
virtual bool waitForBytesWritten(int msecs)
virtual bool waitForReadyRead(int msecs)
qint64 write(const QByteArray &data)
void reset(T *other)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:58:39 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.