kde-playground/kdepimlibs/kimap/imapstreamparser.cpp

572 lines
14 KiB
C++
Raw Normal View History

/*
Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
Copyright (c) 2009 Andras Mantia <amantia@kde.org>
Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Kevin Ottens <kevin@kdab.com>
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 <ctype.h>
#include <QIODevice>
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<QByteArray> ImapStreamParser::readParenthesizedList()
{
QList<QByteArray> 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;
}