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 | |
29 | using namespace KIMAP; |
30 | |
31 | ImapStreamParser::ImapStreamParser( QIODevice *socket, bool serverModeEnabled ) |
32 | { |
33 | m_socket = socket; |
34 | m_isServerModeEnabled = serverModeEnabled; |
35 | m_position = 0; |
36 | m_literalSize = 0; |
37 | } |
38 | |
39 | ImapStreamParser::~ImapStreamParser() |
40 | { |
41 | } |
42 | |
43 | QString ImapStreamParser::readUtf8String() |
44 | { |
45 | QByteArray tmp; |
46 | tmp = readString(); |
47 | QString result = QString::fromUtf8( tmp ); |
48 | return result; |
49 | } |
50 | |
51 | QByteArray 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 | |
75 | bool 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 | |
103 | bool 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 | |
144 | bool ImapStreamParser::atLiteralEnd() const |
145 | { |
146 | return ( m_literalSize == 0 ); |
147 | } |
148 | |
149 | QByteArray 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 | |
172 | bool 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 | |
187 | bool 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 | |
203 | QList<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 | |
302 | bool 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 | |
318 | bool 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 | |
334 | QByteArray 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 | |
420 | qint64 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 | |
453 | void 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 | |
464 | bool 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 | |
477 | void ImapStreamParser::setData( const QByteArray &data ) |
478 | { |
479 | m_data = data; |
480 | } |
481 | |
482 | QByteArray ImapStreamParser::readRemainingData() |
483 | { |
484 | return m_data.mid( m_position ); |
485 | } |
486 | |
487 | int ImapStreamParser::availableDataSize() const |
488 | { |
489 | return m_socket->bytesAvailable() + m_data.size() - m_position; |
490 | } |
491 | |
492 | bool 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 | |
519 | QByteArray 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 | |
556 | void 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 | |
564 | void 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 | |