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 concatToLast = true; 00237 result.last()+='['; 00238 ++i; 00239 continue; 00240 } 00241 if ( m_data.at(i) == ']' ) { 00242 concatToLast = false; 00243 result.last()+=']'; 00244 ++i; 00245 continue; 00246 } 00247 if ( count == 0 ) { 00248 m_position = i; 00249 QByteArray ba; 00250 if (hasLiteral()) { 00251 while (!atLiteralEnd()) { 00252 ba+=readLiteralPart(); 00253 } 00254 } else { 00255 ba = readString(); 00256 } 00257 00258 // We might sometime get some unwanted CRLF, but we're still not at the end 00259 // of the list, would make further string reads fail so eat the CRLFs. 00260 while ( ( m_position < m_data.size() ) && ( m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n' ) ) { 00261 m_position++; 00262 } 00263 00264 i = m_position - 1; 00265 if (concatToLast) { 00266 result.last()+=ba; 00267 } else { 00268 result.append( ba ); 00269 } 00270 } 00271 ++i; 00272 } 00273 00274 throw ImapParserException( "Something went very very wrong!" ); 00275 } 00276 00277 bool ImapStreamParser::hasResponseCode() 00278 { 00279 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00280 throw ImapParserException("Unable to read more data"); 00281 int savedPos = m_position; 00282 stripLeadingSpaces(); 00283 int pos = m_position; 00284 m_position = savedPos; 00285 if ( m_data.at(pos) == '[' ) 00286 { 00287 m_position = pos + 1; 00288 return true; 00289 } 00290 00291 return false; 00292 } 00293 00294 bool ImapStreamParser::atResponseCodeEnd() 00295 { 00296 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00297 throw ImapParserException("Unable to read more data"); 00298 int savedPos = m_position; 00299 stripLeadingSpaces(); 00300 int pos = m_position; 00301 m_position = savedPos; 00302 if ( m_data.at(pos) == ']' ) 00303 { 00304 m_position = pos + 1; 00305 return true; 00306 } 00307 00308 return false; 00309 } 00310 00311 QByteArray ImapStreamParser::parseQuotedString() 00312 { 00313 QByteArray result; 00314 if (! waitForMoreData( m_data.length() == 0 ) ) 00315 throw ImapParserException("Unable to read more data"); 00316 stripLeadingSpaces(); 00317 int end = m_position; 00318 result.clear(); 00319 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00320 throw ImapParserException("Unable to read more data"); 00321 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00322 throw ImapParserException("Unable to read more data"); 00323 00324 bool foundSlash = false; 00325 // quoted string 00326 if ( m_data.at(m_position) == '"' ) { 00327 ++m_position; 00328 int i = m_position; 00329 Q_FOREVER { 00330 if ( !waitForMoreData( m_data.length() <= i ) ) 00331 { 00332 m_position = i; 00333 throw ImapParserException("Unable to read more data"); 00334 } 00335 if ( m_data.at(i) == '\\' ) { 00336 i += 2; 00337 foundSlash = true; 00338 continue; 00339 } 00340 if ( m_data.at(i) == '"' ) { 00341 result = m_data.mid( m_position, i - m_position ); 00342 end = i + 1; // skip the '"' 00343 break; 00344 } 00345 ++i; 00346 } 00347 } 00348 00349 // unquoted string 00350 else { 00351 bool reachedInputEnd = true; 00352 int i = m_position; 00353 Q_FOREVER { 00354 if ( !waitForMoreData( m_data.length() <= i ) ) 00355 { 00356 m_position = i; 00357 throw ImapParserException("Unable to read more data"); 00358 } 00359 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) == '"') { 00360 end = i; 00361 reachedInputEnd = false; 00362 break; 00363 } 00364 if (m_data.at(i) == '\\') 00365 foundSlash = true; 00366 i++; 00367 } 00368 if ( reachedInputEnd ) //FIXME: how can it get here? 00369 end = m_data.length(); 00370 00371 result = m_data.mid( m_position, end - m_position ); 00372 } 00373 00374 // strip quotes 00375 if ( foundSlash ) { 00376 while ( result.contains( "\\\"" ) ) 00377 result.replace( "\\\"", "\"" ); 00378 while ( result.contains( "\\\\" ) ) 00379 result.replace( "\\\\", "\\" ); 00380 } 00381 m_position = end; 00382 return result; 00383 } 00384 00385 qint64 ImapStreamParser::readNumber( bool * ok ) 00386 { 00387 qint64 result; 00388 if ( ok ) 00389 *ok = false; 00390 if (! waitForMoreData( m_data.length() == 0 ) ) 00391 throw ImapParserException("Unable to read more data"); 00392 stripLeadingSpaces(); 00393 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00394 throw ImapParserException("Unable to read more data"); 00395 if ( m_position >= m_data.length() ) 00396 throw ImapParserException("Unable to read more data"); 00397 int i = m_position; 00398 Q_FOREVER { 00399 if ( !waitForMoreData( m_data.length() <= i ) ) 00400 { 00401 m_position = i; 00402 throw ImapParserException("Unable to read more data"); 00403 } 00404 if ( !isdigit( m_data.at( i ) ) ) 00405 break; 00406 ++i; 00407 } 00408 const QByteArray tmp = m_data.mid( m_position, i - m_position ); 00409 result = tmp.toLongLong( ok ); 00410 m_position = i; 00411 return result; 00412 } 00413 00414 void ImapStreamParser::stripLeadingSpaces() 00415 { 00416 for ( int i = m_position; i < m_data.length(); ++i ) { 00417 if ( m_data.at(i) != ' ' ) 00418 { 00419 m_position = i; 00420 return; 00421 } 00422 } 00423 m_position = m_data.length(); 00424 } 00425 00426 bool ImapStreamParser::waitForMoreData( bool wait ) 00427 { 00428 if ( wait ) { 00429 if ( m_socket->bytesAvailable() > 0 || 00430 m_socket->waitForReadyRead(30000) ) { 00431 m_data.append( m_socket->readAll() ); 00432 } else 00433 { 00434 return false; 00435 } 00436 } 00437 return true; 00438 } 00439 00440 void ImapStreamParser::setData( const QByteArray &data ) 00441 { 00442 m_data = data; 00443 } 00444 00445 QByteArray ImapStreamParser::readRemainingData() 00446 { 00447 return m_data.mid(m_position); 00448 } 00449 00450 int ImapStreamParser::availableDataSize() const 00451 { 00452 return m_socket->bytesAvailable()+m_data.size()-m_position; 00453 } 00454 00455 bool ImapStreamParser::atCommandEnd() 00456 { 00457 int savedPos = m_position; 00458 do { 00459 if ( !waitForMoreData( m_position >= m_data.length() ) ) 00460 throw ImapParserException("Unable to read more data"); 00461 stripLeadingSpaces(); 00462 } while ( m_position >= m_data.size() ); 00463 00464 if ( m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') { 00465 if ( m_data.at(m_position) == '\r' ) 00466 ++m_position; 00467 if ( m_position < m_data.length() && m_data.at(m_position) == '\n' ) 00468 ++m_position; 00469 00470 // We'd better empty m_data from time to time before it grows out of control 00471 trimBuffer(); 00472 00473 return true; //command end 00474 } 00475 m_position = savedPos; 00476 return false; //something else 00477 } 00478 00479 QByteArray ImapStreamParser::readUntilCommandEnd() 00480 { 00481 QByteArray result; 00482 int i = m_position; 00483 int paranthesisBalance = 0; 00484 Q_FOREVER { 00485 if ( !waitForMoreData( m_data.length() <= i ) ) 00486 { 00487 m_position = i; 00488 throw ImapParserException("Unable to read more data"); 00489 } 00490 if ( m_data.at(i) == '{' ) 00491 { 00492 m_position = i - 1; 00493 hasLiteral(); //init literal size 00494 result.append(m_data.mid(i-1, m_position - i +1)); 00495 while (!atLiteralEnd()) 00496 { 00497 result.append( readLiteralPart() ); 00498 } 00499 i = m_position; 00500 } 00501 if ( m_data.at(i) == '(' ) 00502 paranthesisBalance++; 00503 if ( m_data.at(i) == ')' ) 00504 paranthesisBalance--; 00505 if ( ( i == m_data.length() && paranthesisBalance == 0 ) || m_data.at(i) == '\n' || m_data.at(i) == '\r') 00506 break; //command end 00507 result.append( m_data.at(i) ); 00508 ++i; 00509 } 00510 m_position = i; 00511 atCommandEnd(); 00512 return result; 00513 } 00514 00515 void ImapStreamParser::sendContinuationResponse( qint64 size ) 00516 { 00517 QByteArray block = "+ Ready for literal data (expecting " 00518 + QByteArray::number( size ) + " bytes)\r\n"; 00519 m_socket->write(block); 00520 m_socket->waitForBytesWritten(30000); 00521 } 00522 00523 void ImapStreamParser::trimBuffer() 00524 { 00525 if ( m_position < 4096 ) // right() is expensive, so don't do it for every line 00526 return; 00527 m_data = m_data.right(m_data.size()-m_position); 00528 m_position = 0; 00529 }