mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
1913 lines
51 KiB
C++
1913 lines
51 KiB
C++
/**********************************************************************
|
|
*
|
|
* imapparser.cc - IMAP4rev1 Parser
|
|
* Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
|
|
* Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Send comments and bug fixes to s.carstens@gmx.de
|
|
*
|
|
*********************************************************************/
|
|
|
|
#include "imapparser.h"
|
|
#include "imapinfo.h"
|
|
#include "mailheader.h"
|
|
#include "mimeheader.h"
|
|
#include "mailaddress.h"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <QList>
|
|
|
|
extern "C" {
|
|
#include <sasl/sasl.h>
|
|
}
|
|
|
|
#include <QRegExp>
|
|
#include <QBuffer>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
|
|
#include <kascii.h>
|
|
#include <kdebug.h>
|
|
#include <kcodecs.h>
|
|
#include <kglobal.h>
|
|
#include <kurl.h>
|
|
|
|
#include <kimap/rfccodecs.h>
|
|
using namespace KIMAP;
|
|
|
|
static sasl_callback_t callbacks[] = {
|
|
{ SASL_CB_ECHOPROMPT, NULL, NULL },
|
|
{ SASL_CB_NOECHOPROMPT, NULL, NULL },
|
|
{ SASL_CB_GETREALM, NULL, NULL },
|
|
{ SASL_CB_USER, NULL, NULL },
|
|
{ SASL_CB_AUTHNAME, NULL, NULL },
|
|
{ SASL_CB_PASS, NULL, NULL },
|
|
{ SASL_CB_CANON_USER, NULL, NULL },
|
|
{ SASL_CB_LIST_END, NULL, NULL }
|
|
};
|
|
|
|
imapParser::imapParser ()
|
|
{
|
|
currentState = ISTATE_NO;
|
|
commandCounter = 0;
|
|
lastHandled = 0;
|
|
}
|
|
|
|
imapParser::~imapParser ()
|
|
{
|
|
delete lastHandled;
|
|
lastHandled = 0;
|
|
}
|
|
|
|
CommandPtr
|
|
imapParser::doCommand (CommandPtr aCmd)
|
|
{
|
|
int pl = 0;
|
|
sendCommand( aCmd );
|
|
while ( pl != -1 && !aCmd->isComplete() ) {
|
|
while ( ( pl = parseLoop() ) == 0 ) {
|
|
;
|
|
}
|
|
}
|
|
return aCmd;
|
|
}
|
|
|
|
CommandPtr
|
|
imapParser::sendCommand (CommandPtr aCmd)
|
|
{
|
|
aCmd->setId( QString::number( commandCounter++ ) );
|
|
sentQueue.append( aCmd );
|
|
|
|
continuation.resize( 0 );
|
|
const QString& command = aCmd->command();
|
|
|
|
if ( command == "SELECT" || command == "EXAMINE" ) {
|
|
// we need to know which box we are selecting
|
|
parseString p;
|
|
p.fromString( aCmd->parameter() );
|
|
currentBox = parseOneWord( p );
|
|
kDebug( 7116 ) << "imapParser::sendCommand - setting current box to" << currentBox;
|
|
} else if ( command == "CLOSE" ) {
|
|
// we no longer have a box open
|
|
currentBox.clear();
|
|
} else if ( command.contains( "SEARCH" ) ||
|
|
command == "GETACL" ||
|
|
command == "LISTRIGHTS" ||
|
|
command == "MYRIGHTS" ||
|
|
command == "GETANNOTATION" ||
|
|
command == "NAMESPACE" ||
|
|
command == "GETQUOTAROOT" ||
|
|
command == "GETQUOTA" ||
|
|
command == "X-GET-OTHER-USERS" ||
|
|
command == "X-GET-DELEGATES" ||
|
|
command == "X-GET-OUT-OF-OFFICE" ) {
|
|
lastResults.clear();
|
|
} else if ( command == "LIST" ||
|
|
command == "LSUB" ) {
|
|
listResponses.clear();
|
|
}
|
|
parseWriteLine( aCmd->getStr() );
|
|
return aCmd;
|
|
}
|
|
|
|
bool
|
|
imapParser::clientLogin (const QString & aUser, const QString & aPass,
|
|
QString & resultInfo)
|
|
{
|
|
CommandPtr cmd;
|
|
bool retVal = false;
|
|
|
|
cmd = doCommand( CommandPtr( new imapCommand( "LOGIN", "\"" + KIMAP::quoteIMAP( aUser ) +
|
|
"\" \"" + KIMAP::quoteIMAP( aPass ) + "\"" ) ) );
|
|
|
|
if ( cmd->result() == "OK" ) {
|
|
currentState = ISTATE_LOGIN;
|
|
retVal = true;
|
|
}
|
|
resultInfo = cmd->resultInfo();
|
|
completeQueue.removeAll( cmd );
|
|
return retVal;
|
|
}
|
|
|
|
static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in )
|
|
{
|
|
kDebug( 7116 ) << "sasl_interact";
|
|
sasl_interact_t *interact = ( sasl_interact_t * ) in;
|
|
|
|
//some mechanisms do not require username && pass, so it doesn't need a popup
|
|
//window for getting this info
|
|
for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
|
|
if ( interact->id == SASL_CB_AUTHNAME ||
|
|
interact->id == SASL_CB_PASS ) {
|
|
|
|
if ( ai.username.isEmpty() || ai.password.isEmpty() ) {
|
|
if ( !slave->openPasswordDialog( ai ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
interact = ( sasl_interact_t * ) in;
|
|
while ( interact->id != SASL_CB_LIST_END ) {
|
|
kDebug( 7116 ) << "SASL_INTERACT id:" << interact->id;
|
|
switch ( interact->id ) {
|
|
case SASL_CB_USER:
|
|
case SASL_CB_AUTHNAME:
|
|
kDebug( 7116 ) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'";
|
|
interact->result = strdup( ai.username.toUtf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
case SASL_CB_PASS:
|
|
kDebug( 7116 ) << "SASL_CB_PASS: [hidden]";
|
|
interact->result = strdup( ai.password.toUtf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
default:
|
|
interact->result = 0;
|
|
interact->len = 0;
|
|
break;
|
|
}
|
|
interact++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai,
|
|
const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo)
|
|
{
|
|
bool retVal = false;
|
|
int result;
|
|
sasl_conn_t *conn = 0;
|
|
sasl_interact_t *client_interact = 0;
|
|
const char *out = 0;
|
|
uint outlen = 0;
|
|
const char *mechusing = 0;
|
|
QByteArray tmp, challenge;
|
|
|
|
kDebug( 7116 ) << "aAuth:" << aAuth << " FQDN:" << aFQDN << " isSSL:" << isSSL;
|
|
|
|
// see if server supports this authenticator
|
|
if ( !hasCapability( "AUTH=" + aAuth ) ) {
|
|
return false;
|
|
}
|
|
|
|
// result = sasl_client_new( isSSL ? "imaps" : "imap",
|
|
result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri
|
|
must be 'imap'. I don't know if it's good or bad. */
|
|
aFQDN.toLatin1(),
|
|
0, 0, callbacks, 0, &conn );
|
|
|
|
if ( result != SASL_OK ) {
|
|
kDebug( 7116 ) << "sasl_client_new failed with:" << result;
|
|
resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
result = sasl_client_start( conn, aAuth.toLatin1(), &client_interact,
|
|
hasCapability( "SASL-IR" ) ? &out : 0, &outlen, &mechusing );
|
|
|
|
if ( result == SASL_INTERACT ) {
|
|
if ( !sasl_interact( slave, ai, client_interact ) ) {
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
}
|
|
} while ( result == SASL_INTERACT );
|
|
|
|
if ( result != SASL_CONTINUE && result != SASL_OK ) {
|
|
kDebug( 7116 ) << "sasl_client_start failed with:" << result;
|
|
resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
CommandPtr cmd;
|
|
|
|
tmp = QByteArray::fromRawData( out, outlen );
|
|
challenge = tmp.toBase64();
|
|
tmp.clear();
|
|
// then lets try it
|
|
QString firstCommand = aAuth;
|
|
if ( !challenge.isEmpty() ) {
|
|
firstCommand += ' ';
|
|
firstCommand += QString::fromLatin1( challenge.data(), challenge.size() );
|
|
}
|
|
cmd = sendCommand( CommandPtr( new imapCommand( "AUTHENTICATE", firstCommand.toLatin1() ) ) );
|
|
|
|
int pl = 0;
|
|
while ( pl != -1 && !cmd->isComplete() ) {
|
|
//read the next line
|
|
while ( ( pl = parseLoop() ) == 0 ) {
|
|
;
|
|
}
|
|
|
|
if ( !continuation.isEmpty() ) {
|
|
// kDebug( 7116 ) << "S:" << QCString( continuation.data(), continuation.size() + 1 );
|
|
if ( continuation.size() > 4 ) {
|
|
tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 );
|
|
challenge = QByteArray::fromBase64( tmp );
|
|
// kDebug( 7116 ) << "S-1:" << QCString( challenge.data(), challenge.size() + 1 );
|
|
tmp.clear();
|
|
}
|
|
|
|
do {
|
|
result = sasl_client_step( conn, challenge.isEmpty() ? 0 : challenge.data(),
|
|
challenge.size(),
|
|
&client_interact,
|
|
&out, &outlen );
|
|
|
|
if ( result == SASL_INTERACT ) {
|
|
if ( !sasl_interact( slave, ai, client_interact ) ) {
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
}
|
|
} while ( result == SASL_INTERACT );
|
|
|
|
if ( result != SASL_CONTINUE && result != SASL_OK ) {
|
|
kDebug( 7116 ) << "sasl_client_step failed with:" << result;
|
|
resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
|
|
tmp = QByteArray::fromRawData( out, outlen );
|
|
// kDebug( 7116 ) << "C-1:" << QCString( tmp.data(), tmp.size() + 1 );
|
|
challenge = tmp.toBase64();
|
|
tmp.clear();
|
|
// kDebug( 7116 ) << "C:" << QCString( challenge.data(), challenge.size() + 1 );
|
|
parseWriteLine( challenge );
|
|
continuation.resize( 0 );
|
|
}
|
|
}
|
|
|
|
if ( cmd->result() == "OK" ) {
|
|
currentState = ISTATE_LOGIN;
|
|
retVal = true;
|
|
}
|
|
resultInfo = cmd->resultInfo();
|
|
completeQueue.removeAll( cmd );
|
|
|
|
sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection.
|
|
return retVal;
|
|
}
|
|
|
|
void
|
|
imapParser::parseUntagged (parseString & result)
|
|
{
|
|
//kDebug( 7116 ) << "imapParser::parseUntagged - '" << result.cstr() << "'";
|
|
|
|
parseOneWord( result ); // *
|
|
QByteArray what = parseLiteral( result ); // see whats coming next
|
|
|
|
switch ( what[0] ) {
|
|
//the status responses
|
|
case 'B': // BAD or BYE
|
|
if ( qstrncmp( what, "BAD", what.size() ) == 0 ) {
|
|
parseResult( what, result );
|
|
} else if ( qstrncmp( what, "BYE", what.size() ) == 0 ) {
|
|
parseResult( what, result );
|
|
if ( sentQueue.count() ) {
|
|
// BYE that interrupts a command -> copy the reason for it
|
|
CommandPtr current = sentQueue.at( 0 );
|
|
current->setResultInfo( result.cstr() );
|
|
}
|
|
currentState = ISTATE_NO;
|
|
}
|
|
break;
|
|
|
|
case 'N': // NO
|
|
if ( what[1] == 'O' && what.size() == 2 ) {
|
|
parseResult( what, result );
|
|
} else if ( qstrncmp( what, "NAMESPACE", what.size() ) == 0 ) {
|
|
parseNamespace( result );
|
|
}
|
|
break;
|
|
|
|
case 'O': // OK
|
|
if ( what[1] == 'K' && what.size() == 2 ) {
|
|
parseResult( what, result );
|
|
} else if ( qstrncmp( what, "OTHER-USER", 10 ) == 0 ) { // X-GET-OTHER-USER
|
|
parseOtherUser( result );
|
|
} else if ( qstrncmp( what, "OUT-OF-OFFICE", 13 ) == 0 ) { // X-GET-OUT-OF-OFFICE
|
|
parseOutOfOffice( result );
|
|
}
|
|
break;
|
|
case 'D':
|
|
if ( qstrncmp( what, "DELEGATE", 8 ) == 0 ) { // X-GET-DELEGATES
|
|
parseDelegate( result );
|
|
}
|
|
break;
|
|
|
|
case 'P': // PREAUTH
|
|
if ( qstrncmp( what, "PREAUTH", what.size() ) == 0 ) {
|
|
parseResult( what, result );
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
break;
|
|
|
|
// parse the other responses
|
|
case 'C': // CAPABILITY
|
|
if ( qstrncmp( what, "CAPABILITY", what.size() ) == 0 ) {
|
|
parseCapability( result );
|
|
}
|
|
break;
|
|
|
|
case 'F': // FLAGS
|
|
if ( qstrncmp( what, "FLAGS", what.size() ) == 0 ) {
|
|
parseFlags( result );
|
|
}
|
|
break;
|
|
|
|
case 'L': // LIST or LSUB or LISTRIGHTS
|
|
if ( qstrncmp( what, "LIST", what.size() ) == 0 ) {
|
|
parseList( result );
|
|
} else if ( qstrncmp( what, "LSUB", what.size() ) == 0 ) {
|
|
parseLsub( result );
|
|
} else if ( qstrncmp( what, "LISTRIGHTS", what.size() ) == 0 ) {
|
|
parseListRights( result );
|
|
}
|
|
break;
|
|
|
|
case 'M': // MYRIGHTS
|
|
if ( qstrncmp( what, "MYRIGHTS", what.size() ) == 0 ) {
|
|
parseMyRights( result );
|
|
}
|
|
break;
|
|
case 'S': // SEARCH or STATUS
|
|
if ( qstrncmp( what, "SEARCH", what.size() ) == 0 ) {
|
|
parseSearch( result );
|
|
} else if ( qstrncmp( what, "STATUS", what.size() ) == 0 ) {
|
|
parseStatus( result );
|
|
}
|
|
break;
|
|
|
|
case 'A': // ACL or ANNOTATION
|
|
if ( qstrncmp( what, "ACL", what.size() ) == 0 ) {
|
|
parseAcl( result );
|
|
} else if ( qstrncmp( what, "ANNOTATION", what.size() ) == 0 ) {
|
|
parseAnnotation( result );
|
|
}
|
|
break;
|
|
case 'Q': // QUOTA or QUOTAROOT
|
|
if ( what.size() > 5 && qstrncmp( what, "QUOTAROOT", what.size() ) == 0 ) {
|
|
parseQuotaRoot( result );
|
|
} else if ( qstrncmp( what, "QUOTA", what.size() ) == 0 ) {
|
|
parseQuota( result );
|
|
}
|
|
break;
|
|
case 'X': // Custom command
|
|
{
|
|
parseCustom( result );
|
|
}
|
|
break;
|
|
default:
|
|
//better be a number
|
|
{
|
|
ulong number;
|
|
bool valid;
|
|
|
|
number = what.toUInt( &valid );
|
|
if ( valid ) {
|
|
what = parseLiteral( result );
|
|
switch ( what[0] ) {
|
|
case 'E':
|
|
if ( qstrncmp( what, "EXISTS", what.size() ) == 0 ) {
|
|
parseExists( number, result );
|
|
} else if ( qstrncmp( what, "EXPUNGE", what.size() ) == 0 ) {
|
|
parseExpunge( number, result );
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if ( qstrncmp( what, "FETCH", what.size() ) == 0 ) {
|
|
seenUid.clear();
|
|
parseFetch( number, result );
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
if ( qstrncmp( what, "STORE", what.size() ) == 0 ) { // deprecated store
|
|
seenUid.clear();
|
|
parseFetch( number, result );
|
|
}
|
|
break;
|
|
|
|
case 'R':
|
|
if ( qstrncmp( what, "RECENT", what.size() ) == 0 ) {
|
|
parseRecent( number, result );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
} //switch
|
|
} //func
|
|
|
|
void
|
|
imapParser::parseResult (QByteArray & result, parseString & rest,
|
|
const QString & command)
|
|
{
|
|
if ( command == "SELECT" ) {
|
|
selectInfo.setReadWrite( true );
|
|
}
|
|
|
|
if ( rest[0] == '[' ) {
|
|
rest.pos++;
|
|
QByteArray option = parseOneWord( rest, true );
|
|
|
|
switch ( option[0] ) {
|
|
case 'A': // ALERT
|
|
if ( option == "ALERT" ) {
|
|
rest.pos = rest.data.indexOf( ']', rest.pos ) + 1;
|
|
// The alert text is after [ALERT].
|
|
// Is this correct or do we need to care about litterals?
|
|
selectInfo.setAlert( rest.cstr() );
|
|
}
|
|
break;
|
|
|
|
case 'N': // NEWNAME
|
|
if ( option == "NEWNAME" ) {
|
|
}
|
|
break;
|
|
|
|
case 'P': //PARSE or PERMANENTFLAGS
|
|
if ( option == "PARSE" ) {
|
|
} else if ( option == "PERMANENTFLAGS" ) {
|
|
uint end = rest.data.indexOf( ']', rest.pos );
|
|
QByteArray flags( rest.data.data() + rest.pos, end - rest.pos );
|
|
selectInfo.setPermanentFlags( flags );
|
|
rest.pos = end;
|
|
}
|
|
break;
|
|
|
|
case 'R': //READ-ONLY or READ-WRITE
|
|
if ( option == "READ-ONLY" ) {
|
|
selectInfo.setReadWrite( false );
|
|
} else if ( option == "READ-WRITE" ) {
|
|
selectInfo.setReadWrite( true );
|
|
}
|
|
break;
|
|
|
|
case 'T': //TRYCREATE
|
|
if ( option == "TRYCREATE" ) {
|
|
}
|
|
break;
|
|
|
|
case 'U': //UIDVALIDITY or UNSEEN
|
|
if ( option == "UIDVALIDITY" ) {
|
|
ulong value;
|
|
if ( parseOneNumber( rest, value ) ) {
|
|
selectInfo.setUidValidity( value );
|
|
}
|
|
} else if ( option == "UNSEEN" ) {
|
|
ulong value;
|
|
if ( parseOneNumber( rest, value ) ) {
|
|
selectInfo.setUnseen( value );
|
|
}
|
|
} else if ( option == "UIDNEXT" ) {
|
|
ulong value;
|
|
if ( parseOneNumber( rest, value ) ) {
|
|
selectInfo.setUidNext( value );
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
if ( rest[0] == ']' ) {
|
|
rest.pos++; //tie off ]
|
|
}
|
|
skipWS( rest );
|
|
}
|
|
|
|
if ( command.isEmpty() ) {
|
|
// This happens when parsing an intermediate result line (those that start with '*').
|
|
// No state change involved, so we can stop here.
|
|
return;
|
|
}
|
|
|
|
switch ( command[0].toLatin1() ) {
|
|
case 'A':
|
|
if ( command == "AUTHENTICATE" ) {
|
|
if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'L':
|
|
if ( command == "LOGIN" ) {
|
|
if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'E':
|
|
if ( command == "EXAMINE" ) {
|
|
if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
|
|
currentState = ISTATE_SELECT;
|
|
} else {
|
|
if ( currentState == ISTATE_SELECT ) {
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
currentBox.clear();
|
|
}
|
|
kDebug( 7116 ) << "imapParser::parseResult - current box is now" << currentBox;
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
if ( command == "SELECT" ) {
|
|
if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
|
|
currentState = ISTATE_SELECT;
|
|
} else {
|
|
if ( currentState == ISTATE_SELECT ) {
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
currentBox.clear();
|
|
}
|
|
kDebug( 7116 ) << "imapParser::parseResult - current box is now" << currentBox;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void imapParser::parseCapability (parseString & result)
|
|
{
|
|
QByteArray data = result.cstr();
|
|
kAsciiToLower( data.data() );
|
|
imapCapabilities = QString::fromLatin1( data ).split( ' ', QString::SkipEmptyParts );
|
|
}
|
|
|
|
void imapParser::parseFlags (parseString & result)
|
|
{
|
|
selectInfo.setFlags( result.cstr() );
|
|
}
|
|
|
|
void imapParser::parseList (parseString & result)
|
|
{
|
|
imapList this_one;
|
|
|
|
if ( result[0] != '(' ) {
|
|
return; //not proper format for us
|
|
}
|
|
|
|
result.pos++; // tie off (
|
|
|
|
this_one.parseAttributes( result );
|
|
|
|
result.pos++; // tie off )
|
|
skipWS( result );
|
|
|
|
this_one.setHierarchyDelimiter( parseLiteral( result ) );
|
|
this_one.setName( QString::fromUtf8( KIMAP::decodeImapFolderName( parseLiteral( result ) ) ) ); // decode modified UTF7
|
|
|
|
listResponses.append( this_one );
|
|
}
|
|
|
|
void imapParser::parseLsub (parseString & result)
|
|
{
|
|
imapList this_one( result.cstr(), *this );
|
|
listResponses.append( this_one );
|
|
}
|
|
|
|
void imapParser::parseListRights (parseString & result)
|
|
{
|
|
parseOneWord( result ); // skip mailbox name
|
|
parseOneWord( result ); // skip user id
|
|
while ( true ) {
|
|
const QByteArray word = parseOneWord( result );
|
|
if ( word.isEmpty() ) {
|
|
break;
|
|
}
|
|
lastResults.append( word );
|
|
}
|
|
}
|
|
|
|
void imapParser::parseAcl (parseString & result)
|
|
{
|
|
parseOneWord( result ); // skip mailbox name
|
|
// The result is user1 perm1 user2 perm2 etc. The caller will sort it out.
|
|
while ( !result.isEmpty() ) {
|
|
const QByteArray word = parseLiteral( result );
|
|
if ( word.isEmpty() ) {
|
|
break;
|
|
}
|
|
lastResults.append( word );
|
|
}
|
|
}
|
|
|
|
void imapParser::parseAnnotation (parseString & result)
|
|
{
|
|
parseOneWord( result ); // skip mailbox name
|
|
skipWS( result );
|
|
parseOneWord( result ); // skip entry name (we know it since we don't allow wildcards in it)
|
|
skipWS( result );
|
|
if ( result.isEmpty() || result[0] != '(' ) {
|
|
return;
|
|
}
|
|
result.pos++;
|
|
skipWS( result );
|
|
// The result is name1 value1 name2 value2 etc. The caller will sort it out.
|
|
while ( !result.isEmpty() && result[0] != ')' ) {
|
|
const QByteArray word = parseLiteral( result );
|
|
if ( word.isEmpty() ) {
|
|
break;
|
|
}
|
|
lastResults.append( word );
|
|
}
|
|
}
|
|
|
|
void imapParser::parseQuota (parseString & result)
|
|
{
|
|
// quota_response ::= "QUOTA" SP astring SP quota_list
|
|
// quota_list ::= "(" #quota_resource ")"
|
|
// quota_resource ::= atom SP number SP number
|
|
QByteArray root = parseOneWord( result );
|
|
if ( root.isEmpty() ) {
|
|
lastResults.append( "" );
|
|
} else {
|
|
lastResults.append( root );
|
|
}
|
|
if ( result.isEmpty() || result[0] != '(' ) {
|
|
return;
|
|
}
|
|
result.pos++;
|
|
skipWS( result );
|
|
QStringList triplet;
|
|
while ( !result.isEmpty() && result[0] != ')' ) {
|
|
const QByteArray word = parseLiteral( result );
|
|
if ( word.isEmpty() ) {
|
|
break;
|
|
}
|
|
triplet.append( word );
|
|
}
|
|
lastResults.append( triplet.join( " " ) );
|
|
}
|
|
|
|
void imapParser::parseQuotaRoot (parseString & result)
|
|
{
|
|
// quotaroot_response
|
|
// ::= "QUOTAROOT" SP astring *(SP astring)
|
|
parseOneWord( result ); // skip mailbox name
|
|
skipWS( result );
|
|
if ( result.isEmpty() ) {
|
|
return;
|
|
}
|
|
QStringList roots;
|
|
while ( !result.isEmpty() ) {
|
|
const QByteArray word = parseLiteral( result );
|
|
if ( word.isEmpty() ) {
|
|
break;
|
|
}
|
|
roots.append( word );
|
|
}
|
|
lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) );
|
|
}
|
|
|
|
void imapParser::parseCustom (parseString & result)
|
|
{
|
|
QByteArray word = parseLiteral( result, false, false );
|
|
lastResults.append( word );
|
|
}
|
|
|
|
void imapParser::parseOtherUser (parseString & result)
|
|
{
|
|
lastResults.append( parseOneWord( result ) );
|
|
}
|
|
|
|
void imapParser::parseDelegate (parseString & result)
|
|
{
|
|
const QString email = parseOneWord( result );
|
|
|
|
QStringList rights;
|
|
while ( !result.isEmpty() ) {
|
|
QByteArray word = parseLiteral( result, false, false );
|
|
rights.append( word );
|
|
}
|
|
|
|
lastResults.append( email + ':' + rights.join( "," ) );
|
|
}
|
|
|
|
void imapParser::parseOutOfOffice (parseString & result)
|
|
{
|
|
const QString state = parseOneWord( result );
|
|
parseOneWord( result ); // skip encoding
|
|
|
|
QByteArray msg = parseLiteral( result, false, false );
|
|
|
|
lastResults.append( state + '^' + QString::fromUtf8( msg ) );
|
|
}
|
|
|
|
void imapParser::parseMyRights (parseString & result)
|
|
{
|
|
parseOneWord( result ); // skip mailbox name
|
|
Q_ASSERT( lastResults.isEmpty() ); // we can only be called once
|
|
lastResults.append( parseOneWord( result ) );
|
|
}
|
|
|
|
void imapParser::parseSearch (parseString & result)
|
|
{
|
|
ulong value;
|
|
|
|
while ( parseOneNumber( result, value ) ) {
|
|
lastResults.append( QString::number( value ) );
|
|
}
|
|
}
|
|
|
|
void imapParser::parseStatus (parseString & inWords)
|
|
{
|
|
lastStatus = imapInfo();
|
|
|
|
parseLiteral( inWords ); // swallow the box
|
|
if ( inWords[0] != '(' ) {
|
|
return;
|
|
}
|
|
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
ulong value;
|
|
|
|
QByteArray label = parseOneWord( inWords );
|
|
if ( parseOneNumber( inWords, value ) ) {
|
|
if ( label == "MESSAGES" ) {
|
|
lastStatus.setCount( value );
|
|
} else if ( label == "RECENT" ) {
|
|
lastStatus.setRecent( value );
|
|
} else if ( label == "UIDVALIDITY" ) {
|
|
lastStatus.setUidValidity( value );
|
|
} else if ( label == "UNSEEN" ) {
|
|
lastStatus.setUnseen( value );
|
|
} else if ( label == "UIDNEXT" ) {
|
|
lastStatus.setUidNext( value );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
}
|
|
|
|
void imapParser::parseExists (ulong value, parseString & result)
|
|
{
|
|
selectInfo.setCount( value );
|
|
result.pos = result.data.size();
|
|
}
|
|
|
|
void imapParser::parseExpunge (ulong value, parseString & result)
|
|
{
|
|
Q_UNUSED( value );
|
|
Q_UNUSED( result );
|
|
}
|
|
|
|
void imapParser::parseAddressList (parseString & inWords, QList<mailAddress *>& list)
|
|
{
|
|
if ( inWords.isEmpty() ) {
|
|
return;
|
|
}
|
|
if ( inWords[0] != '(' ) {
|
|
parseOneWord( inWords ); // parse NIL
|
|
} else {
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
if ( inWords[0] == '(' ) {
|
|
mailAddress *addr = new mailAddress;
|
|
parseAddress( inWords, *addr );
|
|
list.append( addr );
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !inWords.isEmpty() && inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
}
|
|
}
|
|
|
|
const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal)
|
|
{
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
retVal.setFullName( parseLiteral( inWords ) );
|
|
retVal.setCommentRaw( parseLiteral( inWords ) );
|
|
retVal.setUser( parseLiteral( inWords ) );
|
|
retVal.setHost( parseLiteral( inWords ) );
|
|
|
|
if ( !inWords.isEmpty() && inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
return retVal;
|
|
}
|
|
|
|
mailHeader * imapParser::parseEnvelope (parseString & inWords)
|
|
{
|
|
mailHeader *envelope = 0;
|
|
|
|
if ( inWords[0] != '(' ) {
|
|
return envelope;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
envelope = new mailHeader;
|
|
|
|
//date
|
|
envelope->setDate( parseLiteral( inWords ) );
|
|
|
|
//subject
|
|
envelope->setSubject( parseLiteral( inWords ) );
|
|
|
|
QList<mailAddress *> list;
|
|
|
|
//from
|
|
parseAddressList( inWords, list );
|
|
if ( !list.isEmpty() ) {
|
|
envelope->setFrom( *list.last() );
|
|
list.clear();
|
|
}
|
|
|
|
//sender
|
|
parseAddressList(inWords, list);
|
|
if ( !list.isEmpty() ) {
|
|
envelope->setSender( *list.last() );
|
|
list.clear();
|
|
}
|
|
|
|
//reply-to
|
|
parseAddressList( inWords, list );
|
|
if ( !list.isEmpty() ) {
|
|
envelope->setReplyTo( *list.last() );
|
|
list.clear();
|
|
}
|
|
|
|
//to
|
|
parseAddressList( inWords, envelope->to() );
|
|
|
|
//cc
|
|
parseAddressList( inWords, envelope->cc() );
|
|
|
|
//bcc
|
|
parseAddressList( inWords, envelope->bcc() );
|
|
|
|
//in-reply-to
|
|
envelope->setInReplyTo( parseLiteral( inWords ) );
|
|
|
|
//message-id
|
|
envelope->setMessageId( parseLiteral( inWords ) );
|
|
|
|
// see if we have more to come
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
//eat the extensions to this part
|
|
if ( inWords[0] == '(' ) {
|
|
parseSentence( inWords );
|
|
} else {
|
|
parseLiteral( inWords );
|
|
}
|
|
}
|
|
|
|
if ( !inWords.isEmpty() && inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
return envelope;
|
|
}
|
|
|
|
// parse parameter pairs into a dictionary
|
|
// caller must clean up the dictionary items
|
|
QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords)
|
|
{
|
|
QByteArray disposition;
|
|
QHash < QByteArray, QString > retVal;
|
|
|
|
if ( inWords[0] != '(' ) {
|
|
//disposition only
|
|
disposition = parseOneWord( inWords );
|
|
} else {
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
//disposition
|
|
disposition = parseOneWord( inWords );
|
|
|
|
retVal = parseParameters( inWords );
|
|
if ( inWords[0] != ')' ) {
|
|
return retVal;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
}
|
|
|
|
if ( !disposition.isEmpty() ) {
|
|
retVal.insert( "content-disposition", QString( disposition ) );
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// parse parameter pairs into a dictionary
|
|
// caller must clean up the dictionary items
|
|
QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords)
|
|
{
|
|
QHash < QByteArray, QString > retVal;
|
|
|
|
if ( inWords[0] != '(' ) {
|
|
//better be NIL
|
|
parseOneWord( inWords );
|
|
} else {
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
const QByteArray l1 = parseLiteral( inWords );
|
|
const QByteArray l2 = parseLiteral( inWords );
|
|
retVal.insert( l1.toLower(), QString( l2 ) );
|
|
}
|
|
|
|
if ( inWords[0] != ')' ) {
|
|
return retVal;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
mimeHeader * imapParser::parseSimplePart (parseString & inWords,
|
|
QString & inSection, mimeHeader * localPart)
|
|
{
|
|
QByteArray subtype;
|
|
QByteArray typeStr;
|
|
QHash < QByteArray, QString > parameters;
|
|
ulong size;
|
|
|
|
if ( inWords[0] != '(' ) {
|
|
return 0;
|
|
}
|
|
|
|
if ( !localPart ) {
|
|
localPart = new mimeHeader;
|
|
}
|
|
|
|
localPart->setPartSpecifier( inSection );
|
|
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
//body type
|
|
typeStr = parseLiteral( inWords );
|
|
|
|
//body subtype
|
|
subtype = parseLiteral( inWords );
|
|
|
|
localPart->setType( typeStr + '/' + subtype );
|
|
|
|
//body parameter parenthesized list
|
|
parameters = parseParameters( inWords );
|
|
{
|
|
QHashIterator < QByteArray, QString > it( parameters );
|
|
|
|
while ( it.hasNext() ) {
|
|
it.next();
|
|
localPart->setTypeParm( it.key(), it.value() );
|
|
}
|
|
parameters.clear();
|
|
}
|
|
|
|
//body id
|
|
localPart->setID( parseLiteral( inWords ) );
|
|
|
|
//body description
|
|
localPart->setDescription( parseLiteral( inWords ) );
|
|
|
|
//body encoding
|
|
localPart->setEncoding( parseLiteral( inWords ) );
|
|
|
|
//body size
|
|
if ( parseOneNumber( inWords, size ) ) {
|
|
localPart->setLength( size );
|
|
}
|
|
|
|
// type specific extensions
|
|
if ( localPart->getType().toUpper() == "MESSAGE/RFC822" ) {
|
|
//envelope structure
|
|
mailHeader *envelope = parseEnvelope( inWords );
|
|
|
|
//body structure
|
|
parseBodyStructure( inWords, inSection, envelope );
|
|
|
|
localPart->setNestedMessage( envelope );
|
|
|
|
//text lines
|
|
ulong lines;
|
|
parseOneNumber( inWords, lines );
|
|
} else {
|
|
if ( typeStr == "TEXT" ) {
|
|
//text lines
|
|
ulong lines;
|
|
parseOneNumber( inWords, lines );
|
|
}
|
|
|
|
// md5
|
|
parseLiteral( inWords );
|
|
|
|
// body disposition
|
|
parameters = parseDisposition( inWords );
|
|
{
|
|
QString disposition = parameters["content-disposition"];
|
|
|
|
localPart->setDisposition( disposition.toLatin1() );
|
|
QHashIterator < QByteArray, QString > it( parameters );
|
|
while ( it.hasNext() ) {
|
|
it.next();
|
|
localPart->setDispositionParm( it.key(), it.value() );
|
|
}
|
|
parameters.clear();
|
|
}
|
|
|
|
// body language
|
|
parseSentence( inWords );
|
|
}
|
|
|
|
// see if we have more to come
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
//eat the extensions to this part
|
|
if ( inWords[0] == '(' ) {
|
|
parseSentence( inWords );
|
|
} else {
|
|
parseLiteral( inWords );
|
|
}
|
|
}
|
|
|
|
if ( inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
return localPart;
|
|
}
|
|
|
|
mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
|
|
QString & inSection, mimeHeader * localPart)
|
|
{
|
|
bool init = false;
|
|
if ( inSection.isEmpty() ) {
|
|
// first run
|
|
init = true;
|
|
// assume one part
|
|
inSection = '1';
|
|
}
|
|
int section = 0;
|
|
|
|
if ( inWords[0] != '(' ) {
|
|
// skip ""
|
|
parseOneWord( inWords );
|
|
return 0;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
if ( inWords[0] == '(' ) {
|
|
QByteArray subtype;
|
|
QHash< QByteArray, QString > parameters;
|
|
QString outSection;
|
|
|
|
if ( !localPart ) {
|
|
localPart = new mimeHeader;
|
|
} else {
|
|
// might be filled from an earlier run
|
|
localPart->clearNestedParts();
|
|
localPart->clearTypeParameters();
|
|
localPart->clearDispositionParameters();
|
|
// an envelope was passed in so this is the multipart header
|
|
outSection = inSection + ".HEADER";
|
|
}
|
|
if ( inWords[0] == '(' && init ) {
|
|
inSection = '0';
|
|
}
|
|
|
|
// set the section
|
|
if ( !outSection.isEmpty() ) {
|
|
localPart->setPartSpecifier( outSection );
|
|
} else {
|
|
localPart->setPartSpecifier( inSection );
|
|
}
|
|
|
|
// is multipart (otherwise it is a simplepart and handled later)
|
|
while ( inWords[0] == '(' ) {
|
|
outSection = QString::number( ++section );
|
|
if ( !init ) {
|
|
outSection = inSection + '.' + outSection;
|
|
}
|
|
mimeHeader *subpart = parseBodyStructure( inWords, outSection, 0 );
|
|
localPart->addNestedPart( subpart );
|
|
}
|
|
|
|
// fetch subtype
|
|
subtype = parseOneWord( inWords );
|
|
|
|
localPart->setType( "MULTIPART/" + subtype );
|
|
|
|
// fetch parameters
|
|
parameters = parseParameters( inWords );
|
|
{
|
|
QHashIterator < QByteArray, QString > it( parameters );
|
|
|
|
while ( it.hasNext() ) {
|
|
it.next();
|
|
localPart->setTypeParm( it.key(), it.value() );
|
|
}
|
|
parameters.clear();
|
|
}
|
|
|
|
// body disposition
|
|
parameters = parseDisposition( inWords );
|
|
{
|
|
QString disposition = parameters["content-disposition"];
|
|
|
|
localPart->setDisposition( disposition.toLatin1() );
|
|
QHashIterator < QByteArray, QString > it( parameters );
|
|
while ( it.hasNext() ) {
|
|
it.next();
|
|
localPart->setDispositionParm( it.key(), it.value() );
|
|
}
|
|
parameters.clear();
|
|
}
|
|
|
|
// body language
|
|
parseSentence( inWords );
|
|
|
|
} else {
|
|
// is simple part
|
|
inWords.pos--;
|
|
inWords.data[inWords.pos] = '('; //fake a sentence
|
|
if ( localPart ) {
|
|
inSection = inSection + ".1";
|
|
}
|
|
localPart = parseSimplePart( inWords, inSection, localPart );
|
|
inWords.pos--;
|
|
inWords.data[inWords.pos] = ')'; //remove fake
|
|
}
|
|
|
|
// see if we have more to come
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
//eat the extensions to this part
|
|
if ( inWords[0] == '(' ) {
|
|
parseSentence( inWords );
|
|
} else {
|
|
parseLiteral( inWords );
|
|
}
|
|
}
|
|
|
|
if ( inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
return localPart;
|
|
}
|
|
|
|
void imapParser::parseBody (parseString & inWords)
|
|
{
|
|
// see if we got a part specifier
|
|
if ( inWords[0] == '[' ) {
|
|
QByteArray specifier;
|
|
QByteArray label;
|
|
inWords.pos++;
|
|
|
|
specifier = parseOneWord( inWords, true );
|
|
|
|
if ( inWords[0] == '(' ) {
|
|
inWords.pos++;
|
|
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
label = parseOneWord( inWords );
|
|
}
|
|
|
|
if ( inWords[0] == ')' ) {
|
|
inWords.pos++;
|
|
}
|
|
}
|
|
if ( inWords[0] == ']' ) {
|
|
inWords.pos++;
|
|
}
|
|
skipWS( inWords );
|
|
|
|
// parse the header
|
|
if ( qstrncmp( specifier, "0", specifier.size() ) == 0 ) {
|
|
mailHeader *envelope = 0;
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
}
|
|
|
|
if ( !envelope || seenUid.isEmpty() ) {
|
|
kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
|
|
// don't know where to put it, throw it away
|
|
parseLiteral( inWords, true );
|
|
} else {
|
|
kDebug( 7116 ) << "imapParser::parseBody - reading" << envelope << seenUid.toLatin1();
|
|
// fill it up with data
|
|
QString theHeader = parseLiteral( inWords, true );
|
|
mimeIOQString myIO;
|
|
|
|
myIO.setString( theHeader );
|
|
envelope->parseHeader( myIO );
|
|
}
|
|
} else if ( qstrncmp( specifier, "HEADER.FIELDS", specifier.size() ) == 0 ) {
|
|
// BODY[HEADER.FIELDS(References)] {n}
|
|
//kDebug( 7116 ) << "imapParser::parseBody - HEADER.FIELDS:"
|
|
// << QCString(label.data(), label.size()+1);
|
|
if ( qstrncmp( label, "REFERENCES", label.size() ) == 0 ) {
|
|
mailHeader *envelope = 0;
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
}
|
|
|
|
if ( !envelope || seenUid.isEmpty() ) {
|
|
kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
|
|
// don't know where to put it, throw it away
|
|
parseLiteral( inWords, true );
|
|
} else {
|
|
QByteArray references = parseLiteral( inWords, true );
|
|
int start = references.indexOf( '<' );
|
|
int end = references.lastIndexOf( '>' );
|
|
if ( start < end ) {
|
|
references = references.mid( start, end - start + 1 );
|
|
}
|
|
envelope->setReferences( references.simplified() );
|
|
}
|
|
} else { // not a header we care about throw it away
|
|
parseLiteral( inWords, true );
|
|
}
|
|
} else {
|
|
if ( specifier.contains( ".MIME" ) ) {
|
|
mailHeader *envelope = new mailHeader;
|
|
QString theHeader = parseLiteral( inWords, false );
|
|
mimeIOQString myIO;
|
|
myIO.setString( theHeader );
|
|
envelope->parseHeader( myIO );
|
|
if ( lastHandled ) {
|
|
lastHandled->setHeader( envelope );
|
|
}
|
|
return;
|
|
}
|
|
// throw it away
|
|
kDebug( 7116 ) << "imapParser::parseBody - discarding" << seenUid.toLatin1();
|
|
parseLiteral( inWords, true );
|
|
}
|
|
} else { // no part specifier
|
|
mailHeader *envelope = 0;
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
}
|
|
|
|
if ( !envelope || seenUid.isEmpty() ) {
|
|
kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
|
|
// don't know where to put it, throw it away
|
|
parseSentence( inWords );
|
|
} else {
|
|
kDebug( 7116 ) << "imapParser::parseBody - reading" << envelope << seenUid.toLatin1();
|
|
// fill it up with data
|
|
QString section;
|
|
mimeHeader *body = parseBodyStructure( inWords, section, envelope );
|
|
if ( body != envelope ) {
|
|
delete body;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void imapParser::parseFetch (ulong /* value */, parseString & inWords)
|
|
{
|
|
if ( inWords[0] != '(' ) {
|
|
return;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
|
|
delete lastHandled;
|
|
lastHandled = 0;
|
|
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
if ( inWords[0] == '(' ) {
|
|
parseSentence( inWords );
|
|
} else {
|
|
const QByteArray word = parseLiteral( inWords, false, true );
|
|
|
|
switch ( word[0] ) {
|
|
case 'E':
|
|
if ( word == "ENVELOPE" ) {
|
|
mailHeader *envelope = 0;
|
|
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
} else {
|
|
lastHandled = new imapCache();
|
|
}
|
|
|
|
if ( envelope && !envelope->getMessageId().isEmpty() ) {
|
|
// we have seen this one already
|
|
// or don't know where to put it
|
|
parseSentence( inWords );
|
|
} else {
|
|
envelope = parseEnvelope( inWords );
|
|
if ( envelope ) {
|
|
envelope->setPartSpecifier( seenUid + ".0" );
|
|
lastHandled->setHeader( envelope );
|
|
lastHandled->setUid( seenUid.toULong() );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'B':
|
|
if ( word == "BODY" ) {
|
|
parseBody( inWords );
|
|
} else if ( word == "BODY[]" ) {
|
|
// Do the same as with "RFC822"
|
|
parseLiteral( inWords, true );
|
|
} else if ( word == "BODYSTRUCTURE" ) {
|
|
mailHeader *envelope = 0;
|
|
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
}
|
|
|
|
// fill it up with data
|
|
QString section;
|
|
mimeHeader *body = parseBodyStructure( inWords, section, envelope );
|
|
QByteArray data;
|
|
QDataStream stream( &data, QIODevice::WriteOnly );
|
|
if ( body ) {
|
|
body->serialize( stream );
|
|
}
|
|
parseRelay( data );
|
|
delete body;
|
|
}
|
|
break;
|
|
|
|
case 'U':
|
|
if ( word == "UID" ) {
|
|
seenUid = parseOneWord( inWords );
|
|
mailHeader *envelope = 0;
|
|
if ( lastHandled ) {
|
|
envelope = lastHandled->getHeader();
|
|
} else {
|
|
lastHandled = new imapCache();
|
|
}
|
|
|
|
if ( seenUid.isEmpty() ) {
|
|
// unknown what to do
|
|
kDebug( 7116 ) << "imapParser::parseFetch - UID empty";
|
|
} else {
|
|
lastHandled->setUid( seenUid.toULong() );
|
|
}
|
|
if ( envelope ) {
|
|
envelope->setPartSpecifier( seenUid );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'R':
|
|
if ( word == "RFC822.SIZE" ) {
|
|
ulong size;
|
|
parseOneNumber( inWords, size );
|
|
|
|
if ( !lastHandled ) {
|
|
lastHandled = new imapCache();
|
|
}
|
|
lastHandled->setSize( size );
|
|
} else if ( word.startsWith( "RFC822" ) ) { //krazy:exclude=strings
|
|
// might be RFC822 RFC822.TEXT RFC822.HEADER
|
|
parseLiteral( inWords, true );
|
|
}
|
|
break;
|
|
|
|
case 'I':
|
|
if ( word == "INTERNALDATE" ) {
|
|
const QByteArray date = parseOneWord( inWords );
|
|
if ( !lastHandled ) {
|
|
lastHandled = new imapCache();
|
|
}
|
|
lastHandled->setDate( date );
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if ( word == "FLAGS" ) {
|
|
//kDebug( 7116 ) << "GOT FLAGS" << inWords.cstr();
|
|
if ( !lastHandled ) {
|
|
lastHandled = new imapCache();
|
|
}
|
|
lastHandled->setFlags( imapInfo::_flags( inWords.cstr() ) );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
parseLiteral( inWords );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// see if we have more to come
|
|
while ( !inWords.isEmpty() && inWords[0] != ')' ) {
|
|
//eat the extensions to this part
|
|
if ( inWords[0] == '(' ) {
|
|
parseSentence( inWords );
|
|
} else {
|
|
parseLiteral( inWords );
|
|
}
|
|
}
|
|
|
|
if ( inWords.isEmpty() || inWords[0] != ')' ) {
|
|
return;
|
|
}
|
|
inWords.pos++;
|
|
skipWS( inWords );
|
|
}
|
|
|
|
// default parser
|
|
void imapParser::parseSentence (parseString & inWords)
|
|
{
|
|
bool first = true;
|
|
int stack = 0;
|
|
|
|
//find the first nesting parentheses
|
|
|
|
while ( !inWords.isEmpty() && ( stack != 0 || first ) ) {
|
|
first = false;
|
|
skipWS( inWords );
|
|
|
|
unsigned char ch = inWords[0];
|
|
switch ( ch ) {
|
|
case '(':
|
|
inWords.pos++;
|
|
++stack;
|
|
break;
|
|
case ')':
|
|
inWords.pos++;
|
|
--stack;
|
|
break;
|
|
case '[':
|
|
inWords.pos++;
|
|
++stack;
|
|
break;
|
|
case ']':
|
|
inWords.pos++;
|
|
--stack;
|
|
break;
|
|
default:
|
|
parseLiteral( inWords );
|
|
skipWS( inWords );
|
|
break;
|
|
}
|
|
}
|
|
skipWS( inWords );
|
|
}
|
|
|
|
void imapParser::parseRecent (ulong value, parseString & result)
|
|
{
|
|
selectInfo.setRecent( value );
|
|
result.pos = result.data.size();
|
|
}
|
|
|
|
void imapParser::parseNamespace (parseString & result)
|
|
{
|
|
if ( result[0] != '(' ) {
|
|
return;
|
|
}
|
|
|
|
QString delimEmpty;
|
|
if ( namespaceToDelimiter.contains( QString() ) ) {
|
|
delimEmpty = namespaceToDelimiter[QString()];
|
|
}
|
|
|
|
namespaceToDelimiter.clear();
|
|
imapNamespaces.clear();
|
|
|
|
// remember what section we're in (user, other users, shared)
|
|
int ns = -1;
|
|
bool personalAvailable = false;
|
|
while ( !result.isEmpty() ) {
|
|
if ( result[0] == '(' ) {
|
|
result.pos++; // tie off (
|
|
if ( result[0] == '(' ) {
|
|
// new namespace section
|
|
result.pos++; // tie off (
|
|
++ns;
|
|
}
|
|
// namespace prefix
|
|
QString prefix = QString::fromLatin1( parseOneWord( result ) );
|
|
// delimiter
|
|
QString delim = QString::fromLatin1( parseOneWord( result ) );
|
|
kDebug( 7116 ) << "imapParser::parseNamespace ns='" << prefix << "',delim='" << delim << "'";
|
|
if ( ns == 0 ) {
|
|
// at least one personal ns
|
|
personalAvailable = true;
|
|
}
|
|
QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim;
|
|
imapNamespaces.append( nsentry );
|
|
if ( prefix.right( 1 ) == delim ) {
|
|
// strip delimiter to get a correct entry for comparisons
|
|
prefix.resize( prefix.length() );
|
|
}
|
|
namespaceToDelimiter[prefix] = delim;
|
|
|
|
result.pos++; // tie off )
|
|
skipWS( result );
|
|
} else if ( result[0] == ')' ) {
|
|
result.pos++; // tie off )
|
|
skipWS( result );
|
|
} else if ( result[0] == 'N' ) {
|
|
// drop NIL
|
|
++ns;
|
|
parseOneWord( result );
|
|
} else {
|
|
// drop whatever it is
|
|
parseOneWord( result );
|
|
}
|
|
}
|
|
if ( !delimEmpty.isEmpty() ) {
|
|
// remember default delimiter
|
|
namespaceToDelimiter[QString()] = delimEmpty;
|
|
if ( !personalAvailable ) {
|
|
// at least one personal ns would be nice
|
|
kDebug( 7116 ) << "imapParser::parseNamespace - registering own personal ns";
|
|
QString nsentry = "0==" + delimEmpty;
|
|
imapNamespaces.append( nsentry );
|
|
}
|
|
}
|
|
}
|
|
|
|
int imapParser::parseLoop ()
|
|
{
|
|
parseString result;
|
|
|
|
if ( !parseReadLine( result.data ) ) {
|
|
return -1;
|
|
}
|
|
|
|
//kDebug( 7116 ) << result.cstr(); // includes \n
|
|
|
|
if ( result.data.isEmpty() ) {
|
|
return 0;
|
|
}
|
|
if ( !sentQueue.count() ) {
|
|
// maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE
|
|
kDebug( 7116 ) << "imapParser::parseLoop - unhandledResponse:" << result.cstr();
|
|
unhandled << result.cstr();
|
|
} else {
|
|
CommandPtr current = sentQueue.at( 0 );
|
|
switch ( result[0] ) {
|
|
case '*':
|
|
result.data.resize( result.data.size() - 2 ); // tie off CRLF
|
|
parseUntagged( result );
|
|
break;
|
|
case '+':
|
|
continuation = result.data;
|
|
break;
|
|
default:
|
|
{
|
|
QByteArray tag = parseLiteral( result );
|
|
if ( current->id() == tag.data() ) {
|
|
result.data.resize( result.data.size() - 2 ); // tie off CRLF
|
|
QByteArray resultCode = parseLiteral( result ); //the result
|
|
current->setResult( resultCode );
|
|
current->setResultInfo( result.cstr() );
|
|
current->setComplete();
|
|
|
|
sentQueue.removeAll( current );
|
|
completeQueue.append( current );
|
|
if ( result.length() ) {
|
|
parseResult( resultCode, result, current->command() );
|
|
}
|
|
} else {
|
|
kDebug( 7116 ) << "imapParser::parseLoop - unknown tag '" << tag << "'";
|
|
QByteArray cstr = tag + ' ' + result.cstr();
|
|
result.data = cstr;
|
|
result.pos = 0;
|
|
result.data.resize( cstr.length() );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
imapParser::parseRelay (const QByteArray & buffer)
|
|
{
|
|
Q_UNUSED( buffer );
|
|
qWarning( "imapParser::parseRelay - virtual function not reimplemented - data lost" );
|
|
}
|
|
|
|
void
|
|
imapParser::parseRelay (ulong len)
|
|
{
|
|
Q_UNUSED( len );
|
|
qWarning( "imapParser::parseRelay - virtual function not reimplemented - announcement lost" );
|
|
}
|
|
|
|
bool imapParser::parseRead (QByteArray & buffer, long len, long relay)
|
|
{
|
|
Q_UNUSED( buffer );
|
|
Q_UNUSED( len );
|
|
Q_UNUSED( relay );
|
|
qWarning( "imapParser::parseRead - virtual function not reimplemented - no data read" );
|
|
return false;
|
|
}
|
|
|
|
bool imapParser::parseReadLine (QByteArray & buffer, long relay)
|
|
{
|
|
Q_UNUSED( buffer );
|
|
Q_UNUSED( relay );
|
|
qWarning( "imapParser::parseReadLine - virtual function not reimplemented - no data read" );
|
|
return false;
|
|
}
|
|
|
|
void
|
|
imapParser::parseWriteLine (const QString & str)
|
|
{
|
|
Q_UNUSED( str );
|
|
qWarning( "imapParser::parseWriteLine - virtual function not reimplemented - no data written" );
|
|
}
|
|
|
|
void
|
|
imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section,
|
|
QString & _type, QString & _uid, QString & _validity, QString & _info)
|
|
{
|
|
QStringList parameters;
|
|
|
|
_box = _url.path();
|
|
kDebug( 7116 ) << "imapParser::parseURL" << _box;
|
|
int paramStart = _box.indexOf( "/;" );
|
|
if ( paramStart > -1 ) {
|
|
QString paramString = _box.right( _box.length() - paramStart - 2 );
|
|
parameters = paramString.split( ';', QString::SkipEmptyParts ); //split parameters
|
|
_box.truncate( paramStart ); // strip parameters
|
|
}
|
|
// extract parameters
|
|
for ( QStringList::ConstIterator it( parameters.constBegin() );
|
|
it != parameters.constEnd(); ++it ) {
|
|
QString temp = ( *it );
|
|
|
|
// if we have a '/' separator we'll just nuke it
|
|
int pt = temp.indexOf( '/' );
|
|
if ( pt > 0 ) {
|
|
temp.truncate( pt );
|
|
}
|
|
if ( temp.startsWith( QLatin1String( "section=" ), Qt::CaseInsensitive ) ) {
|
|
_section = temp.right( temp.length() - 8 );
|
|
} else if ( temp.startsWith( QLatin1String( "type=" ), Qt::CaseInsensitive ) ) {
|
|
_type = temp.right( temp.length() - 5 );
|
|
} else if ( temp.startsWith( QLatin1String( "uid=" ), Qt::CaseInsensitive ) ) {
|
|
_uid = temp.right( temp.length() - 4 );
|
|
} else if ( temp.startsWith( QLatin1String( "uidvalidity=" ), Qt::CaseInsensitive ) ) {
|
|
_validity = temp.right( temp.length() - 12 );
|
|
} else if ( temp.startsWith( QLatin1String( "info=" ), Qt::CaseInsensitive ) ) {
|
|
_info = temp.right( temp.length() - 5 );
|
|
}
|
|
}
|
|
// kDebug( 7116 ) << "URL: section=" << _section << ", type=" << _type << ", uid=" << _uid;
|
|
// kDebug( 7116 ) << "URL: user()" << _url.user();
|
|
// kDebug( 7116 ) << "URL: path()" << _url.path();
|
|
// kDebug( 7116 ) << "URL: encodedPathAndQuery()" << _url.encodedPathAndQuery();
|
|
|
|
if ( !_box.isEmpty() ) {
|
|
// strip /
|
|
if ( _box[0] == '/' ) {
|
|
_box = _box.right( _box.length() - 1 );
|
|
}
|
|
if ( !_box.isEmpty() && _box[_box.length() - 1] == '/' ) {
|
|
_box.truncate( _box.length() - 1 );
|
|
}
|
|
}
|
|
kDebug( 7116 ) << "URL: box=" << _box << ", section=" << _section << ", type="
|
|
<< _type << ", uid=" << _uid << ", validity=" << _validity
|
|
<< ", info=" << _info;
|
|
}
|
|
|
|
|
|
QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) {
|
|
|
|
if ( !inWords.isEmpty() && inWords[0] == '{' ) {
|
|
QByteArray retVal;
|
|
int runLen = inWords.find('}', 1);
|
|
if ( runLen > 0 ) {
|
|
bool proper;
|
|
long runLenSave = runLen + 1;
|
|
QByteArray tmpstr( runLen, '\0' );
|
|
inWords.takeMidNoResize( tmpstr, 1, runLen - 1 );
|
|
runLen = tmpstr.toULong( &proper );
|
|
inWords.pos += runLenSave;
|
|
if ( proper ) {
|
|
//now get the literal from the server
|
|
if ( relay ) {
|
|
parseRelay( runLen );
|
|
}
|
|
QByteArray rv;
|
|
parseRead( rv, runLen, relay ? runLen : 0 );
|
|
rv.resize( qMax( runLen, rv.size() ) ); // what's the point?
|
|
retVal = rv;
|
|
inWords.clear();
|
|
parseReadLine( inWords.data ); // must get more
|
|
|
|
// no duplicate data transfers
|
|
relay = false;
|
|
} else {
|
|
kDebug( 7116 ) << "imapParser::parseLiteral - error parsing {} -" /*<< strLen*/;
|
|
}
|
|
} else {
|
|
inWords.clear();
|
|
kDebug( 7116 ) << "imapParser::parseLiteral - error parsing unmatched {";
|
|
}
|
|
skipWS( inWords );
|
|
return retVal;
|
|
}
|
|
return parseOneWord( inWords, stopAtBracket );
|
|
}
|
|
|
|
// does not know about literals ( {7} literal )
|
|
QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket)
|
|
{
|
|
uint len = inWords.length();
|
|
if ( len == 0 ) {
|
|
return QByteArray();
|
|
}
|
|
|
|
if ( len > 0 && inWords[0] == '"' ) {
|
|
unsigned int i = 1;
|
|
bool quote = false;
|
|
while ( i < len && ( inWords[i] != '"' || quote ) ) {
|
|
if ( inWords[i] == '\\' ) {
|
|
quote = !quote;
|
|
} else {
|
|
quote = false;
|
|
}
|
|
i++;
|
|
}
|
|
if ( i < len ) {
|
|
QByteArray retVal;
|
|
retVal.resize( i );
|
|
inWords.pos++;
|
|
inWords.takeLeftNoResize( retVal, i - 1 );
|
|
len = i - 1;
|
|
int offset = 0;
|
|
for ( unsigned int j = 0; j < len; j++ ) {
|
|
if ( retVal[j] == '\\' ) {
|
|
offset++;
|
|
j++;
|
|
}
|
|
retVal[j - offset] = retVal[j];
|
|
}
|
|
retVal.resize( len - offset );
|
|
inWords.pos += i;
|
|
skipWS( inWords );
|
|
return retVal;
|
|
} else {
|
|
kDebug( 7116 ) << "imapParser::parseOneWord - error parsing unmatched \"";
|
|
QByteArray retVal = inWords.cstr();
|
|
inWords.clear();
|
|
return retVal;
|
|
}
|
|
} else {
|
|
// not quoted
|
|
unsigned int i;
|
|
// search for end
|
|
for ( i = 0; i < len; ++i ) {
|
|
char ch = inWords[i];
|
|
if ( ch <= ' ' || ch == '(' || ch == ')' ||
|
|
( stopAtBracket && ( ch == '[' || ch == ']' ) ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
QByteArray retVal;
|
|
retVal.resize( i );
|
|
inWords.takeLeftNoResize( retVal, i );
|
|
inWords.pos += i;
|
|
|
|
if ( retVal == "NIL" ) {
|
|
retVal.truncate( 0 );
|
|
}
|
|
skipWS( inWords );
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
|
|
{
|
|
bool valid;
|
|
num = parseOneWord( inWords, true ).toULong( &valid );
|
|
return valid;
|
|
}
|
|
|
|
bool imapParser::hasCapability (const QString & cap)
|
|
{
|
|
QString c = cap.toLower();
|
|
// kDebug( 7116 ) << "imapParser::hasCapability - Looking for '" << cap << "'";
|
|
for ( QStringList::ConstIterator it = imapCapabilities.constBegin();
|
|
it != imapCapabilities.constEnd(); ++it ) {
|
|
// kDebug( 7116 ) << "imapParser::hasCapability - Examining '" << ( *it ) << "'";
|
|
if ( !( kasciistricmp( c.toLatin1(), ( *it ).toAscii() ) ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void imapParser::removeCapability (const QString & cap)
|
|
{
|
|
imapCapabilities.removeAll( cap.toLower() );
|
|
}
|
|
|
|
QString imapParser::namespaceForBox( const QString & box )
|
|
{
|
|
kDebug( 7116 ) << "imapParse::namespaceForBox" << box;
|
|
QString myNamespace;
|
|
if ( !box.isEmpty() ) {
|
|
const QList<QString> list = namespaceToDelimiter.keys();
|
|
QString cleanPrefix;
|
|
for ( QList<QString>::ConstIterator it = list.begin(); it != list.end(); ++it ) {
|
|
if ( !( *it ).isEmpty() && box.contains( *it ) ) {
|
|
return ( *it );
|
|
}
|
|
}
|
|
}
|
|
return myNamespace;
|
|
}
|