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()
const char * constData() const const
QByteArray fromRawData(const char *data, qsizetype size)
bool isEmpty() const const
QByteArray number(double n, char format, int precision)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
virtual bool waitForBytesWritten(int msecs)
qint64 write(const QByteArray &data)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 12:10:33 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.