/**********************************************************************
 *
 *   imapparser.cpp  - 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 <tdeglobal.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( TDEIO::SlaveBase *slave, TDEIO::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 ( TDEIO::SlaveBase *slave, TDEIO::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

  if(!what.isEmpty ()) {
  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);
	if(!what.isEmpty ()) {
        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 ( ' ', 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 it is 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;
    long srunLen = inWords.find ('}', 1); // Can return -1, so use a signed long
    if (srunLen > 0)
    {
      ulong runLen = (ulong)srunLen;
      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;
}

