KIMAP Library
imapstreamparser.cpp
00001 /* 00002 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> 00003 Copyright (c) 2009 Andras Mantia <amantia@kde.org> 00004 00005 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 00006 Author: Kevin Ottens <kevin@kdab.com> 00007 00008 This library is free software; you can redistribute it and/or modify it 00009 under the terms of the GNU Library General Public License as published by 00010 the Free Software Foundation; either version 2 of the License, or (at your 00011 option) any later version. 00012 00013 This library is distributed in the hope that it will be useful, but WITHOUT 00014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00015 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00016 License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to the 00020 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00021 02110-1301, USA. 00022 */ 00023 00024 #include "imapstreamparser.h" 00025 00026 #include <ctype.h> 00027 #include <QIODevice> 00028 00029 using namespace KIMAP; 00030 00031 ImapStreamParser::ImapStreamParser( QIODevice *socket, bool serverModeEnabled ) 00032 { 00033 m_socket = socket; 00034 m_isServerModeEnabled = serverModeEnabled; 00035 m_position = 0; 00036 m_literalSize = 0; 00037 } 00038 00039 ImapStreamParser::~ImapStreamParser() 00040 { 00041 } 00042 00043 QString ImapStreamParser::readUtf8String() 00044 { 00045 QByteArray tmp; 00046 tmp = readString(); 00047 QString result = QString::fromUtf8( tmp ); 00048 return result; 00049 } 00050 00051 00052 QByteArray ImapStreamParser::readString() 00053 { 00054 QByteArray result; 00055 if ( !waitForMoreData( m_data.length() == 0 ) ) 00056 throw ImapParserException("Unable to read more data"); 00057 stripLeadingSpaces(); 00058 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00059 throw ImapParserException("Unable to read more data"); 00060 00061 // literal string 00062 // TODO: error handling 00063 if ( hasLiteral() ) { 00064 while (!atLiteralEnd()) { 00065 result += readLiteralPart(); 00066 } 00067 return result; 00068 } 00069 00070 // quoted string 00071 return parseQuotedString(); 00072 } 00073 00074 bool ImapStreamParser::hasString() 00075 { 00076 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00077 throw ImapParserException("Unable to read more data"); 00078 int savedPos = m_position; 00079 stripLeadingSpaces(); 00080 int pos = m_position; 00081 m_position = savedPos; 00082 if ( m_data.at(pos) == '{' ) 00083 return true; //literal string 00084 if (m_data.at(pos) == '"' ) 00085 return true; //quoted string 00086 if ( m_data.at(pos) != ' ' && 00087 m_data.at(pos) != '(' && 00088 m_data.at(pos) != ')' && 00089 m_data.at(pos) != '[' && 00090 m_data.at(pos) != ']' && 00091 m_data.at(pos) != '\n' && 00092 m_data.at(pos) != '\r' ) 00093 return true; //unquoted string 00094 00095 return false; //something else, not a string 00096 } 00097 00098 bool ImapStreamParser::hasLiteral() 00099 { 00100 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00101 throw ImapParserException("Unable to read more data"); 00102 int savedPos = m_position; 00103 stripLeadingSpaces(); 00104 if ( m_data.at(m_position) == '{' ) 00105 { 00106 int end = -1; 00107 do { 00108 end = m_data.indexOf( '}', m_position ); 00109 if ( !waitForMoreData( end == -1 ) ) 00110 throw ImapParserException("Unable to read more data"); 00111 } while (end == -1); 00112 Q_ASSERT( end > m_position ); 00113 m_literalSize = m_data.mid( m_position + 1, end - m_position - 1 ).toInt(); 00114 // strip CRLF 00115 m_position = end + 1; 00116 00117 if ( m_position < m_data.length() && m_data.at(m_position) == '\r' ) 00118 ++m_position; 00119 if ( m_position < m_data.length() && m_data.at(m_position) == '\n' ) 00120 ++m_position; 00121 00122 //FIXME: Makes sense only on the server side? 00123 if (m_isServerModeEnabled && m_literalSize > 0) 00124 sendContinuationResponse( m_literalSize ); 00125 return true; 00126 } else 00127 { 00128 m_position = savedPos; 00129 return false; 00130 } 00131 } 00132 00133 bool ImapStreamParser::atLiteralEnd() const 00134 { 00135 return (m_literalSize == 0); 00136 } 00137 00138 QByteArray ImapStreamParser::readLiteralPart() 00139 { 00140 static qint64 maxLiteralPartSize = 4096; 00141 int size = qMin(maxLiteralPartSize, m_literalSize); 00142 00143 if ( !waitForMoreData( m_data.length() < m_position + size ) ) 00144 throw ImapParserException("Unable to read more data"); 00145 00146 if ( m_data.length() < m_position + size ) { // Still not enough data 00147 // Take what's already there 00148 size = m_data.length() - m_position; 00149 } 00150 00151 QByteArray result = m_data.mid(m_position, size); 00152 m_position += size; 00153 m_literalSize -= size; 00154 Q_ASSERT(m_literalSize >= 0); 00155 trimBuffer(); 00156 00157 return result; 00158 } 00159 00160 bool ImapStreamParser::hasList() 00161 { 00162 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00163 throw ImapParserException("Unable to read more data"); 00164 int savedPos = m_position; 00165 stripLeadingSpaces(); 00166 int pos = m_position; 00167 m_position = savedPos; 00168 if ( m_data.at(pos) == '(' ) 00169 { 00170 return true; 00171 } 00172 00173 return false; 00174 } 00175 00176 bool ImapStreamParser::atListEnd() 00177 { 00178 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00179 throw ImapParserException("Unable to read more data"); 00180 int savedPos = m_position; 00181 stripLeadingSpaces(); 00182 int pos = m_position; 00183 m_position = savedPos; 00184 if ( m_data.at(pos) == ')' ) 00185 { 00186 m_position = pos + 1; 00187 return true; 00188 } 00189 00190 return false; 00191 } 00192 00193 QList<QByteArray> ImapStreamParser::readParenthesizedList() 00194 { 00195 QList<QByteArray> result; 00196 if (! waitForMoreData( m_data.length() <= m_position ) ) 00197 throw ImapParserException("Unable to read more data"); 00198 00199 stripLeadingSpaces(); 00200 if ( m_data.at(m_position) != '(' ) 00201 return result; //no list found 00202 00203 bool concatToLast = false; 00204 int count = 0; 00205 int sublistbegin = m_position; 00206 int i = m_position + 1; 00207 Q_FOREVER { 00208 if ( !waitForMoreData( m_data.length() <= i ) ) 00209 { 00210 m_position = i; 00211 throw ImapParserException("Unable to read more data"); 00212 } 00213 if ( m_data.at(i) == '(' ) { 00214 ++count; 00215 if ( count == 1 ) 00216 sublistbegin = i; 00217 ++i; 00218 continue; 00219 } 00220 if ( m_data.at(i) == ')' ) { 00221 if ( count <= 0 ) { 00222 m_position = i + 1; 00223 return result; 00224 } 00225 if ( count == 1 ) 00226 result.append( m_data.mid( sublistbegin, i - sublistbegin + 1 ) ); 00227 --count; 00228 ++i; 00229 continue; 00230 } 00231 if ( m_data.at(i) == ' ' ) { 00232 ++i; 00233 continue; 00234 } 00235 if ( m_data.at(i) == '"' ) { 00236 if ( count > 0 ) { 00237 m_position = i; 00238 parseQuotedString(); 00239 i = m_position; 00240 continue; 00241 } 00242 } 00243 if ( m_data.at(i) == '[' ) { 00244 concatToLast = true; 00245 result.last()+='['; 00246 ++i; 00247 continue; 00248 } 00249 if ( m_data.at(i) == ']' ) { 00250 concatToLast = false; 00251 result.last()+=']'; 00252 ++i; 00253 continue; 00254 } 00255 if ( count == 0 ) { 00256 m_position = i; 00257 QByteArray ba; 00258 if (hasLiteral()) { 00259 while (!atLiteralEnd()) { 00260 ba+=readLiteralPart(); 00261 } 00262 } else { 00263 ba = readString(); 00264 } 00265 00266 // We might sometime get some unwanted CRLF, but we're still not at the end 00267 // of the list, would make further string reads fail so eat the CRLFs. 00268 while ( ( m_position < m_data.size() ) && ( m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n' ) ) { 00269 m_position++; 00270 } 00271 00272 i = m_position - 1; 00273 if (concatToLast) { 00274 result.last()+=ba; 00275 } else { 00276 result.append( ba ); 00277 } 00278 } 00279 ++i; 00280 } 00281 00282 throw ImapParserException( "Something went very very wrong!" ); 00283 } 00284 00285 bool ImapStreamParser::hasResponseCode() 00286 { 00287 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00288 throw ImapParserException("Unable to read more data"); 00289 int savedPos = m_position; 00290 stripLeadingSpaces(); 00291 int pos = m_position; 00292 m_position = savedPos; 00293 if ( m_data.at(pos) == '[' ) 00294 { 00295 m_position = pos + 1; 00296 return true; 00297 } 00298 00299 return false; 00300 } 00301 00302 bool ImapStreamParser::atResponseCodeEnd() 00303 { 00304 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00305 throw ImapParserException("Unable to read more data"); 00306 int savedPos = m_position; 00307 stripLeadingSpaces(); 00308 int pos = m_position; 00309 m_position = savedPos; 00310 if ( m_data.at(pos) == ']' ) 00311 { 00312 m_position = pos + 1; 00313 return true; 00314 } 00315 00316 return false; 00317 } 00318 00319 QByteArray ImapStreamParser::parseQuotedString() 00320 { 00321 QByteArray result; 00322 if (! waitForMoreData( m_data.length() == 0 ) ) 00323 throw ImapParserException("Unable to read more data"); 00324 stripLeadingSpaces(); 00325 int end = m_position; 00326 result.clear(); 00327 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00328 throw ImapParserException("Unable to read more data"); 00329 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00330 throw ImapParserException("Unable to read more data"); 00331 00332 bool foundSlash = false; 00333 // quoted string 00334 if ( m_data.at(m_position) == '"' ) { 00335 ++m_position; 00336 int i = m_position; 00337 Q_FOREVER { 00338 if ( !waitForMoreData( m_data.length() <= i ) ) 00339 { 00340 m_position = i; 00341 throw ImapParserException("Unable to read more data"); 00342 } 00343 if ( m_data.at(i) == '\\' ) { 00344 i += 2; 00345 foundSlash = true; 00346 continue; 00347 } 00348 if ( m_data.at(i) == '"' ) { 00349 result = m_data.mid( m_position, i - m_position ); 00350 end = i + 1; // skip the '"' 00351 break; 00352 } 00353 ++i; 00354 } 00355 } 00356 00357 // unquoted string 00358 else { 00359 bool reachedInputEnd = true; 00360 int i = m_position; 00361 Q_FOREVER { 00362 if ( !waitForMoreData( m_data.length() <= i ) ) 00363 { 00364 m_position = i; 00365 throw ImapParserException("Unable to read more data"); 00366 } 00367 if ( m_data.at(i) == ' ' || m_data.at(i) == '(' || m_data.at(i) == ')' || m_data.at(i) == '[' || m_data.at(i) == ']' || m_data.at(i) == '\n' || m_data.at(i) == '\r' || m_data.at(i) == '"') { 00368 end = i; 00369 reachedInputEnd = false; 00370 break; 00371 } 00372 if (m_data.at(i) == '\\') 00373 foundSlash = true; 00374 i++; 00375 } 00376 if ( reachedInputEnd ) //FIXME: how can it get here? 00377 end = m_data.length(); 00378 00379 result = m_data.mid( m_position, end - m_position ); 00380 } 00381 00382 // strip quotes 00383 if ( foundSlash ) { 00384 while ( result.contains( "\\\"" ) ) 00385 result.replace( "\\\"", "\"" ); 00386 while ( result.contains( "\\\\" ) ) 00387 result.replace( "\\\\", "\\" ); 00388 } 00389 m_position = end; 00390 return result; 00391 } 00392 00393 qint64 ImapStreamParser::readNumber( bool * ok ) 00394 { 00395 qint64 result; 00396 if ( ok ) 00397 *ok = false; 00398 if (! waitForMoreData( m_data.length() == 0 ) ) 00399 throw ImapParserException("Unable to read more data"); 00400 stripLeadingSpaces(); 00401 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00402 throw ImapParserException("Unable to read more data"); 00403 if ( m_position >= m_data.length() ) 00404 throw ImapParserException("Unable to read more data"); 00405 int i = m_position; 00406 Q_FOREVER { 00407 if ( !waitForMoreData( m_data.length() <= i ) ) 00408 { 00409 m_position = i; 00410 throw ImapParserException("Unable to read more data"); 00411 } 00412 if ( !isdigit( m_data.at( i ) ) ) 00413 break; 00414 ++i; 00415 } 00416 const QByteArray tmp = m_data.mid( m_position, i - m_position ); 00417 result = tmp.toLongLong( ok ); 00418 m_position = i; 00419 return result; 00420 } 00421 00422 void ImapStreamParser::stripLeadingSpaces() 00423 { 00424 for ( int i = m_position; i < m_data.length(); ++i ) { 00425 if ( m_data.at(i) != ' ' ) 00426 { 00427 m_position = i; 00428 return; 00429 } 00430 } 00431 m_position = m_data.length(); 00432 } 00433 00434 bool ImapStreamParser::waitForMoreData( bool wait ) 00435 { 00436 if ( wait ) { 00437 if ( m_socket->bytesAvailable() > 0 || 00438 m_socket->waitForReadyRead(30000) ) { 00439 m_data.append( m_socket->readAll() ); 00440 } else 00441 { 00442 return false; 00443 } 00444 } 00445 return true; 00446 } 00447 00448 void ImapStreamParser::setData( const QByteArray &data ) 00449 { 00450 m_data = data; 00451 } 00452 00453 QByteArray ImapStreamParser::readRemainingData() 00454 { 00455 return m_data.mid(m_position); 00456 } 00457 00458 int ImapStreamParser::availableDataSize() const 00459 { 00460 return m_socket->bytesAvailable()+m_data.size()-m_position; 00461 } 00462 00463 bool ImapStreamParser::atCommandEnd() 00464 { 00465 int savedPos = m_position; 00466 do { 00467 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00468 throw ImapParserException("Unable to read more data"); 00469 stripLeadingSpaces(); 00470 } while ( m_position >= m_data.size() ); 00471 00472 if ( m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') { 00473 if ( m_data.at(m_position) == '\r' ) 00474 ++m_position; 00475 if ( m_position < m_data.length() && m_data.at(m_position) == '\n' ) 00476 ++m_position; 00477 00478 // We'd better empty m_data from time to time before it grows out of control 00479 trimBuffer(); 00480 00481 return true; //command end 00482 } 00483 m_position = savedPos; 00484 return false; //something else 00485 } 00486 00487 QByteArray ImapStreamParser::readUntilCommandEnd() 00488 { 00489 QByteArray result; 00490 int i = m_position; 00491 int paranthesisBalance = 0; 00492 Q_FOREVER { 00493 if ( !waitForMoreData( m_data.length() <= i ) ) 00494 { 00495 m_position = i; 00496 throw ImapParserException("Unable to read more data"); 00497 } 00498 if ( m_data.at(i) == '{' ) 00499 { 00500 m_position = i - 1; 00501 hasLiteral(); //init literal size 00502 result.append(m_data.mid(i-1, m_position - i +1)); 00503 while (!atLiteralEnd()) 00504 { 00505 result.append( readLiteralPart() ); 00506 } 00507 i = m_position; 00508 } 00509 if ( m_data.at(i) == '(' ) 00510 paranthesisBalance++; 00511 if ( m_data.at(i) == ')' ) 00512 paranthesisBalance--; 00513 if ( ( i == m_data.length() && paranthesisBalance == 0 ) || m_data.at(i) == '\n' || m_data.at(i) == '\r') 00514 break; //command end 00515 result.append( m_data.at(i) ); 00516 ++i; 00517 } 00518 m_position = i; 00519 atCommandEnd(); 00520 return result; 00521 } 00522 00523 void ImapStreamParser::sendContinuationResponse( qint64 size ) 00524 { 00525 QByteArray block = "+ Ready for literal data (expecting " 00526 + QByteArray::number( size ) + " bytes)\r\n"; 00527 m_socket->write(block); 00528 m_socket->waitForBytesWritten(30000); 00529 } 00530 00531 void ImapStreamParser::trimBuffer() 00532 { 00533 if ( m_position < 4096 ) // right() is expensive, so don't do it for every line 00534 return; 00535 m_data = m_data.right(m_data.size()-m_position); 00536 m_position = 0; 00537 }