1/*
2 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
3 Copyright (c) 2009 Andras Mantia <amantia@kde.org>
4
5 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 Author: Kevin Ottens <kevin@kdab.com>
7
8 This library is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Library General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at your
11 option) any later version.
12
13 This library is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
16 License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22*/
23
24#include "imapstreamparser.h"
25
26#include <ctype.h>
27#include <QIODevice>
28
29using namespace KIMAP;
30
31ImapStreamParser::ImapStreamParser( QIODevice *socket, bool serverModeEnabled )
32{
33 m_socket = socket;
34 m_isServerModeEnabled = serverModeEnabled;
35 m_position = 0;
36 m_literalSize = 0;
37}
38
39ImapStreamParser::~ImapStreamParser()
40{
41}
42
43QString ImapStreamParser::readUtf8String()
44{
45 QByteArray tmp;
46 tmp = readString();
47 QString result = QString::fromUtf8( tmp );
48 return result;
49}
50
51QByteArray ImapStreamParser::readString()
52{
53 QByteArray result;
54 if ( !waitForMoreData( m_data.length() == 0 ) ) {
55 throw ImapParserException( "Unable to read more data" );
56 }
57 stripLeadingSpaces();
58 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
59 throw ImapParserException( "Unable to read more data" );
60 }
61
62 // literal string
63 // TODO: error handling
64 if ( hasLiteral() ) {
65 while ( !atLiteralEnd() ) {
66 result += readLiteralPart();
67 }
68 return result;
69 }
70
71 // quoted string
72 return parseQuotedString();
73}
74
75bool ImapStreamParser::hasString()
76{
77 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
78 throw ImapParserException( "Unable to read more data" );
79 }
80 int savedPos = m_position;
81 stripLeadingSpaces();
82 int pos = m_position;
83 m_position = savedPos;
84 if ( m_data.at( pos ) == '{' ) {
85 return true; //literal string
86 }
87 if ( m_data.at( pos ) == '"' ) {
88 return true; //quoted string
89 }
90 if ( m_data.at( pos ) != ' ' &&
91 m_data.at( pos ) != '(' &&
92 m_data.at( pos ) != ')' &&
93 m_data.at( pos ) != '[' &&
94 m_data.at( pos ) != ']' &&
95 m_data.at( pos ) != '\n' &&
96 m_data.at( pos ) != '\r' ) {
97 return true; //unquoted string
98 }
99
100 return false; //something else, not a string
101}
102
103bool ImapStreamParser::hasLiteral()
104{
105 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
106 throw ImapParserException( "Unable to read more data" );
107 }
108 int savedPos = m_position;
109 stripLeadingSpaces();
110 if ( m_data.at( m_position ) == '{' ) {
111 int end = -1;
112 do {
113 end = m_data.indexOf( '}', m_position );
114 if ( !waitForMoreData( end == -1 ) ) {
115 throw ImapParserException( "Unable to read more data" );
116 }
117 } while ( end == -1 );
118 Q_ASSERT( end > m_position );
119 m_literalSize = m_data.mid( m_position + 1, end - m_position - 1 ).toInt();
120 // strip CRLF
121 m_position = end + 1;
122 // ensure that the CRLF is available
123 if ( !waitForMoreData( m_position + 1 >= m_data.length() ) ) {
124 throw ImapParserException( "Unable to read more data" );
125 }
126 if ( m_position < m_data.length() && m_data.at( m_position ) == '\r' ) {
127 ++m_position;
128 }
129 if ( m_position < m_data.length() && m_data.at( m_position ) == '\n' ) {
130 ++m_position;
131 }
132
133 //FIXME: Makes sense only on the server side?
134 if ( m_isServerModeEnabled && m_literalSize > 0 ) {
135 sendContinuationResponse( m_literalSize );
136 }
137 return true;
138 } else {
139 m_position = savedPos;
140 return false;
141 }
142}
143
144bool ImapStreamParser::atLiteralEnd() const
145{
146 return ( m_literalSize == 0 );
147}
148
149QByteArray ImapStreamParser::readLiteralPart()
150{
151 static qint64 maxLiteralPartSize = 4096;
152 int size = qMin(maxLiteralPartSize, m_literalSize);
153
154 if ( !waitForMoreData( m_data.length() < m_position + size ) ) {
155 throw ImapParserException( "Unable to read more data" );
156 }
157
158 if ( m_data.length() < m_position + size ) { // Still not enough data
159 // Take what's already there
160 size = m_data.length() - m_position;
161 }
162
163 QByteArray result = m_data.mid( m_position, size );
164 m_position += size;
165 m_literalSize -= size;
166 Q_ASSERT( m_literalSize >= 0 );
167 trimBuffer();
168
169 return result;
170}
171
172bool ImapStreamParser::hasList()
173{
174 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
175 throw ImapParserException( "Unable to read more data" );
176 }
177 int savedPos = m_position;
178 stripLeadingSpaces();
179 int pos = m_position;
180 m_position = savedPos;
181 if ( m_data.at( pos ) == '(' ) {
182 return true;
183 }
184 return false;
185}
186
187bool ImapStreamParser::atListEnd()
188{
189 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
190 throw ImapParserException( "Unable to read more data" );
191 }
192 int savedPos = m_position;
193 stripLeadingSpaces();
194 int pos = m_position;
195 m_position = savedPos;
196 if ( m_data.at( pos ) == ')' ) {
197 m_position = pos + 1;
198 return true;
199 }
200 return false;
201}
202
203QList<QByteArray> ImapStreamParser::readParenthesizedList()
204{
205 QList<QByteArray> result;
206 if ( !waitForMoreData( m_data.length() <= m_position ) ) {
207 throw ImapParserException( "Unable to read more data" );
208 }
209
210 stripLeadingSpaces();
211 if ( m_data.at( m_position ) != '(' ) {
212 return result; //no list found
213 }
214
215 bool concatToLast = false;
216 int count = 0;
217 int sublistbegin = m_position;
218 int i = m_position + 1;
219 Q_FOREVER {
220 if ( !waitForMoreData( m_data.length() <= i ) ) {
221 m_position = i;
222 throw ImapParserException( "Unable to read more data" );
223 }
224 if ( m_data.at( i ) == '(' ) {
225 ++count;
226 if ( count == 1 ) {
227 sublistbegin = i;
228 }
229 ++i;
230 continue;
231 }
232 if ( m_data.at( i ) == ')' ) {
233 if ( count <= 0 ) {
234 m_position = i + 1;
235 return result;
236 }
237 if ( count == 1 ) {
238 result.append( m_data.mid( sublistbegin, i - sublistbegin + 1 ) );
239 }
240 --count;
241 ++i;
242 continue;
243 }
244 if ( m_data.at( i ) == ' ' ) {
245 ++i;
246 continue;
247 }
248 if ( m_data.at( i ) == '"' ) {
249 if ( count > 0 ) {
250 m_position = i;
251 parseQuotedString();
252 i = m_position;
253 continue;
254 }
255 }
256 if ( m_data.at( i ) == '[' ) {
257 concatToLast = true;
258 if ( result.isEmpty() ) {
259 result.append( QByteArray() );
260 }
261 result.last() += '[';
262 ++i;
263 continue;
264 }
265 if ( m_data.at( i ) == ']' ) {
266 concatToLast = false;
267 result.last() += ']';
268 ++i;
269 continue;
270 }
271 if ( count == 0 ) {
272 m_position = i;
273 QByteArray ba;
274 if ( hasLiteral() ) {
275 while ( !atLiteralEnd() ) {
276 ba += readLiteralPart();
277 }
278 } else {
279 ba = readString();
280 }
281
282 // We might sometime get some unwanted CRLF, but we're still not at the end
283 // of the list, would make further string reads fail so eat the CRLFs.
284 while ( ( m_position < m_data.size() ) &&
285 ( m_data.at( m_position ) == '\r' || m_data.at( m_position ) == '\n' ) ) {
286 m_position++;
287 }
288
289 i = m_position - 1;
290 if ( concatToLast ) {
291 result.last() += ba;
292 } else {
293 result.append( ba );
294 }
295 }
296 ++i;
297 }
298
299 throw ImapParserException( "Something went very very wrong!" );
300}
301
302bool ImapStreamParser::hasResponseCode()
303{
304 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
305 throw ImapParserException( "Unable to read more data" );
306 }
307 int savedPos = m_position;
308 stripLeadingSpaces();
309 int pos = m_position;
310 m_position = savedPos;
311 if ( m_data.at( pos ) == '[' ) {
312 m_position = pos + 1;
313 return true;
314 }
315 return false;
316}
317
318bool ImapStreamParser::atResponseCodeEnd()
319{
320 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
321 throw ImapParserException( "Unable to read more data" );
322 }
323 int savedPos = m_position;
324 stripLeadingSpaces();
325 int pos = m_position;
326 m_position = savedPos;
327 if ( m_data.at( pos ) == ']' ) {
328 m_position = pos + 1;
329 return true;
330 }
331 return false;
332}
333
334QByteArray ImapStreamParser::parseQuotedString()
335{
336 QByteArray result;
337 if ( !waitForMoreData( m_data.length() == 0 ) ) {
338 throw ImapParserException( "Unable to read more data" );
339 }
340 stripLeadingSpaces();
341 int end = m_position;
342 result.clear();
343 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
344 throw ImapParserException( "Unable to read more data" );
345 }
346 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
347 throw ImapParserException( "Unable to read more data" );
348 }
349
350 bool foundSlash = false;
351 // quoted string
352 if ( m_data.at( m_position ) == '"' ) {
353 ++m_position;
354 int i = m_position;
355 Q_FOREVER {
356 if ( !waitForMoreData( m_data.length() <= i ) ) {
357 m_position = i;
358 throw ImapParserException( "Unable to read more data" );
359 }
360 if ( m_data.at( i ) == '\\' ) {
361 i += 2;
362 foundSlash = true;
363 continue;
364 }
365 if ( m_data.at( i ) == '"' ) {
366 result = m_data.mid( m_position, i - m_position );
367 end = i + 1; // skip the '"'
368 break;
369 }
370 ++i;
371 }
372 }
373
374 // unquoted string
375 else {
376 bool reachedInputEnd = true;
377 int i = m_position;
378 Q_FOREVER {
379 if ( !waitForMoreData( m_data.length() <= i ) ) {
380 m_position = i;
381 throw ImapParserException( "Unable to read more data" );
382 }
383 if ( m_data.at( i ) == ' ' ||
384 m_data.at( i ) == '(' ||
385 m_data.at( i ) == ')' ||
386 m_data.at( i ) == '[' ||
387 m_data.at( i ) == ']' ||
388 m_data.at( i ) == '\n' ||
389 m_data.at( i ) == '\r' ||
390 m_data.at( i ) == '"' ) {
391 end = i;
392 reachedInputEnd = false;
393 break;
394 }
395 if ( m_data.at( i ) == '\\' ) {
396 foundSlash = true;
397 }
398 i++;
399 }
400 if ( reachedInputEnd ) { //FIXME: how can it get here?
401 end = m_data.length();
402 }
403
404 result = m_data.mid( m_position, end - m_position );
405 }
406
407 // strip quotes
408 if ( foundSlash ) {
409 while ( result.contains( "\\\"" ) ) {
410 result.replace( "\\\"", "\"" );
411 }
412 while ( result.contains( "\\\\" ) ) {
413 result.replace( "\\\\", "\\" );
414 }
415 }
416 m_position = end;
417 return result;
418}
419
420qint64 ImapStreamParser::readNumber( bool * ok )
421{
422 qint64 result;
423 if ( ok ) {
424 *ok = false;
425 }
426 if ( !waitForMoreData( m_data.length() == 0 ) ) {
427 throw ImapParserException( "Unable to read more data" );
428 }
429 stripLeadingSpaces();
430 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
431 throw ImapParserException( "Unable to read more data" );
432 }
433 if ( m_position >= m_data.length() ) {
434 throw ImapParserException( "Unable to read more data" );
435 }
436 int i = m_position;
437 Q_FOREVER {
438 if ( !waitForMoreData( m_data.length() <= i ) ) {
439 m_position = i;
440 throw ImapParserException( "Unable to read more data" );
441 }
442 if ( !isdigit( m_data.at( i ) ) ) {
443 break;
444 }
445 ++i;
446 }
447 const QByteArray tmp = m_data.mid( m_position, i - m_position );
448 result = tmp.toLongLong( ok );
449 m_position = i;
450 return result;
451}
452
453void ImapStreamParser::stripLeadingSpaces()
454{
455 for ( int i = m_position; i < m_data.length(); ++i ) {
456 if ( m_data.at( i ) != ' ' ) {
457 m_position = i;
458 return;
459 }
460 }
461 m_position = m_data.length();
462}
463
464bool ImapStreamParser::waitForMoreData( bool wait )
465{
466 if ( wait ) {
467 if ( m_socket->bytesAvailable() > 0 ||
468 m_socket->waitForReadyRead( 30000 ) ) {
469 m_data.append( m_socket->readAll() );
470 } else {
471 return false;
472 }
473 }
474 return true;
475}
476
477void ImapStreamParser::setData( const QByteArray &data )
478{
479 m_data = data;
480}
481
482QByteArray ImapStreamParser::readRemainingData()
483{
484 return m_data.mid( m_position );
485}
486
487int ImapStreamParser::availableDataSize() const
488{
489 return m_socket->bytesAvailable() + m_data.size() - m_position;
490}
491
492bool ImapStreamParser::atCommandEnd()
493{
494 int savedPos = m_position;
495 do {
496 if ( !waitForMoreData( m_position >= m_data.length() ) ) {
497 throw ImapParserException( "Unable to read more data" );
498 }
499 stripLeadingSpaces();
500 } while ( m_position >= m_data.size() );
501
502 if ( m_data.at( m_position ) == '\n' || m_data.at( m_position ) == '\r' ) {
503 if ( m_data.at( m_position ) == '\r' ) {
504 ++m_position;
505 }
506 if ( m_position < m_data.length() && m_data.at( m_position ) == '\n' ) {
507 ++m_position;
508 }
509
510 // We'd better empty m_data from time to time before it grows out of control
511 trimBuffer();
512
513 return true; //command end
514 }
515 m_position = savedPos;
516 return false; //something else
517}
518
519QByteArray ImapStreamParser::readUntilCommandEnd()
520{
521 QByteArray result;
522 int i = m_position;
523 int paranthesisBalance = 0;
524 Q_FOREVER {
525 if ( !waitForMoreData( m_data.length() <= i ) ) {
526 m_position = i;
527 throw ImapParserException( "Unable to read more data" );
528 }
529 if ( m_data.at( i ) == '{' ) {
530 m_position = i - 1;
531 hasLiteral(); //init literal size
532 result.append( m_data.mid( i - 1, m_position - i + 1 ) );
533 while ( !atLiteralEnd() ) {
534 result.append( readLiteralPart() );
535 }
536 i = m_position;
537 }
538 if ( m_data.at( i ) == '(' ) {
539 paranthesisBalance++;
540 }
541 if ( m_data.at( i ) == ')' ) {
542 paranthesisBalance--;
543 }
544 if ( ( i == m_data.length() && paranthesisBalance == 0 ) ||
545 m_data.at( i ) == '\n' || m_data.at( i ) == '\r') {
546 break; //command end
547 }
548 result.append( m_data.at( i ) );
549 ++i;
550 }
551 m_position = i;
552 atCommandEnd();
553 return result;
554}
555
556void ImapStreamParser::sendContinuationResponse( qint64 size )
557{
558 QByteArray block = "+ Ready for literal data (expecting " +
559 QByteArray::number( size ) + " bytes)\r\n";
560 m_socket->write( block );
561 m_socket->waitForBytesWritten( 30000 );
562}
563
564void ImapStreamParser::trimBuffer()
565{
566 if ( m_position < 4096 ) { // right() is expensive, so don't do it for every line
567 return;
568 }
569 m_data = m_data.right( m_data.size() - m_position );
570 m_position = 0;
571}
572