You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2090 lines
52 KiB
2090 lines
52 KiB
/**********************************************************************
|
|
*
|
|
* imapparser.cc - IMAP4rev1 Parser
|
|
* Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
|
|
* Copyright (C) 2000 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
|
|
*
|
|
*********************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "rfcdecoder.h"
|
|
|
|
#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>
|
|
|
|
#ifdef HAVE_LIBSASL2
|
|
extern "C" {
|
|
#include <sasl/sasl.h>
|
|
}
|
|
#endif
|
|
|
|
#include <tqregexp.h>
|
|
#include <tqbuffer.h>
|
|
#include <tqstring.h>
|
|
#include <tqstringlist.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <kmdcodec.h>
|
|
#include <kurl.h>
|
|
|
|
#include <kasciistricmp.h>
|
|
#include <kasciistringtools.h>
|
|
|
|
#ifdef HAVE_LIBSASL2
|
|
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 }
|
|
};
|
|
#endif
|
|
|
|
imapParser::imapParser ()
|
|
{
|
|
sentQueue.setAutoDelete (false);
|
|
completeQueue.setAutoDelete (true);
|
|
currentState = ISTATE_NO;
|
|
commandCounter = 0;
|
|
lastHandled = 0;
|
|
}
|
|
|
|
imapParser::~imapParser ()
|
|
{
|
|
delete lastHandled;
|
|
lastHandled = 0;
|
|
}
|
|
|
|
imapCommand *
|
|
imapParser::doCommand (imapCommand * aCmd)
|
|
{
|
|
int pl = 0;
|
|
sendCommand (aCmd);
|
|
while (pl != -1 && !aCmd->isComplete ()) {
|
|
while ((pl = parseLoop ()) == 0)
|
|
;
|
|
}
|
|
|
|
return aCmd;
|
|
}
|
|
|
|
imapCommand *
|
|
imapParser::sendCommand (imapCommand * aCmd)
|
|
{
|
|
aCmd->setId (TQString::number(commandCounter++));
|
|
sentQueue.append (aCmd);
|
|
|
|
continuation.resize(0);
|
|
const TQString& command = aCmd->command();
|
|
|
|
if (command == "SELECT" || command == "EXAMINE")
|
|
{
|
|
// we need to know which box we are selecting
|
|
parseString p;
|
|
p.fromString(aCmd->parameter());
|
|
currentBox = parseOneWordC(p);
|
|
kdDebug(7116) << "imapParser::sendCommand - setting current box to " << currentBox << endl;
|
|
}
|
|
else if (command == "CLOSE")
|
|
{
|
|
// we no longer have a box open
|
|
currentBox = TQString();
|
|
}
|
|
else if (command.find ("SEARCH") != -1
|
|
|| 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 TQString & aUser, const TQString & aPass,
|
|
TQString & resultInfo)
|
|
{
|
|
imapCommand *cmd;
|
|
bool retVal = false;
|
|
|
|
cmd =
|
|
doCommand (new
|
|
imapCommand ("LOGIN", "\"" + rfcDecoder::quoteIMAP(aUser)
|
|
+ "\" \"" + rfcDecoder::quoteIMAP(aPass) + "\""));
|
|
|
|
if (cmd->result () == "OK")
|
|
{
|
|
currentState = ISTATE_LOGIN;
|
|
retVal = true;
|
|
}
|
|
resultInfo = cmd->resultInfo();
|
|
completeQueue.removeRef (cmd);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
#ifdef HAVE_LIBSASL2
|
|
static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in )
|
|
{
|
|
kdDebug(7116) << "sasl_interact" << endl;
|
|
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->openPassDlg(ai))
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
interact = ( sasl_interact_t * ) in;
|
|
while( interact->id != SASL_CB_LIST_END ) {
|
|
kdDebug(7116) << "SASL_INTERACT id: " << interact->id << endl;
|
|
switch( interact->id ) {
|
|
case SASL_CB_USER:
|
|
case SASL_CB_AUTHNAME:
|
|
kdDebug(7116) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'" << endl;
|
|
interact->result = strdup( ai.username.utf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
case SASL_CB_PASS:
|
|
kdDebug(7116) << "SASL_CB_PASS: [hidden] " << endl;
|
|
interact->result = strdup( ai.password.utf8() );
|
|
interact->len = strlen( (const char *) interact->result );
|
|
break;
|
|
default:
|
|
interact->result = 0;
|
|
interact->len = 0;
|
|
break;
|
|
}
|
|
interact++;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai,
|
|
const TQString & aFTQDN, const TQString & aAuth, bool isSSL, TQString & resultInfo)
|
|
{
|
|
bool retVal = false;
|
|
#ifdef HAVE_LIBSASL2
|
|
int result;
|
|
sasl_conn_t *conn = 0;
|
|
sasl_interact_t *client_interact = 0;
|
|
const char *out = 0;
|
|
uint outlen = 0;
|
|
const char *mechusing = 0;
|
|
TQByteArray tmp, challenge;
|
|
|
|
kdDebug(7116) << "aAuth: " << aAuth << " FTQDN: " << aFTQDN << " isSSL: " << isSSL << endl;
|
|
|
|
// 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. */
|
|
aFTQDN.latin1(),
|
|
0, 0, callbacks, 0, &conn );
|
|
|
|
if ( result != SASL_OK ) {
|
|
kdDebug(7116) << "sasl_client_new failed with: " << result << endl;
|
|
resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) );
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
result = sasl_client_start(conn, aAuth.latin1(), &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 ) {
|
|
kdDebug(7116) << "sasl_client_start failed with: " << result << endl;
|
|
resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) );
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
imapCommand *cmd;
|
|
|
|
tmp.setRawData( out, outlen );
|
|
KCodecs::base64Encode( tmp, challenge );
|
|
tmp.resetRawData( out, outlen );
|
|
// then lets try it
|
|
TQString firstCommand = aAuth;
|
|
if ( !challenge.isEmpty() ) {
|
|
firstCommand += " ";
|
|
firstCommand += TQString::fromLatin1( challenge.data(), challenge.size() );
|
|
}
|
|
cmd = sendCommand (new imapCommand ("AUTHENTICATE", firstCommand.latin1()));
|
|
|
|
int pl = 0;
|
|
while ( pl != -1 && !cmd->isComplete () )
|
|
{
|
|
//read the next line
|
|
while ((pl = parseLoop()) == 0) ;
|
|
|
|
if (!continuation.isEmpty())
|
|
{
|
|
// kdDebug(7116) << "S: " << TQCString(continuation.data(),continuation.size()+1) << endl;
|
|
if ( continuation.size() > 4 ) {
|
|
tmp.setRawData( continuation.data() + 2, continuation.size() - 4 );
|
|
KCodecs::base64Decode( tmp, challenge );
|
|
// kdDebug(7116) << "S-1: " << TQCString(challenge.data(),challenge.size()+1) << endl;
|
|
tmp.resetRawData( continuation.data() + 2, continuation.size() - 4 );
|
|
}
|
|
|
|
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 ) {
|
|
kdDebug(7116) << "sasl_client_step failed with: " << result << endl;
|
|
resultInfo = TQString::fromUtf8( sasl_errdetail( conn ) );
|
|
sasl_dispose( &conn );
|
|
return false;
|
|
}
|
|
|
|
tmp.setRawData( out, outlen );
|
|
// kdDebug(7116) << "C-1: " << TQCString(tmp.data(),tmp.size()+1) << endl;
|
|
KCodecs::base64Encode( tmp, challenge );
|
|
tmp.resetRawData( out, outlen );
|
|
// kdDebug(7116) << "C: " << TQCString(challenge.data(),challenge.size()+1) << endl;
|
|
parseWriteLine (challenge);
|
|
continuation.resize(0);
|
|
}
|
|
}
|
|
|
|
if (cmd->result () == "OK")
|
|
{
|
|
currentState = ISTATE_LOGIN;
|
|
retVal = true;
|
|
}
|
|
resultInfo = cmd->resultInfo();
|
|
completeQueue.removeRef (cmd);
|
|
|
|
sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection.
|
|
#endif //HAVE_LIBSASL2
|
|
return retVal;
|
|
}
|
|
|
|
void
|
|
imapParser::parseUntagged (parseString & result)
|
|
{
|
|
//kdDebug(7116) << "imapParser::parseUntagged - '" << result.cstr() << "'" << endl;
|
|
|
|
parseOneWordC(result); // *
|
|
TQByteArray what = parseLiteral (result); // see whats coming next
|
|
|
|
switch (what[0])
|
|
{
|
|
//the status responses
|
|
case 'B': // BAD or BYE
|
|
if (tqstrncmp(what, "BAD", what.size()) == 0)
|
|
{
|
|
parseResult (what, result);
|
|
}
|
|
else if (tqstrncmp(what, "BYE", what.size()) == 0)
|
|
{
|
|
parseResult (what, result);
|
|
if ( sentQueue.count() ) {
|
|
// BYE that interrupts a command -> copy the reason for it
|
|
imapCommand *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 (tqstrncmp(what, "NAMESPACE", what.size()) == 0)
|
|
{
|
|
parseNamespace (result);
|
|
}
|
|
break;
|
|
|
|
case 'O': // OK
|
|
if (what[1] == 'K' && what.size() == 2)
|
|
{
|
|
parseResult (what, result);
|
|
} else if (tqstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER
|
|
parseOtherUser (result);
|
|
} else if (tqstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE
|
|
parseOutOfOffice (result);
|
|
}
|
|
break;
|
|
case 'D':
|
|
if (tqstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES
|
|
parseDelegate (result);
|
|
}
|
|
break;
|
|
|
|
case 'P': // PREAUTH
|
|
if (tqstrncmp(what, "PREAUTH", what.size()) == 0)
|
|
{
|
|
parseResult (what, result);
|
|
currentState = ISTATE_LOGIN;
|
|
}
|
|
break;
|
|
|
|
// parse the other responses
|
|
case 'C': // CAPABILITY
|
|
if (tqstrncmp(what, "CAPABILITY", what.size()) == 0)
|
|
{
|
|
parseCapability (result);
|
|
}
|
|
break;
|
|
|
|
case 'F': // FLAGS
|
|
if (tqstrncmp(what, "FLAGS", what.size()) == 0)
|
|
{
|
|
parseFlags (result);
|
|
}
|
|
break;
|
|
|
|
case 'L': // LIST or LSUB or LISTRIGHTS
|
|
if (tqstrncmp(what, "LIST", what.size()) == 0)
|
|
{
|
|
parseList (result);
|
|
}
|
|
else if (tqstrncmp(what, "LSUB", what.size()) == 0)
|
|
{
|
|
parseLsub (result);
|
|
}
|
|
else if (tqstrncmp(what, "LISTRIGHTS", what.size()) == 0)
|
|
{
|
|
parseListRights (result);
|
|
}
|
|
break;
|
|
|
|
case 'M': // MYRIGHTS
|
|
if (tqstrncmp(what, "MYRIGHTS", what.size()) == 0)
|
|
{
|
|
parseMyRights (result);
|
|
}
|
|
break;
|
|
case 'S': // SEARCH or STATUS
|
|
if (tqstrncmp(what, "SEARCH", what.size()) == 0)
|
|
{
|
|
parseSearch (result);
|
|
}
|
|
else if (tqstrncmp(what, "STATUS", what.size()) == 0)
|
|
{
|
|
parsetStatus (result);
|
|
}
|
|
break;
|
|
|
|
case 'A': // ACL or ANNOTATION
|
|
if (tqstrncmp(what, "ACL", what.size()) == 0)
|
|
{
|
|
parseAcl (result);
|
|
}
|
|
else if (tqstrncmp(what, "ANNOTATION", what.size()) == 0)
|
|
{
|
|
parseAnnotation (result);
|
|
}
|
|
break;
|
|
case 'Q': // QUOTA or QUOTAROOT
|
|
if ( what.size() > 5 && tqstrncmp(what, "QUOTAROOT", what.size()) == 0)
|
|
{
|
|
parseQuotaRoot( result );
|
|
}
|
|
else if (tqstrncmp(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 = TQCString(what, what.size() + 1).toUInt(&valid);
|
|
if (valid)
|
|
{
|
|
what = parseLiteral (result);
|
|
switch (what[0])
|
|
{
|
|
case 'E':
|
|
if (tqstrncmp(what, "EXISTS", what.size()) == 0)
|
|
{
|
|
parseExists (number, result);
|
|
}
|
|
else if (tqstrncmp(what, "EXPUNGE", what.size()) == 0)
|
|
{
|
|
parseExpunge (number, result);
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if (tqstrncmp(what, "FETCH", what.size()) == 0)
|
|
{
|
|
seenUid = TQString();
|
|
parseFetch (number, result);
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
if (tqstrncmp(what, "STORE", what.size()) == 0) // deprecated store
|
|
{
|
|
seenUid = TQString();
|
|
parseFetch (number, result);
|
|
}
|
|
break;
|
|
|
|
case 'R':
|
|
if (tqstrncmp(what, "RECENT", what.size()) == 0)
|
|
{
|
|
parseRecent (number, result);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
} //switch
|
|
} //func
|
|
|
|
|
|
void
|
|
imapParser::parseResult (TQByteArray & result, parseString & rest,
|
|
const TQString & command)
|
|
{
|
|
if (command == "SELECT")
|
|
selectInfo.setReadWrite(true);
|
|
|
|
if (rest[0] == '[')
|
|
{
|
|
rest.pos++;
|
|
TQCString option = parseOneWordC(rest, TRUE);
|
|
|
|
switch (option[0])
|
|
{
|
|
case 'A': // ALERT
|
|
if (option == "ALERT")
|
|
{
|
|
rest.pos = rest.data.find(']', 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.find(']', rest.pos);
|
|
TQCString 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);
|
|
}
|
|
else
|
|
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].latin1 ())
|
|
{
|
|
case 'A':
|
|
if (command == "AUTHENTICATE")
|
|
if (tqstrncmp(result, "OK", result.size()) == 0)
|
|
currentState = ISTATE_LOGIN;
|
|
break;
|
|
|
|
case 'L':
|
|
if (command == "LOGIN")
|
|
if (tqstrncmp(result, "OK", result.size()) == 0)
|
|
currentState = ISTATE_LOGIN;
|
|
break;
|
|
|
|
case 'E':
|
|
if (command == "EXAMINE")
|
|
{
|
|
if (tqstrncmp(result, "OK", result.size()) == 0)
|
|
currentState = ISTATE_SELECT;
|
|
else
|
|
{
|
|
if (currentState == ISTATE_SELECT)
|
|
currentState = ISTATE_LOGIN;
|
|
currentBox = TQString();
|
|
}
|
|
kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl;
|
|
}
|
|
break;
|
|
|
|
case 'S':
|
|
if (command == "SELECT")
|
|
{
|
|
if (tqstrncmp(result, "OK", result.size()) == 0)
|
|
currentState = ISTATE_SELECT;
|
|
else
|
|
{
|
|
if (currentState == ISTATE_SELECT)
|
|
currentState = ISTATE_LOGIN;
|
|
currentBox = TQString();
|
|
}
|
|
kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
void imapParser::parseCapability (parseString & result)
|
|
{
|
|
TQCString temp( result.cstr() );
|
|
imapCapabilities = TQStringList::split ( ' ', KPIM::kAsciiToLower( temp.data() ) );
|
|
}
|
|
|
|
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(parseLiteralC(result));
|
|
this_one.setName (rfcDecoder::fromIMAP(parseLiteralC(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)
|
|
{
|
|
parseOneWordC (result); // skip mailbox name
|
|
parseOneWordC (result); // skip user id
|
|
int outlen = 1;
|
|
while ( outlen ) {
|
|
TQCString word = parseOneWordC (result, false, &outlen);
|
|
lastResults.append (word);
|
|
}
|
|
}
|
|
|
|
void imapParser::parseAcl (parseString & result)
|
|
{
|
|
parseOneWordC (result); // skip mailbox name
|
|
int outlen = 1;
|
|
// The result is user1 perm1 user2 perm2 etc. The caller will sort it out.
|
|
while ( outlen && !result.isEmpty() ) {
|
|
TQCString word = parseLiteralC (result, false, false, &outlen);
|
|
lastResults.append (word);
|
|
}
|
|
}
|
|
|
|
void imapParser::parseAnnotation (parseString & result)
|
|
{
|
|
parseOneWordC (result); // skip mailbox name
|
|
skipWS (result);
|
|
parseOneWordC (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);
|
|
int outlen = 1;
|
|
// The result is name1 value1 name2 value2 etc. The caller will sort it out.
|
|
while ( outlen && !result.isEmpty() && result[0] != ')' ) {
|
|
TQCString word = parseLiteralC (result, false, false, &outlen);
|
|
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
|
|
TQCString root = parseOneWordC( result );
|
|
if ( root.isEmpty() ) {
|
|
lastResults.append( "" );
|
|
} else {
|
|
lastResults.append( root );
|
|
}
|
|
if (result.isEmpty() || result[0] != '(')
|
|
return;
|
|
result.pos++;
|
|
skipWS (result);
|
|
TQStringList triplet;
|
|
int outlen = 1;
|
|
while ( outlen && !result.isEmpty() && result[0] != ')' ) {
|
|
TQCString word = parseLiteralC (result, false, false, &outlen);
|
|
triplet.append(word);
|
|
}
|
|
lastResults.append( triplet.join(" ") );
|
|
}
|
|
|
|
void imapParser::parseQuotaRoot (parseString & result)
|
|
{
|
|
// quotaroot_response
|
|
// ::= "QUOTAROOT" SP astring *(SP astring)
|
|
parseOneWordC (result); // skip mailbox name
|
|
skipWS (result);
|
|
if ( result.isEmpty() )
|
|
return;
|
|
TQStringList roots;
|
|
int outlen = 1;
|
|
while ( outlen && !result.isEmpty() ) {
|
|
TQCString word = parseLiteralC (result, false, false, &outlen);
|
|
roots.append (word);
|
|
}
|
|
lastResults.append( roots.isEmpty()? "" : roots.join(" ") );
|
|
}
|
|
|
|
void imapParser::parseCustom (parseString & result)
|
|
{
|
|
int outlen = 1;
|
|
TQCString word = parseLiteralC (result, false, false, &outlen);
|
|
lastResults.append( word );
|
|
}
|
|
|
|
void imapParser::parseOtherUser (parseString & result)
|
|
{
|
|
lastResults.append( parseOneWordC( result ) );
|
|
}
|
|
|
|
void imapParser::parseDelegate (parseString & result)
|
|
{
|
|
const TQString email = parseOneWordC( result );
|
|
|
|
TQStringList rights;
|
|
int outlen = 1;
|
|
while ( outlen && !result.isEmpty() ) {
|
|
TQCString word = parseLiteralC( result, false, false, &outlen );
|
|
rights.append( word );
|
|
}
|
|
|
|
lastResults.append( email + ":" + rights.join( "," ) );
|
|
}
|
|
|
|
void imapParser::parseOutOfOffice (parseString & result)
|
|
{
|
|
const TQString state = parseOneWordC (result);
|
|
parseOneWordC (result); // skip encoding
|
|
|
|
int outlen = 1;
|
|
TQCString msg = parseLiteralC (result, false, false, &outlen);
|
|
|
|
lastResults.append( state + "^" + TQString::fromUtf8( msg ) );
|
|
}
|
|
|
|
void imapParser::parseMyRights (parseString & result)
|
|
{
|
|
parseOneWordC (result); // skip mailbox name
|
|
Q_ASSERT( lastResults.isEmpty() ); // we can only be called once
|
|
lastResults.append (parseOneWordC (result) );
|
|
}
|
|
|
|
void imapParser::parseSearch (parseString & result)
|
|
{
|
|
ulong value;
|
|
|
|
while (parseOneNumber (result, value))
|
|
{
|
|
lastResults.append (TQString::number(value));
|
|
}
|
|
}
|
|
|
|
void imapParser::parsetStatus (parseString & inWords)
|
|
{
|
|
lasStatus = imapInfo ();
|
|
|
|
parseLiteralC(inWords); // swallow the box
|
|
if (inWords.isEmpty() || inWords[0] != '(')
|
|
return;
|
|
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
while (!inWords.isEmpty() && inWords[0] != ')')
|
|
{
|
|
ulong value;
|
|
|
|
TQCString label = parseOneWordC(inWords);
|
|
if (parseOneNumber (inWords, value))
|
|
{
|
|
if (label == "MESSAGES")
|
|
lasStatus.setCount (value);
|
|
else if (label == "RECENT")
|
|
lasStatus.setRecent (value);
|
|
else if (label == "UIDVALIDITY")
|
|
lasStatus.setUidValidity (value);
|
|
else if (label == "UNSEEN")
|
|
lasStatus.setUnseen (value);
|
|
else if (label == "UIDNEXT")
|
|
lasStatus.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, TQPtrList<mailAddress>& list)
|
|
{
|
|
if (inWords.isEmpty())
|
|
return;
|
|
if (inWords[0] != '(')
|
|
{
|
|
parseOneWordC (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(parseLiteralC(inWords));
|
|
retVal.setCommentRaw(parseLiteralC(inWords));
|
|
retVal.setUser(parseLiteralC(inWords));
|
|
retVal.setHost(parseLiteralC(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(parseLiteralC(inWords));
|
|
|
|
//subject
|
|
envelope->setSubject(parseLiteralC(inWords));
|
|
|
|
TQPtrList<mailAddress> list;
|
|
list.setAutoDelete(true);
|
|
|
|
//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(parseLiteralC(inWords));
|
|
|
|
//message-id
|
|
envelope->setMessageId(parseLiteralC(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
|
|
parseLiteralC (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
|
|
TQAsciiDict < TQString > imapParser::parseDisposition (parseString & inWords)
|
|
{
|
|
TQCString disposition;
|
|
TQAsciiDict < TQString > retVal (17, false);
|
|
|
|
// return value is a shallow copy
|
|
retVal.setAutoDelete (false);
|
|
|
|
if (inWords[0] != '(')
|
|
{
|
|
//disposition only
|
|
disposition = parseOneWordC (inWords);
|
|
}
|
|
else
|
|
{
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
//disposition
|
|
disposition = parseOneWordC (inWords);
|
|
retVal = parseParameters (inWords);
|
|
if (inWords[0] != ')')
|
|
return retVal;
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
}
|
|
|
|
if (!disposition.isEmpty ())
|
|
{
|
|
retVal.insert ("content-disposition", new TQString(disposition));
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
// parse parameter pairs into a dictionary
|
|
// caller must clean up the dictionary items
|
|
TQAsciiDict < TQString > imapParser::parseParameters (parseString & inWords)
|
|
{
|
|
TQAsciiDict < TQString > retVal (17, false);
|
|
|
|
// return value is a shallow copy
|
|
retVal.setAutoDelete (false);
|
|
|
|
if (inWords[0] != '(')
|
|
{
|
|
//better be NIL
|
|
parseOneWordC (inWords);
|
|
}
|
|
else
|
|
{
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
while (!inWords.isEmpty () && inWords[0] != ')')
|
|
{
|
|
TQCString l1 = parseLiteralC(inWords);
|
|
TQCString l2 = parseLiteralC(inWords);
|
|
retVal.insert (l1, new TQString(l2));
|
|
}
|
|
|
|
if (inWords[0] != ')')
|
|
return retVal;
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
mimeHeader * imapParser::parseSimplePart (parseString & inWords,
|
|
TQString & inSection, mimeHeader * localPart)
|
|
{
|
|
TQCString subtype;
|
|
TQCString typeStr;
|
|
TQAsciiDict < TQString > parameters (17, false);
|
|
ulong size;
|
|
|
|
parameters.setAutoDelete (true);
|
|
|
|
if (inWords[0] != '(')
|
|
return 0;
|
|
|
|
if (!localPart)
|
|
localPart = new mimeHeader;
|
|
|
|
localPart->setPartSpecifier (inSection);
|
|
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
//body type
|
|
typeStr = parseLiteralC(inWords);
|
|
|
|
//body subtype
|
|
subtype = parseLiteralC(inWords);
|
|
|
|
localPart->setType (typeStr + "/" + subtype);
|
|
|
|
//body parameter parenthesized list
|
|
parameters = parseParameters (inWords);
|
|
{
|
|
TQAsciiDictIterator < TQString > it (parameters);
|
|
|
|
while (it.current ())
|
|
{
|
|
localPart->setTypeParm (it.currentKey (), *(it.current ()));
|
|
++it;
|
|
}
|
|
parameters.clear ();
|
|
}
|
|
|
|
//body id
|
|
localPart->setID (parseLiteralC(inWords));
|
|
|
|
//body description
|
|
localPart->setDescription (parseLiteralC(inWords));
|
|
|
|
//body encoding
|
|
localPart->setEncoding (parseLiteralC(inWords));
|
|
|
|
//body size
|
|
if (parseOneNumber (inWords, size))
|
|
localPart->setLength (size);
|
|
|
|
// type specific extensions
|
|
if (localPart->getType().upper() == "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
|
|
parseLiteralC(inWords);
|
|
|
|
// body disposition
|
|
parameters = parseDisposition (inWords);
|
|
{
|
|
TQString *disposition = parameters["content-disposition"];
|
|
|
|
if (disposition)
|
|
localPart->setDisposition (disposition->ascii ());
|
|
parameters.remove ("content-disposition");
|
|
TQAsciiDictIterator < TQString > it (parameters);
|
|
while (it.current ())
|
|
{
|
|
localPart->setDispositionParm (it.currentKey (),
|
|
*(it.current ()));
|
|
++it;
|
|
}
|
|
|
|
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
|
|
parseLiteralC(inWords);
|
|
}
|
|
if (inWords[0] == ')')
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
return localPart;
|
|
}
|
|
|
|
mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
|
|
TQString & 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 ""
|
|
parseOneWordC (inWords);
|
|
return 0;
|
|
}
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
if (inWords[0] == '(')
|
|
{
|
|
TQByteArray subtype;
|
|
TQAsciiDict < TQString > parameters (17, false);
|
|
TQString outSection;
|
|
parameters.setAutoDelete (true);
|
|
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 its a simplepart and handled later)
|
|
while (inWords[0] == '(')
|
|
{
|
|
outSection = TQString::number(++section);
|
|
if (!init)
|
|
outSection = inSection + "." + outSection;
|
|
mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0);
|
|
localPart->addNestedPart (subpart);
|
|
}
|
|
|
|
// fetch subtype
|
|
subtype = parseOneWordC (inWords);
|
|
|
|
localPart->setType ("MULTIPART/" + b2c(subtype));
|
|
|
|
// fetch parameters
|
|
parameters = parseParameters (inWords);
|
|
{
|
|
TQAsciiDictIterator < TQString > it (parameters);
|
|
|
|
while (it.current ())
|
|
{
|
|
localPart->setTypeParm (it.currentKey (), *(it.current ()));
|
|
++it;
|
|
}
|
|
parameters.clear ();
|
|
}
|
|
|
|
// body disposition
|
|
parameters = parseDisposition (inWords);
|
|
{
|
|
TQString *disposition = parameters["content-disposition"];
|
|
|
|
if (disposition)
|
|
localPart->setDisposition (disposition->ascii ());
|
|
parameters.remove ("content-disposition");
|
|
TQAsciiDictIterator < TQString > it (parameters);
|
|
while (it.current ())
|
|
{
|
|
localPart->setDispositionParm (it.currentKey (),
|
|
*(it.current ()));
|
|
++it;
|
|
}
|
|
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
|
|
parseLiteralC(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] == '[')
|
|
{
|
|
TQCString specifier;
|
|
TQCString label;
|
|
inWords.pos++;
|
|
|
|
specifier = parseOneWordC (inWords, TRUE);
|
|
|
|
if (inWords[0] == '(')
|
|
{
|
|
inWords.pos++;
|
|
|
|
while (!inWords.isEmpty () && inWords[0] != ')')
|
|
{
|
|
label = parseOneWordC (inWords);
|
|
}
|
|
|
|
if (!inWords.isEmpty () && inWords[0] == ')')
|
|
inWords.pos++;
|
|
}
|
|
if (!inWords.isEmpty () && inWords[0] == ']')
|
|
inWords.pos++;
|
|
skipWS (inWords);
|
|
|
|
// parse the header
|
|
if (specifier == "0")
|
|
{
|
|
mailHeader *envelope = 0;
|
|
if (lastHandled)
|
|
envelope = lastHandled->getHeader ();
|
|
|
|
if (!envelope || seenUid.isEmpty ())
|
|
{
|
|
kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
|
|
// don't know where to put it, throw it away
|
|
parseLiteralC(inWords, true);
|
|
}
|
|
else
|
|
{
|
|
kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl;
|
|
// fill it up with data
|
|
TQString theHeader = parseLiteralC(inWords, true);
|
|
mimeIOTQString myIO;
|
|
|
|
myIO.setString (theHeader);
|
|
envelope->parseHeader (myIO);
|
|
|
|
}
|
|
}
|
|
else if (specifier == "HEADER.FIELDS")
|
|
{
|
|
// BODY[HEADER.FIELDS (References)] {n}
|
|
//kdDebug(7116) << "imapParser::parseBody - HEADER.FIELDS: "
|
|
// << TQCString(label.data(), label.size()+1) << endl;
|
|
if (label == "REFERENCES")
|
|
{
|
|
mailHeader *envelope = 0;
|
|
if (lastHandled)
|
|
envelope = lastHandled->getHeader ();
|
|
|
|
if (!envelope || seenUid.isEmpty ())
|
|
{
|
|
kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
|
|
// don't know where to put it, throw it away
|
|
parseLiteralC (inWords, true);
|
|
}
|
|
else
|
|
{
|
|
TQCString references = parseLiteralC(inWords, true);
|
|
int start = references.find ('<');
|
|
int end = references.findRev ('>');
|
|
if (start < end)
|
|
references = references.mid (start, end - start + 1);
|
|
envelope->setReferences(references.simplifyWhiteSpace());
|
|
}
|
|
}
|
|
else
|
|
{ // not a header we care about throw it away
|
|
parseLiteralC(inWords, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (specifier.find(".MIME") != -1)
|
|
{
|
|
mailHeader *envelope = new mailHeader;
|
|
TQString theHeader = parseLiteralC(inWords, false);
|
|
mimeIOTQString myIO;
|
|
myIO.setString (theHeader);
|
|
envelope->parseHeader (myIO);
|
|
if (lastHandled)
|
|
lastHandled->setHeader (envelope);
|
|
return;
|
|
}
|
|
// throw it away
|
|
kdDebug(7116) << "imapParser::parseBody - discarding " << seenUid.ascii () << endl;
|
|
parseLiteralC(inWords, true);
|
|
}
|
|
|
|
}
|
|
else // no part specifier
|
|
{
|
|
mailHeader *envelope = 0;
|
|
if (lastHandled)
|
|
envelope = lastHandled->getHeader ();
|
|
|
|
if (!envelope || seenUid.isEmpty ())
|
|
{
|
|
kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl;
|
|
// don't know where to put it, throw it away
|
|
parseSentence (inWords);
|
|
}
|
|
else
|
|
{
|
|
kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl;
|
|
// fill it up with data
|
|
TQString 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
|
|
{
|
|
TQCString word = parseLiteralC(inWords, false, true);
|
|
|
|
if(!word.isEmpty()) {
|
|
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"
|
|
parseLiteralC(inWords, true);
|
|
}
|
|
else if (word == "BODYSTRUCTURE")
|
|
{
|
|
mailHeader *envelope = 0;
|
|
|
|
if (lastHandled)
|
|
envelope = lastHandled->getHeader ();
|
|
|
|
// fill it up with data
|
|
TQString section;
|
|
mimeHeader *body =
|
|
parseBodyStructure (inWords, section, envelope);
|
|
TQByteArray data;
|
|
TQDataStream stream( data, IO_WriteOnly );
|
|
if (body) body->serialize(stream);
|
|
parseRelay(data);
|
|
|
|
delete body;
|
|
}
|
|
break;
|
|
|
|
case 'U':
|
|
if (word == "UID")
|
|
{
|
|
seenUid = parseOneWordC(inWords);
|
|
mailHeader *envelope = 0;
|
|
if (lastHandled)
|
|
envelope = lastHandled->getHeader ();
|
|
else
|
|
lastHandled = new imapCache();
|
|
|
|
if (seenUid.isEmpty ())
|
|
{
|
|
// unknown what to do
|
|
kdDebug(7116) << "imapParser::parseFetch - UID empty" << endl;
|
|
}
|
|
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.find ("RFC822") == 0)
|
|
{
|
|
// might be RFC822 RFC822.TEXT RFC822.HEADER
|
|
parseLiteralC(inWords, true);
|
|
}
|
|
break;
|
|
|
|
case 'I':
|
|
if (word == "INTERNALDATE")
|
|
{
|
|
TQCString date = parseOneWordC(inWords);
|
|
if (!lastHandled) lastHandled = new imapCache();
|
|
lastHandled->setDate(date);
|
|
}
|
|
break;
|
|
|
|
case 'F':
|
|
if (word == "FLAGS")
|
|
{
|
|
//kdDebug(7116) << "GOT FLAGS " << inWords.cstr() << endl;
|
|
if (!lastHandled) lastHandled = new imapCache();
|
|
lastHandled->setFlags (imapInfo::_flags (inWords.cstr()));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
parseLiteralC(inWords);
|
|
break;
|
|
}
|
|
} else {
|
|
parseLiteralC(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
|
|
parseLiteralC(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:
|
|
parseLiteralC(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;
|
|
|
|
TQString delimEmpty;
|
|
if ( namespaceToDelimiter.contains( TQString() ) )
|
|
delimEmpty = namespaceToDelimiter[TQString()];
|
|
|
|
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
|
|
TQCString prefix = parseOneWordC( result );
|
|
// delimiter
|
|
TQCString delim = parseOneWordC( result );
|
|
kdDebug(7116) << "imapParser::parseNamespace ns='" << prefix <<
|
|
"',delim='" << delim << "'" << endl;
|
|
if ( ns == 0 )
|
|
{
|
|
// at least one personal ns
|
|
personalAvailable = true;
|
|
}
|
|
TQString nsentry = TQString::number( ns ) + "=" + TQString(prefix) +
|
|
"=" + TQString(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;
|
|
parseOneWordC( result );
|
|
} else {
|
|
// drop whatever it is
|
|
parseOneWordC( result );
|
|
}
|
|
}
|
|
if ( !delimEmpty.isEmpty() ) {
|
|
// remember default delimiter
|
|
namespaceToDelimiter[TQString()] = delimEmpty;
|
|
if ( !personalAvailable )
|
|
{
|
|
// at least one personal ns would be nice
|
|
kdDebug(7116) << "imapParser::parseNamespace - registering own personal ns" << endl;
|
|
TQString nsentry = "0==" + delimEmpty;
|
|
imapNamespaces.append( nsentry );
|
|
}
|
|
}
|
|
}
|
|
|
|
int imapParser::parseLoop ()
|
|
{
|
|
parseString result;
|
|
|
|
if (!parseReadLine(result.data)) return -1;
|
|
|
|
//kdDebug(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
|
|
kdDebug(7116) << "imapParser::parseLoop - unhandledResponse: \n" << result.cstr() << endl;
|
|
unhandled << result.cstr();
|
|
}
|
|
else
|
|
{
|
|
imapCommand *current = sentQueue.at (0);
|
|
switch (result[0])
|
|
{
|
|
case '*':
|
|
result.data.resize(result.data.size() - 2); // tie off CRLF
|
|
parseUntagged (result);
|
|
break;
|
|
case '+':
|
|
continuation.duplicate(result.data);
|
|
break;
|
|
default:
|
|
{
|
|
TQCString tag = parseLiteralC(result);
|
|
if (current->id() == tag.data())
|
|
{
|
|
result.data.resize(result.data.size() - 2); // tie off CRLF
|
|
TQByteArray resultCode = parseLiteral (result); //the result
|
|
current->setResult (resultCode);
|
|
current->setResultInfo(result.cstr());
|
|
current->setComplete ();
|
|
|
|
sentQueue.removeRef (current);
|
|
completeQueue.append (current);
|
|
if (result.length())
|
|
parseResult (resultCode, result, current->command());
|
|
}
|
|
else
|
|
{
|
|
kdDebug(7116) << "imapParser::parseLoop - unknown tag '" << tag << "'" << endl;
|
|
TQCString cstr = tag + " " + result.cstr();
|
|
result.data = cstr;
|
|
result.pos = 0;
|
|
result.data.resize(cstr.length());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
imapParser::parseRelay (const TQByteArray & buffer)
|
|
{
|
|
Q_UNUSED(buffer);
|
|
tqWarning
|
|
("imapParser::parseRelay - virtual function not reimplemented - data lost");
|
|
}
|
|
|
|
void
|
|
imapParser::parseRelay (ulong len)
|
|
{
|
|
Q_UNUSED(len);
|
|
tqWarning
|
|
("imapParser::parseRelay - virtual function not reimplemented - announcement lost");
|
|
}
|
|
|
|
bool imapParser::parseRead (TQByteArray & buffer, ulong len, ulong relay)
|
|
{
|
|
Q_UNUSED(buffer);
|
|
Q_UNUSED(len);
|
|
Q_UNUSED(relay);
|
|
tqWarning
|
|
("imapParser::parseRead - virtual function not reimplemented - no data read");
|
|
return FALSE;
|
|
}
|
|
|
|
bool imapParser::parseReadLine (TQByteArray & buffer, ulong relay)
|
|
{
|
|
Q_UNUSED(buffer);
|
|
Q_UNUSED(relay);
|
|
tqWarning
|
|
("imapParser::parseReadLine - virtual function not reimplemented - no data read");
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
imapParser::parseWriteLine (const TQString & str)
|
|
{
|
|
Q_UNUSED(str);
|
|
tqWarning
|
|
("imapParser::parseWriteLine - virtual function not reimplemented - no data written");
|
|
}
|
|
|
|
void
|
|
imapParser::parseURL (const KURL & _url, TQString & _box, TQString & _section,
|
|
TQString & _type, TQString & _uid, TQString & _validity, TQString & _info)
|
|
{
|
|
TQStringList parameters;
|
|
|
|
_box = _url.path ();
|
|
kdDebug(7116) << "imapParser::parseURL " << _box << endl;
|
|
int paramStart = _box.find("/;");
|
|
if ( paramStart > -1 )
|
|
{
|
|
TQString paramString = _box.right( _box.length() - paramStart-2 );
|
|
parameters = TQStringList::split (';', paramString); //split parameters
|
|
_box.truncate( paramStart ); // strip parameters
|
|
}
|
|
// extract parameters
|
|
for (TQStringList::ConstIterator it (parameters.begin ());
|
|
it != parameters.end (); ++it)
|
|
{
|
|
TQString temp = (*it);
|
|
|
|
int pt = temp.find ('/');
|
|
if (pt > 0)
|
|
{
|
|
if (temp.findRev ('"', pt) == -1 || temp.find('"', pt) == -1)
|
|
{
|
|
// if we have non-quoted '/' separator we'll just nuke it
|
|
temp.truncate(pt);
|
|
}
|
|
}
|
|
if (temp.find ("section=", 0, false) == 0)
|
|
_section = temp.right (temp.length () - 8);
|
|
else if (temp.find ("type=", 0, false) == 0)
|
|
_type = temp.right (temp.length () - 5);
|
|
else if (temp.find ("uid=", 0, false) == 0)
|
|
_uid = temp.right (temp.length () - 4);
|
|
else if (temp.find ("uidvalidity=", 0, false) == 0)
|
|
_validity = temp.right (temp.length () - 12);
|
|
else if (temp.find ("info=", 0, false) == 0)
|
|
_info = temp.right (temp.length () - 5);
|
|
}
|
|
// kdDebug(7116) << "URL: section= " << _section << ", type= " << _type << ", uid= " << _uid << endl;
|
|
// kdDebug(7116) << "URL: user() " << _url.user() << endl;
|
|
// kdDebug(7116) << "URL: path() " << _url.path() << endl;
|
|
// kdDebug(7116) << "URL: encodedPathAndQuery() " << _url.encodedPathAndQuery() << endl;
|
|
|
|
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);
|
|
}
|
|
kdDebug(7116) << "URL: box= " << _box << ", section= " << _section << ", type= "
|
|
<< _type << ", uid= " << _uid << ", validity= " << _validity << ", info= " << _info << endl;
|
|
}
|
|
|
|
|
|
TQCString imapParser::parseLiteralC(parseString & inWords, bool relay, bool stopAtBracket, int *outlen) {
|
|
|
|
if (!inWords.isEmpty() && inWords[0] == '{')
|
|
{
|
|
TQCString retVal;
|
|
ulong runLen = inWords.find ('}', 1);
|
|
if (runLen > 0)
|
|
{
|
|
bool proper;
|
|
ulong runLenSave = runLen + 1;
|
|
TQCString tmpstr(runLen);
|
|
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);
|
|
TQByteArray rv;
|
|
parseRead (rv, runLen, relay ? runLen : 0);
|
|
rv.resize(TQMAX(runLen, rv.size())); // what's the point?
|
|
retVal = b2c(rv);
|
|
inWords.clear();
|
|
parseReadLine (inWords.data); // must get more
|
|
|
|
// no duplicate data transfers
|
|
relay = false;
|
|
}
|
|
else
|
|
{
|
|
kdDebug(7116) << "imapParser::parseLiteral - error parsing {} - " /*<< strLen*/ << endl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
inWords.clear();
|
|
kdDebug(7116) << "imapParser::parseLiteral - error parsing unmatched {" << endl;
|
|
}
|
|
if (outlen) {
|
|
*outlen = retVal.length(); // optimize me
|
|
}
|
|
skipWS (inWords);
|
|
return retVal;
|
|
}
|
|
|
|
return parseOneWordC(inWords, stopAtBracket, outlen);
|
|
}
|
|
|
|
// does not know about literals ( {7} literal )
|
|
TQCString imapParser::parseOneWordC (parseString & inWords, bool stopAtBracket, int *outLen)
|
|
{
|
|
uint retValSize = 0;
|
|
uint len = inWords.length();
|
|
if (len == 0) {
|
|
return TQCString();
|
|
}
|
|
|
|
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)
|
|
{
|
|
TQCString retVal(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[len - offset] = 0;
|
|
retValSize = len - offset;
|
|
inWords.pos += i;
|
|
skipWS (inWords);
|
|
if (outLen) {
|
|
*outLen = retValSize;
|
|
}
|
|
return retVal;
|
|
}
|
|
else
|
|
{
|
|
kdDebug(7116) << "imapParser::parseOneWord - error parsing unmatched \"" << endl;
|
|
TQCString retVal = inWords.cstr();
|
|
retValSize = len;
|
|
inWords.clear();
|
|
if (outLen) {
|
|
*outLen = retValSize;
|
|
}
|
|
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;
|
|
}
|
|
|
|
TQCString retVal(i+1);
|
|
inWords.takeLeftNoResize(retVal, i);
|
|
retValSize = i;
|
|
inWords.pos += i;
|
|
|
|
if (retVal == "NIL") {
|
|
retVal.truncate(0);
|
|
retValSize = 0;
|
|
}
|
|
skipWS (inWords);
|
|
if (outLen) {
|
|
*outLen = retValSize;
|
|
}
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
|
|
{
|
|
bool valid;
|
|
num = parseOneWordC(inWords, TRUE).toULong(&valid);
|
|
return valid;
|
|
}
|
|
|
|
bool imapParser::hasCapability (const TQString & cap)
|
|
{
|
|
TQString c = cap.lower();
|
|
// kdDebug(7116) << "imapParser::hasCapability - Looking for '" << cap << "'" << endl;
|
|
for (TQStringList::ConstIterator it = imapCapabilities.begin ();
|
|
it != imapCapabilities.end (); ++it)
|
|
{
|
|
// kdDebug(7116) << "imapParser::hasCapability - Examining '" << (*it) << "'" << endl;
|
|
if ( !(kasciistricmp(c.ascii(), (*it).ascii())) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void imapParser::removeCapability (const TQString & cap)
|
|
{
|
|
imapCapabilities.remove(cap.lower());
|
|
}
|
|
|
|
TQString imapParser::namespaceForBox( const TQString & box )
|
|
{
|
|
kdDebug(7116) << "imapParse::namespaceForBox " << box << endl;
|
|
TQString myNamespace;
|
|
if ( !box.isEmpty() )
|
|
{
|
|
TQValueList<TQString> list = namespaceToDelimiter.keys();
|
|
TQString cleanPrefix;
|
|
for ( TQValueList<TQString>::Iterator it = list.begin(); it != list.end(); ++it )
|
|
{
|
|
if ( !(*it).isEmpty() && box.find( *it ) != -1 )
|
|
return (*it);
|
|
}
|
|
}
|
|
return myNamespace;
|
|
}
|
|
|