/* 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; 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; }