/* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2009 Andras Mantia Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "imapstreamparser.h" #include #include using namespace KIMAP; ImapStreamParser::ImapStreamParser( QIODevice *socket, bool serverModeEnabled ) { m_socket = socket; m_isServerModeEnabled = serverModeEnabled; m_position = 0; m_literalSize = 0; } ImapStreamParser::~ImapStreamParser() { } QString ImapStreamParser::readUtf8String() { QByteArray tmp; tmp = readString(); QString result = QString::fromUtf8( tmp ); return result; } QByteArray ImapStreamParser::readString() { QByteArray result; if ( !waitForMoreData( m_data.length() == 0 ) ) { throw ImapParserException( "Unable to read more data" ); } stripLeadingSpaces(); if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } // literal string // TODO: error handling if ( hasLiteral() ) { while ( !atLiteralEnd() ) { result += readLiteralPart(); } return result; } // quoted string return parseQuotedString(); } bool ImapStreamParser::hasString() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); int pos = m_position; m_position = savedPos; if ( m_data.at( pos ) == '{' ) { return true; //literal string } if ( m_data.at( pos ) == '"' ) { return true; //quoted string } if ( m_data.at( pos ) != ' ' && m_data.at( pos ) != '(' && m_data.at( pos ) != ')' && m_data.at( pos ) != '[' && m_data.at( pos ) != ']' && m_data.at( pos ) != '\n' && m_data.at( pos ) != '\r' ) { return true; //unquoted string } return false; //something else, not a string } bool ImapStreamParser::hasLiteral() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); if ( m_data.at( m_position ) == '{' ) { int end = -1; do { end = m_data.indexOf( '}', m_position ); if ( !waitForMoreData( end == -1 ) ) { throw ImapParserException( "Unable to read more data" ); } } while ( end == -1 ); Q_ASSERT( end > m_position ); m_literalSize = m_data.mid( m_position + 1, end - m_position - 1 ).toInt(); // strip CRLF m_position = end + 1; // ensure that the CRLF is available if ( !waitForMoreData( m_position + 1 >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } if ( m_position < m_data.length() && m_data.at( m_position ) == '\r' ) { ++m_position; } if ( m_position < m_data.length() && m_data.at( m_position ) == '\n' ) { ++m_position; } //FIXME: Makes sense only on the server side? if ( m_isServerModeEnabled && m_literalSize > 0 ) { sendContinuationResponse( m_literalSize ); } return true; } else { m_position = savedPos; return false; } } bool ImapStreamParser::atLiteralEnd() const { return ( m_literalSize == 0 ); } QByteArray ImapStreamParser::readLiteralPart() { static qint64 maxLiteralPartSize = 4096; int size = qMin(maxLiteralPartSize, m_literalSize); if ( !waitForMoreData( m_data.length() < m_position + size ) ) { throw ImapParserException( "Unable to read more data" ); } if ( m_data.length() < m_position + size ) { // Still not enough data // Take what's already there size = m_data.length() - m_position; } QByteArray result = m_data.mid( m_position, size ); m_position += size; m_literalSize -= size; Q_ASSERT( m_literalSize >= 0 ); trimBuffer(); return result; } bool ImapStreamParser::hasList() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); int pos = m_position; m_position = savedPos; if ( m_data.at( pos ) == '(' ) { return true; } return false; } bool ImapStreamParser::atListEnd() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); int pos = m_position; m_position = savedPos; if ( m_data.at( pos ) == ')' ) { m_position = pos + 1; return true; } return false; } QList ImapStreamParser::readParenthesizedList() { QList result; if ( !waitForMoreData( m_data.length() <= m_position ) ) { throw ImapParserException( "Unable to read more data" ); } stripLeadingSpaces(); if ( m_data.at( m_position ) != '(' ) { return result; //no list found } bool concatToLast = false; int count = 0; int sublistbegin = m_position; int i = m_position + 1; Q_FOREVER { if ( !waitForMoreData( m_data.length() <= i ) ) { m_position = i; throw ImapParserException( "Unable to read more data" ); } if ( m_data.at( i ) == '(' ) { ++count; if ( count == 1 ) { sublistbegin = i; } ++i; continue; } if ( m_data.at( i ) == ')' ) { if ( count <= 0 ) { m_position = i + 1; return result; } if ( count == 1 ) { result.append( m_data.mid( sublistbegin, i - sublistbegin + 1 ) ); } --count; ++i; continue; } if ( m_data.at( i ) == ' ' ) { ++i; continue; } if ( m_data.at( i ) == '"' ) { if ( count > 0 ) { m_position = i; parseQuotedString(); i = m_position; continue; } } if ( m_data.at( i ) == '[' ) { concatToLast = true; if ( result.isEmpty() ) { result.append( QByteArray() ); } result.last() += '['; ++i; continue; } if ( m_data.at( i ) == ']' ) { concatToLast = false; result.last() += ']'; ++i; continue; } if ( count == 0 ) { m_position = i; QByteArray ba; if ( hasLiteral() ) { while ( !atLiteralEnd() ) { ba += readLiteralPart(); } } else { ba = readString(); } // We might sometime get some unwanted CRLF, but we're still not at the end // of the list, would make further string reads fail so eat the CRLFs. while ( ( m_position < m_data.size() ) && ( m_data.at( m_position ) == '\r' || m_data.at( m_position ) == '\n' ) ) { m_position++; } i = m_position - 1; if ( concatToLast ) { result.last() += ba; } else { result.append( ba ); } } ++i; } throw ImapParserException( "Something went very very wrong!" ); } bool ImapStreamParser::hasResponseCode() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); int pos = m_position; m_position = savedPos; if ( m_data.at( pos ) == '[' ) { m_position = pos + 1; return true; } return false; } bool ImapStreamParser::atResponseCodeEnd() { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } int savedPos = m_position; stripLeadingSpaces(); int pos = m_position; m_position = savedPos; if ( m_data.at( pos ) == ']' ) { m_position = pos + 1; return true; } return false; } QByteArray ImapStreamParser::parseQuotedString() { QByteArray result; if ( !waitForMoreData( m_data.length() == 0 ) ) { throw ImapParserException( "Unable to read more data" ); } stripLeadingSpaces(); int end = m_position; result.clear(); if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } bool foundSlash = false; // quoted string if ( m_data.at( m_position ) == '"' ) { ++m_position; int i = m_position; Q_FOREVER { if ( !waitForMoreData( m_data.length() <= i ) ) { m_position = i; throw ImapParserException( "Unable to read more data" ); } if ( m_data.at( i ) == '\\' ) { i += 2; foundSlash = true; continue; } if ( m_data.at( i ) == '"' ) { result = m_data.mid( m_position, i - m_position ); end = i + 1; // skip the '"' break; } ++i; } } // unquoted string else { bool reachedInputEnd = true; int i = m_position; Q_FOREVER { if ( !waitForMoreData( m_data.length() <= i ) ) { m_position = i; throw ImapParserException( "Unable to read more data" ); } 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 ) == '"' ) { end = i; reachedInputEnd = false; break; } if ( m_data.at( i ) == '\\' ) { foundSlash = true; } i++; } if ( reachedInputEnd ) { //FIXME: how can it get here? end = m_data.length(); } result = m_data.mid( m_position, end - m_position ); } // strip quotes if ( foundSlash ) { while ( result.contains( "\\\"" ) ) { result.replace( "\\\"", "\"" ); } while ( result.contains( "\\\\" ) ) { result.replace( "\\\\", "\\" ); } } m_position = end; return result; } qint64 ImapStreamParser::readNumber( bool * ok ) { qint64 result; if ( ok ) { *ok = false; } if ( !waitForMoreData( m_data.length() == 0 ) ) { throw ImapParserException( "Unable to read more data" ); } stripLeadingSpaces(); if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } if ( m_position >= m_data.length() ) { throw ImapParserException( "Unable to read more data" ); } int i = m_position; Q_FOREVER { if ( !waitForMoreData( m_data.length() <= i ) ) { m_position = i; throw ImapParserException( "Unable to read more data" ); } if ( !isdigit( m_data.at( i ) ) ) { break; } ++i; } const QByteArray tmp = m_data.mid( m_position, i - m_position ); result = tmp.toLongLong( ok ); m_position = i; return result; } void ImapStreamParser::stripLeadingSpaces() { for ( int i = m_position; i < m_data.length(); ++i ) { if ( m_data.at( i ) != ' ' ) { m_position = i; return; } } m_position = m_data.length(); } bool ImapStreamParser::waitForMoreData( bool wait ) { if ( wait ) { if ( m_socket->bytesAvailable() > 0 || m_socket->waitForReadyRead( 30000 ) ) { m_data.append( m_socket->readAll() ); } else { return false; } } return true; } void ImapStreamParser::setData( const QByteArray &data ) { m_data = data; } QByteArray ImapStreamParser::readRemainingData() { return m_data.mid( m_position ); } int ImapStreamParser::availableDataSize() const { return m_socket->bytesAvailable() + m_data.size() - m_position; } bool ImapStreamParser::atCommandEnd() { int savedPos = m_position; do { if ( !waitForMoreData( m_position >= m_data.length() ) ) { throw ImapParserException( "Unable to read more data" ); } stripLeadingSpaces(); } while ( m_position >= m_data.size() ); if ( m_data.at( m_position ) == '\n' || m_data.at( m_position ) == '\r' ) { if ( m_data.at( m_position ) == '\r' ) { ++m_position; } if ( m_position < m_data.length() && m_data.at( m_position ) == '\n' ) { ++m_position; } // We'd better empty m_data from time to time before it grows out of control trimBuffer(); return true; //command end } m_position = savedPos; return false; //something else } QByteArray ImapStreamParser::readUntilCommandEnd() { QByteArray result; int i = m_position; int paranthesisBalance = 0; Q_FOREVER { if ( !waitForMoreData( m_data.length() <= i ) ) { m_position = i; throw ImapParserException( "Unable to read more data" ); } if ( m_data.at( i ) == '{' ) { m_position = i - 1; hasLiteral(); //init literal size result.append( m_data.mid( i - 1, m_position - i + 1 ) ); while ( !atLiteralEnd() ) { result.append( readLiteralPart() ); } i = m_position; } if ( m_data.at( i ) == '(' ) { paranthesisBalance++; } if ( m_data.at( i ) == ')' ) { paranthesisBalance--; } if ( ( i == m_data.length() && paranthesisBalance == 0 ) || m_data.at( i ) == '\n' || m_data.at( i ) == '\r') { break; //command end } result.append( m_data.at( i ) ); ++i; } m_position = i; atCommandEnd(); return result; } void ImapStreamParser::sendContinuationResponse( qint64 size ) { QByteArray block = "+ Ready for literal data (expecting " + QByteArray::number( size ) + " bytes)\r\n"; m_socket->write( block ); m_socket->waitForBytesWritten( 30000 ); } void ImapStreamParser::trimBuffer() { if ( m_position < 4096 ) { // right() is expensive, so don't do it for every line return; } m_data = m_data.right( m_data.size() - m_position ); m_position = 0; }