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