/**********************************************************************
 *
 *   imapparser.cc  - IMAP4rev1 Parser
 *   Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
 *   Copyright (C) 2000 Sven Carstens <s.carstens@gmx.de>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *   Send comments and bug fixes to s.carstens@gmx.de
 *
 *********************************************************************/

#include "imapparser.h"
#include "imapinfo.h"
#include "mailheader.h"
#include "mimeheader.h"
#include "mailaddress.h"

#include <sys/types.h>

#include <stdlib.h>
#include <unistd.h>
#include <QList>

extern "C" {
#include <sasl/sasl.h>
}

#include <QRegExp>
#include <QBuffer>
#include <QString>
#include <QStringList>

#include <kascii.h>
#include <kdebug.h>
#include <kcodecs.h>
#include <kglobal.h>
#include <kurl.h>

#include <kimap/rfccodecs.h>
using namespace KIMAP;

static sasl_callback_t callbacks[] = {
    { SASL_CB_ECHOPROMPT, NULL, NULL },
    { SASL_CB_NOECHOPROMPT, NULL, NULL },
    { SASL_CB_GETREALM, NULL, NULL },
    { SASL_CB_USER, NULL, NULL },
    { SASL_CB_AUTHNAME, NULL, NULL },
    { SASL_CB_PASS, NULL, NULL },
    { SASL_CB_CANON_USER, NULL, NULL },
    { SASL_CB_LIST_END, NULL, NULL }
};

imapParser::imapParser ()
{
  currentState = ISTATE_NO;
  commandCounter = 0;
  lastHandled = 0;
}

imapParser::~imapParser ()
{
  delete lastHandled;
  lastHandled = 0;
}

CommandPtr
imapParser::doCommand (CommandPtr aCmd)
{
  int pl = 0;
  sendCommand( aCmd );
  while ( pl != -1 && !aCmd->isComplete() ) {
    while ( ( pl = parseLoop() ) == 0 ) {
      ;
    }
  }
  return aCmd;
}

CommandPtr
imapParser::sendCommand (CommandPtr aCmd)
{
  aCmd->setId( QString::number( commandCounter++ ) );
  sentQueue.append( aCmd );

  continuation.resize( 0 );
  const QString& command = aCmd->command();

  if ( command == "SELECT" || command == "EXAMINE" ) {
     // we need to know which box we are selecting
    parseString p;
    p.fromString( aCmd->parameter() );
    currentBox = parseOneWord( p );
    kDebug( 7116 ) << "imapParser::sendCommand - setting current box to" << currentBox;
  } else if ( command == "CLOSE" ) {
     // we no longer have a box open
    currentBox.clear();
  } else if ( command.contains( "SEARCH" ) ||
              command == "GETACL" ||
              command == "LISTRIGHTS" ||
              command == "MYRIGHTS" ||
              command == "GETANNOTATION" ||
              command == "NAMESPACE" ||
              command == "GETQUOTAROOT" ||
              command == "GETQUOTA" ||
              command == "X-GET-OTHER-USERS" ||
              command == "X-GET-DELEGATES" ||
              command == "X-GET-OUT-OF-OFFICE" ) {
    lastResults.clear();
  } else if ( command == "LIST" ||
              command == "LSUB" ) {
    listResponses.clear();
  }
  parseWriteLine( aCmd->getStr() );
  return aCmd;
}

bool
imapParser::clientLogin (const QString & aUser, const QString & aPass,
  QString & resultInfo)
{
  CommandPtr cmd;
  bool retVal = false;

  cmd = doCommand( CommandPtr( new imapCommand( "LOGIN", "\"" + KIMAP::quoteIMAP( aUser ) +
                                                "\" \"" + KIMAP::quoteIMAP( aPass ) + "\"" ) ) );

  if ( cmd->result() == "OK" ) {
    currentState = ISTATE_LOGIN;
    retVal = true;
  }
  resultInfo = cmd->resultInfo();
  completeQueue.removeAll( cmd );
  return retVal;
}

static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in )
{
  kDebug( 7116 ) << "sasl_interact";
  sasl_interact_t *interact = ( sasl_interact_t * ) in;

  //some mechanisms do not require username && pass, so it doesn't need a popup
  //window for getting this info
  for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
    if ( interact->id == SASL_CB_AUTHNAME ||
         interact->id == SASL_CB_PASS ) {

      if ( ai.username.isEmpty() || ai.password.isEmpty() ) {
        if ( !slave->openPasswordDialog( ai ) ) {
          return false;
        }
      }
      break;
    }
  }

  interact = ( sasl_interact_t * ) in;
  while ( interact->id != SASL_CB_LIST_END ) {
    kDebug( 7116 ) << "SASL_INTERACT id:" << interact->id;
    switch ( interact->id ) {
      case SASL_CB_USER:
      case SASL_CB_AUTHNAME:
        kDebug( 7116 ) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'";
        interact->result = strdup( ai.username.toUtf8() );
        interact->len = strlen( (const char *) interact->result );
        break;
      case SASL_CB_PASS:
        kDebug( 7116 ) << "SASL_CB_PASS: [hidden]";
        interact->result = strdup( ai.password.toUtf8() );
        interact->len = strlen( (const char *) interact->result );
        break;
      default:
        interact->result = 0;
        interact->len = 0;
        break;
    }
    interact++;
  }
  return true;
}

bool
imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai,
  const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo)
{
  bool retVal = false;
  int result;
  sasl_conn_t *conn = 0;
  sasl_interact_t *client_interact = 0;
  const char *out = 0;
  uint outlen = 0;
  const char *mechusing = 0;
  QByteArray tmp, challenge;

  kDebug( 7116 ) << "aAuth:" << aAuth << " FQDN:" << aFQDN << " isSSL:" << isSSL;

  // see if server supports this authenticator
  if ( !hasCapability( "AUTH=" + aAuth ) ) {
    return false;
  }

//  result = sasl_client_new( isSSL ? "imaps" : "imap",
  result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri
                                       must be 'imap'. I don't know if it's good or bad. */
                            aFQDN.toLatin1(),
                            0, 0, callbacks, 0, &conn );

  if ( result != SASL_OK ) {
    kDebug( 7116 ) << "sasl_client_new failed with:" << result;
    resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
    return false;
  }

  do {
    result = sasl_client_start( conn, aAuth.toLatin1(), &client_interact,
                                hasCapability( "SASL-IR" ) ? &out : 0, &outlen, &mechusing );

    if ( result == SASL_INTERACT ) {
      if ( !sasl_interact( slave, ai, client_interact ) ) {
        sasl_dispose( &conn );
        return false;
      }
    }
  } while ( result == SASL_INTERACT );

  if ( result != SASL_CONTINUE && result != SASL_OK ) {
    kDebug( 7116 ) << "sasl_client_start failed with:" << result;
    resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
    sasl_dispose( &conn );
    return false;
  }
  CommandPtr cmd;

  tmp = QByteArray::fromRawData( out, outlen );
  challenge = tmp.toBase64();
  tmp.clear();
  // then lets try it
  QString firstCommand = aAuth;
  if ( !challenge.isEmpty() ) {
    firstCommand += ' ';
    firstCommand += QString::fromLatin1( challenge.data(), challenge.size() );
  }
  cmd = sendCommand( CommandPtr( new imapCommand( "AUTHENTICATE", firstCommand.toLatin1() ) ) );

  int pl = 0;
  while ( pl != -1 && !cmd->isComplete() ) {
    //read the next line
    while ( ( pl = parseLoop() ) == 0 ) {
      ;
    }

    if ( !continuation.isEmpty() ) {
//      kDebug( 7116 ) << "S:" << QCString( continuation.data(), continuation.size() + 1 );
      if ( continuation.size() > 4 ) {
        tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 );
        challenge = QByteArray::fromBase64( tmp );
//        kDebug( 7116 ) << "S-1:" << QCString( challenge.data(), challenge.size() + 1 );
        tmp.clear();
      }

      do {
        result = sasl_client_step( conn, challenge.isEmpty() ? 0 : challenge.data(),
                                   challenge.size(),
                                   &client_interact,
                                   &out, &outlen );

        if ( result == SASL_INTERACT ) {
          if ( !sasl_interact( slave, ai, client_interact ) ) {
            sasl_dispose( &conn );
            return false;
          }
        }
      } while ( result == SASL_INTERACT );

      if ( result != SASL_CONTINUE && result != SASL_OK ) {
        kDebug( 7116 ) << "sasl_client_step failed with:" << result;
        resultInfo = QString::fromUtf8( sasl_errdetail( conn ) );
        sasl_dispose( &conn );
        return false;
      }

      tmp = QByteArray::fromRawData( out, outlen );
//      kDebug( 7116 ) << "C-1:" << QCString( tmp.data(), tmp.size() + 1 );
      challenge = tmp.toBase64();
      tmp.clear();
//      kDebug( 7116 ) << "C:" << QCString( challenge.data(), challenge.size() + 1 );
      parseWriteLine( challenge );
      continuation.resize( 0 );
    }
  }

  if ( cmd->result() == "OK" ) {
    currentState = ISTATE_LOGIN;
    retVal = true;
  }
  resultInfo = cmd->resultInfo();
  completeQueue.removeAll( cmd );

  sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection.
  return retVal;
}

void
imapParser::parseUntagged (parseString & result)
{
  //kDebug( 7116 ) << "imapParser::parseUntagged - '" << result.cstr() << "'";

  parseOneWord( result );        // *
  QByteArray what = parseLiteral( result ); // see whats coming next

  switch ( what[0] ) {
    //the status responses
  case 'B':                    // BAD or BYE
    if ( qstrncmp( what, "BAD", what.size() ) == 0 ) {
      parseResult( what, result );
    } else if ( qstrncmp( what, "BYE", what.size() ) == 0 ) {
      parseResult( what, result );
      if ( sentQueue.count() ) {
        // BYE that interrupts a command -> copy the reason for it
        CommandPtr current = sentQueue.at( 0 );
        current->setResultInfo( result.cstr() );
      }
      currentState = ISTATE_NO;
    }
    break;

  case 'N':                    // NO
    if ( what[1] == 'O' && what.size() == 2 ) {
      parseResult( what, result );
    } else if ( qstrncmp( what, "NAMESPACE", what.size() ) == 0 ) {
      parseNamespace( result );
    }
    break;

  case 'O':                    // OK
    if ( what[1] == 'K' && what.size() == 2 ) {
      parseResult( what, result );
    } else if ( qstrncmp( what, "OTHER-USER", 10 ) == 0 ) { // X-GET-OTHER-USER
      parseOtherUser( result );
    } else if ( qstrncmp( what, "OUT-OF-OFFICE", 13 ) == 0 ) { // X-GET-OUT-OF-OFFICE
      parseOutOfOffice( result );
    }
    break;
  case 'D':
    if ( qstrncmp( what, "DELEGATE", 8 ) == 0 ) { // X-GET-DELEGATES
      parseDelegate( result );
    }
    break;

  case 'P':                    // PREAUTH
    if ( qstrncmp( what, "PREAUTH", what.size() ) == 0 ) {
      parseResult( what, result );
      currentState = ISTATE_LOGIN;
    }
    break;

    // parse the other responses
  case 'C':                    // CAPABILITY
    if ( qstrncmp( what, "CAPABILITY", what.size() ) == 0 ) {
      parseCapability( result );
    }
    break;

  case 'F':                    // FLAGS
    if ( qstrncmp( what, "FLAGS", what.size() ) == 0 ) {
      parseFlags( result );
    }
    break;

  case 'L':                    // LIST or LSUB or LISTRIGHTS
    if ( qstrncmp( what, "LIST", what.size() ) == 0 ) {
      parseList( result );
    } else if ( qstrncmp( what, "LSUB", what.size() ) == 0 ) {
      parseLsub( result );
    } else if ( qstrncmp( what, "LISTRIGHTS", what.size() ) == 0 ) {
      parseListRights( result );
    }
    break;

  case 'M': // MYRIGHTS
    if ( qstrncmp( what, "MYRIGHTS", what.size() ) == 0 ) {
      parseMyRights( result );
    }
    break;
  case 'S':                    // SEARCH or STATUS
    if ( qstrncmp( what, "SEARCH", what.size() ) == 0 ) {
      parseSearch( result );
    } else if ( qstrncmp( what, "STATUS", what.size() ) == 0 ) {
      parseStatus( result );
    }
    break;

  case 'A': // ACL or ANNOTATION
    if ( qstrncmp( what, "ACL", what.size() ) == 0 ) {
      parseAcl( result );
    } else if ( qstrncmp( what, "ANNOTATION", what.size() ) == 0 ) {
      parseAnnotation( result );
    }
    break;
  case 'Q': // QUOTA or QUOTAROOT
    if ( what.size() > 5 && qstrncmp( what, "QUOTAROOT", what.size() ) == 0 ) {
      parseQuotaRoot( result );
    } else if ( qstrncmp( what, "QUOTA", what.size() ) == 0 ) {
      parseQuota( result );
    }
    break;
  case 'X': // Custom command
    {
      parseCustom( result );
    }
    break;
  default:
    //better be a number
    {
      ulong number;
      bool valid;

      number = what.toUInt( &valid );
      if ( valid ) {
        what = parseLiteral( result );
        switch ( what[0] ) {
        case 'E':
          if ( qstrncmp( what, "EXISTS", what.size() ) == 0 ) {
            parseExists( number, result );
          } else if ( qstrncmp( what, "EXPUNGE", what.size() ) == 0 ) {
            parseExpunge( number, result );
          }
          break;

        case 'F':
          if ( qstrncmp( what, "FETCH", what.size() ) == 0 ) {
            seenUid.clear();
            parseFetch( number, result );
          }
          break;

        case 'S':
          if ( qstrncmp( what, "STORE", what.size() ) == 0 ) {  // deprecated store
            seenUid.clear();
            parseFetch( number, result );
          }
          break;

        case 'R':
          if ( qstrncmp( what, "RECENT", what.size() ) == 0 ) {
            parseRecent( number, result );
          }
          break;
        default:
          break;
        }
      }
    }
    break;
  }                             //switch
}                               //func

void
imapParser::parseResult (QByteArray & result, parseString & rest,
  const QString & command)
{
  if ( command == "SELECT" ) {
    selectInfo.setReadWrite( true );
  }

  if ( rest[0] == '[' ) {
    rest.pos++;
    QByteArray option = parseOneWord( rest, true );

    switch ( option[0] ) {
    case 'A':                  // ALERT
      if ( option == "ALERT" ) {
        rest.pos = rest.data.indexOf( ']', rest.pos ) + 1;
        // The alert text is after [ALERT].
        // Is this correct or do we need to care about litterals?
        selectInfo.setAlert( rest.cstr() );
      }
      break;

    case 'N':                  // NEWNAME
      if ( option == "NEWNAME" ) {
      }
      break;

    case 'P':                  //PARSE or PERMANENTFLAGS
      if ( option == "PARSE" ) {
      } else if ( option == "PERMANENTFLAGS" ) {
        uint end = rest.data.indexOf( ']', rest.pos );
        QByteArray flags( rest.data.data() + rest.pos, end - rest.pos );
        selectInfo.setPermanentFlags( flags );
        rest.pos = end;
      }
      break;

    case 'R':                  //READ-ONLY or READ-WRITE
      if ( option == "READ-ONLY" ) {
        selectInfo.setReadWrite( false );
      } else if ( option == "READ-WRITE" ) {
        selectInfo.setReadWrite( true );
      }
      break;

    case 'T':                  //TRYCREATE
      if ( option == "TRYCREATE" ) {
      }
      break;

    case 'U':                  //UIDVALIDITY or UNSEEN
      if ( option == "UIDVALIDITY" ) {
        ulong value;
        if ( parseOneNumber( rest, value ) ) {
          selectInfo.setUidValidity( value );
        }
      } else if ( option == "UNSEEN" ) {
        ulong value;
        if ( parseOneNumber( rest, value ) ) {
          selectInfo.setUnseen( value );
        }
      } else if ( option == "UIDNEXT" ) {
        ulong value;
        if ( parseOneNumber( rest, value ) ) {
          selectInfo.setUidNext( value );
        }
      }
      break;

    }
    if ( rest[0] == ']' ) {
      rest.pos++; //tie off ]
    }
    skipWS( rest );
  }

  if ( command.isEmpty() ) {
    // This happens when parsing an intermediate result line (those that start with '*').
    // No state change involved, so we can stop here.
    return;
  }

  switch ( command[0].toLatin1() ) {
  case 'A':
    if ( command == "AUTHENTICATE" ) {
      if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
        currentState = ISTATE_LOGIN;
      }
    }
    break;

  case 'L':
    if ( command == "LOGIN" ) {
      if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
        currentState = ISTATE_LOGIN;
      }
    }
    break;

  case 'E':
    if ( command == "EXAMINE" ) {
      if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
        currentState = ISTATE_SELECT;
      } else {
        if ( currentState == ISTATE_SELECT ) {
          currentState = ISTATE_LOGIN;
        }
        currentBox.clear();
      }
      kDebug( 7116 ) << "imapParser::parseResult - current box is now" << currentBox;
    }
    break;

  case 'S':
    if ( command == "SELECT" ) {
      if ( qstrncmp( result, "OK", result.size() ) == 0 ) {
        currentState = ISTATE_SELECT;
      } else {
        if ( currentState == ISTATE_SELECT ) {
          currentState = ISTATE_LOGIN;
        }
        currentBox.clear();
      }
      kDebug( 7116 ) << "imapParser::parseResult - current box is now" << currentBox;
    }
    break;

  default:
    break;
  }
}

void imapParser::parseCapability (parseString & result)
{
  QByteArray data = result.cstr();
  kAsciiToLower( data.data() );
  imapCapabilities = QString::fromLatin1( data ).split( ' ', QString::SkipEmptyParts );
}

void imapParser::parseFlags (parseString & result)
{
  selectInfo.setFlags( result.cstr() );
}

void imapParser::parseList (parseString & result)
{
  imapList this_one;

  if ( result[0] != '(' ) {
    return;                     //not proper format for us
  }

  result.pos++; // tie off (

  this_one.parseAttributes( result );

  result.pos++; // tie off )
  skipWS( result );

  this_one.setHierarchyDelimiter( parseLiteral( result ) );
  this_one.setName( QString::fromUtf8( KIMAP::decodeImapFolderName( parseLiteral( result ) ) ) );  // decode modified UTF7

  listResponses.append( this_one );
}

void imapParser::parseLsub (parseString & result)
{
  imapList this_one( result.cstr(), *this );
  listResponses.append( this_one );
}

void imapParser::parseListRights (parseString & result)
{
  parseOneWord( result ); // skip mailbox name
  parseOneWord( result ); // skip user id
  while ( true ) {
    const QByteArray word = parseOneWord( result );
    if ( word.isEmpty() ) {
      break;
    }
    lastResults.append( word );
  }
}

void imapParser::parseAcl (parseString & result)
{
  parseOneWord( result ); // skip mailbox name
  // The result is user1 perm1 user2 perm2 etc. The caller will sort it out.
  while ( !result.isEmpty() ) {
    const QByteArray word = parseLiteral( result );
    if ( word.isEmpty() ) {
      break;
    }
    lastResults.append( word );
  }
}

void imapParser::parseAnnotation (parseString & result)
{
  parseOneWord( result ); // skip mailbox name
  skipWS( result );
  parseOneWord( result ); // skip entry name (we know it since we don't allow wildcards in it)
  skipWS( result );
  if ( result.isEmpty() || result[0] != '(' ) {
    return;
  }
  result.pos++;
  skipWS( result );
  // The result is name1 value1 name2 value2 etc. The caller will sort it out.
  while ( !result.isEmpty() && result[0] != ')' ) {
    const QByteArray word = parseLiteral( result );
    if ( word.isEmpty() ) {
      break;
    }
    lastResults.append( word );
  }
}

void imapParser::parseQuota (parseString & result)
{
  // quota_response  ::= "QUOTA" SP astring SP quota_list
  // quota_list      ::= "(" #quota_resource ")"
  // quota_resource  ::= atom SP number SP number
  QByteArray root = parseOneWord( result );
  if ( root.isEmpty() ) {
    lastResults.append( "" );
  } else {
    lastResults.append( root );
  }
  if ( result.isEmpty() || result[0] != '(' ) {
    return;
  }
  result.pos++;
  skipWS( result );
  QStringList triplet;
  while ( !result.isEmpty() && result[0] != ')' ) {
    const QByteArray word = parseLiteral( result );
    if ( word.isEmpty() ) {
      break;
    }
    triplet.append( word );
  }
  lastResults.append( triplet.join( " " ) );
}

void imapParser::parseQuotaRoot (parseString & result)
{
  //    quotaroot_response
  //         ::= "QUOTAROOT" SP astring *(SP astring)
  parseOneWord( result ); // skip mailbox name
  skipWS( result );
  if ( result.isEmpty() ) {
    return;
  }
  QStringList roots;
  while ( !result.isEmpty() ) {
    const QByteArray word = parseLiteral( result );
    if ( word.isEmpty() ) {
      break;
    }
    roots.append( word );
  }
  lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) );
}

void imapParser::parseCustom (parseString & result)
{
  QByteArray word = parseLiteral( result, false, false );
  lastResults.append( word );
}

void imapParser::parseOtherUser (parseString & result)
{
  lastResults.append( parseOneWord( result ) );
}

void imapParser::parseDelegate (parseString & result)
{
  const QString email = parseOneWord( result );

  QStringList rights;
  while ( !result.isEmpty() ) {
    QByteArray word = parseLiteral( result, false, false );
    rights.append( word );
  }

  lastResults.append( email + ':' + rights.join( "," ) );
}

void imapParser::parseOutOfOffice (parseString & result)
{
  const QString state = parseOneWord( result );
  parseOneWord( result ); // skip encoding

  QByteArray msg = parseLiteral( result, false, false );

  lastResults.append( state + '^' + QString::fromUtf8( msg ) );
}

void imapParser::parseMyRights (parseString & result)
{
  parseOneWord( result ); // skip mailbox name
  Q_ASSERT( lastResults.isEmpty() ); // we can only be called once
  lastResults.append( parseOneWord( result ) );
}

void imapParser::parseSearch (parseString & result)
{
  ulong value;

  while ( parseOneNumber( result, value ) ) {
    lastResults.append( QString::number( value ) );
  }
}

void imapParser::parseStatus (parseString & inWords)
{
  lastStatus = imapInfo();

  parseLiteral( inWords );       // swallow the box
  if ( inWords[0] != '(' ) {
    return;
  }

  inWords.pos++;
  skipWS( inWords );

  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    ulong value;

    QByteArray label = parseOneWord( inWords );
    if ( parseOneNumber( inWords, value ) ) {
      if ( label == "MESSAGES" ) {
        lastStatus.setCount( value );
      } else if ( label == "RECENT" ) {
        lastStatus.setRecent( value );
      } else if ( label == "UIDVALIDITY" ) {
        lastStatus.setUidValidity( value );
      } else if ( label == "UNSEEN" ) {
        lastStatus.setUnseen( value );
      } else if ( label == "UIDNEXT" ) {
        lastStatus.setUidNext( value );
      }
    }
  }

  if ( inWords[0] == ')' ) {
    inWords.pos++;
  }
  skipWS( inWords );
}

void imapParser::parseExists (ulong value, parseString & result)
{
  selectInfo.setCount( value );
  result.pos = result.data.size();
}

void imapParser::parseExpunge (ulong value, parseString & result)
{
  Q_UNUSED( value );
  Q_UNUSED( result );
}

void imapParser::parseAddressList (parseString & inWords, QList<mailAddress *>& list)
{
  if ( inWords.isEmpty() ) {
    return;
  }
  if ( inWords[0] != '(' ) {
    parseOneWord( inWords );     // parse NIL
  } else {
    inWords.pos++;
    skipWS( inWords );

    while ( !inWords.isEmpty() && inWords[0] != ')' ) {
      if ( inWords[0] == '(' ) {
        mailAddress *addr = new mailAddress;
        parseAddress( inWords, *addr );
        list.append( addr );
      } else {
        break;
      }
    }

    if ( !inWords.isEmpty() && inWords[0] == ')' ) {
      inWords.pos++;
    }
    skipWS( inWords );
  }
}

const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal)
{
  inWords.pos++;
  skipWS( inWords );

  retVal.setFullName( parseLiteral( inWords ) );
  retVal.setCommentRaw( parseLiteral( inWords ) );
  retVal.setUser( parseLiteral( inWords ) );
  retVal.setHost( parseLiteral( inWords ) );

  if ( !inWords.isEmpty() && inWords[0] == ')' ) {
    inWords.pos++;
  }
  skipWS( inWords );
  return retVal;
}

mailHeader * imapParser::parseEnvelope (parseString & inWords)
{
  mailHeader *envelope = 0;

  if ( inWords[0] != '(' ) {
    return envelope;
  }
  inWords.pos++;
  skipWS( inWords );

  envelope = new mailHeader;

  //date
  envelope->setDate( parseLiteral( inWords ) );

  //subject
  envelope->setSubject( parseLiteral( inWords ) );

  QList<mailAddress *> list;

  //from
  parseAddressList( inWords, list );
  if ( !list.isEmpty() ) {
    envelope->setFrom( *list.last() );
    list.clear();
  }

  //sender
  parseAddressList(inWords, list);
  if ( !list.isEmpty() ) {
    envelope->setSender( *list.last() );
    list.clear();
  }

  //reply-to
  parseAddressList( inWords, list );
  if ( !list.isEmpty() ) {
    envelope->setReplyTo( *list.last() );
    list.clear();
  }

  //to
  parseAddressList( inWords, envelope->to() );

  //cc
  parseAddressList( inWords, envelope->cc() );

  //bcc
  parseAddressList( inWords, envelope->bcc() );

  //in-reply-to
  envelope->setInReplyTo( parseLiteral( inWords ) );

  //message-id
  envelope->setMessageId( parseLiteral( inWords ) );

  // see if we have more to come
  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    //eat the extensions to this part
    if ( inWords[0] == '(' ) {
      parseSentence( inWords );
    } else {
      parseLiteral( inWords );
    }
  }

  if ( !inWords.isEmpty() && inWords[0] == ')' ) {
    inWords.pos++;
  }
  skipWS( inWords );
  return envelope;
}

// parse parameter pairs into a dictionary
// caller must clean up the dictionary items
QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords)
{
  QByteArray disposition;
  QHash < QByteArray, QString > retVal;

  if ( inWords[0] != '(' ) {
    //disposition only
    disposition = parseOneWord( inWords );
  } else {
    inWords.pos++;
    skipWS( inWords );

    //disposition
    disposition = parseOneWord( inWords );

    retVal = parseParameters( inWords );
    if ( inWords[0] != ')' ) {
      return retVal;
    }
    inWords.pos++;
    skipWS( inWords );
  }

  if ( !disposition.isEmpty() ) {
    retVal.insert( "content-disposition", QString( disposition ) );
  }
  return retVal;
}

// parse parameter pairs into a dictionary
// caller must clean up the dictionary items
QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords)
{
  QHash < QByteArray, QString > retVal;

  if ( inWords[0] != '(' ) {
    //better be NIL
    parseOneWord( inWords );
  } else {
    inWords.pos++;
    skipWS( inWords );

    while ( !inWords.isEmpty() && inWords[0] != ')' ) {
      const QByteArray l1 = parseLiteral( inWords );
      const QByteArray l2 = parseLiteral( inWords );
      retVal.insert( l1.toLower(), QString( l2 ) );
    }

    if ( inWords[0] != ')' ) {
      return retVal;
    }
    inWords.pos++;
    skipWS( inWords );
  }
  return retVal;
}

mimeHeader * imapParser::parseSimplePart (parseString & inWords,
  QString & inSection, mimeHeader * localPart)
{
  QByteArray subtype;
  QByteArray typeStr;
  QHash < QByteArray, QString > parameters;
  ulong size;

  if ( inWords[0] != '(' ) {
    return 0;
  }

  if ( !localPart ) {
    localPart = new mimeHeader;
  }

  localPart->setPartSpecifier( inSection );

  inWords.pos++;
  skipWS( inWords );

  //body type
  typeStr = parseLiteral( inWords );

  //body subtype
  subtype = parseLiteral( inWords );

  localPart->setType( typeStr + '/' + subtype );

  //body parameter parenthesized list
  parameters = parseParameters( inWords );
  {
    QHashIterator < QByteArray, QString > it( parameters );

    while ( it.hasNext() ) {
      it.next();
      localPart->setTypeParm( it.key(), it.value() );
    }
    parameters.clear();
  }

  //body id
  localPart->setID( parseLiteral( inWords ) );

  //body description
  localPart->setDescription( parseLiteral( inWords ) );

  //body encoding
  localPart->setEncoding( parseLiteral( inWords ) );

  //body size
  if ( parseOneNumber( inWords, size ) ) {
    localPart->setLength( size );
  }

  // type specific extensions
  if ( localPart->getType().toUpper() == "MESSAGE/RFC822" ) {
    //envelope structure
    mailHeader *envelope = parseEnvelope( inWords );

    //body structure
    parseBodyStructure( inWords, inSection, envelope );

    localPart->setNestedMessage( envelope );

    //text lines
    ulong lines;
    parseOneNumber( inWords, lines );
  } else {
    if ( typeStr ==  "TEXT" ) {
      //text lines
      ulong lines;
      parseOneNumber( inWords, lines );
    }

    // md5
    parseLiteral( inWords );

    // body disposition
    parameters = parseDisposition( inWords );
    {
      QString disposition = parameters["content-disposition"];

      localPart->setDisposition( disposition.toLatin1() );
      QHashIterator < QByteArray, QString > it( parameters );
      while ( it.hasNext() ) {
        it.next();
        localPart->setDispositionParm( it.key(), it.value() );
      }
      parameters.clear();
    }

    // body language
    parseSentence( inWords );
  }

  // see if we have more to come
  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    //eat the extensions to this part
    if ( inWords[0] == '(' ) {
      parseSentence( inWords );
    } else {
      parseLiteral( inWords );
    }
  }

  if ( inWords[0] == ')' ) {
    inWords.pos++;
  }
  skipWS( inWords );
  return localPart;
}

mimeHeader * imapParser::parseBodyStructure (parseString & inWords,
  QString & inSection, mimeHeader * localPart)
{
  bool init = false;
  if ( inSection.isEmpty() ) {
    // first run
    init = true;
    // assume one part
    inSection = '1';
  }
  int section = 0;

  if ( inWords[0] != '(' ) {
    // skip ""
    parseOneWord( inWords );
    return 0;
  }
  inWords.pos++;
  skipWS( inWords );

  if ( inWords[0] == '(' ) {
    QByteArray subtype;
    QHash< QByteArray, QString > parameters;
    QString outSection;

    if ( !localPart ) {
      localPart = new mimeHeader;
    } else {
      // might be filled from an earlier run
      localPart->clearNestedParts();
      localPart->clearTypeParameters();
      localPart->clearDispositionParameters();
      // an envelope was passed in so this is the multipart header
      outSection = inSection + ".HEADER";
    }
    if ( inWords[0] == '(' && init ) {
      inSection = '0';
    }

    // set the section
    if ( !outSection.isEmpty() ) {
      localPart->setPartSpecifier( outSection );
    } else {
      localPart->setPartSpecifier( inSection );
    }

    // is multipart (otherwise it is a simplepart and handled later)
    while ( inWords[0] == '(' ) {
      outSection = QString::number( ++section );
      if ( !init ) {
        outSection = inSection + '.' + outSection;
      }
      mimeHeader *subpart = parseBodyStructure( inWords, outSection, 0 );
      localPart->addNestedPart( subpart );
    }

    // fetch subtype
    subtype = parseOneWord( inWords );

    localPart->setType( "MULTIPART/" + subtype );

    // fetch parameters
    parameters = parseParameters( inWords );
    {
      QHashIterator < QByteArray, QString > it( parameters );

      while ( it.hasNext() ) {
        it.next();
        localPart->setTypeParm( it.key(), it.value() );
      }
      parameters.clear();
    }

    // body disposition
    parameters = parseDisposition( inWords );
    {
      QString disposition = parameters["content-disposition"];

      localPart->setDisposition( disposition.toLatin1() );
      QHashIterator < QByteArray, QString > it( parameters );
      while ( it.hasNext() ) {
        it.next();
        localPart->setDispositionParm( it.key(), it.value() );
      }
      parameters.clear();
    }

    // body language
    parseSentence( inWords );

  } else {
    // is simple part
    inWords.pos--;
    inWords.data[inWords.pos] = '('; //fake a sentence
    if ( localPart ) {
      inSection = inSection + ".1";
    }
    localPart = parseSimplePart( inWords, inSection, localPart );
    inWords.pos--;
    inWords.data[inWords.pos] = ')'; //remove fake
  }

  // see if we have more to come
  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    //eat the extensions to this part
    if ( inWords[0] == '(' ) {
      parseSentence( inWords );
    } else {
      parseLiteral( inWords );
    }
  }

  if ( inWords[0] == ')' ) {
    inWords.pos++;
  }
  skipWS( inWords );
  return localPart;
}

void imapParser::parseBody (parseString & inWords)
{
  // see if we got a part specifier
  if ( inWords[0] == '[' ) {
    QByteArray specifier;
    QByteArray label;
    inWords.pos++;

    specifier = parseOneWord( inWords, true );

    if ( inWords[0] == '(' ) {
      inWords.pos++;

      while ( !inWords.isEmpty() && inWords[0] != ')' ) {
        label = parseOneWord( inWords );
      }

      if ( inWords[0] == ')' ) {
        inWords.pos++;
      }
    }
    if ( inWords[0] == ']' ) {
      inWords.pos++;
    }
    skipWS( inWords );

    // parse the header
    if ( qstrncmp( specifier, "0", specifier.size() ) == 0 ) {
      mailHeader *envelope = 0;
      if ( lastHandled ) {
        envelope = lastHandled->getHeader();
      }

      if ( !envelope || seenUid.isEmpty() ) {
        kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
        // don't know where to put it, throw it away
        parseLiteral( inWords, true );
      } else {
        kDebug( 7116 ) << "imapParser::parseBody - reading" << envelope << seenUid.toLatin1();
        // fill it up with data
        QString theHeader = parseLiteral( inWords, true );
        mimeIOQString myIO;

        myIO.setString( theHeader );
        envelope->parseHeader( myIO );
      }
    } else if ( qstrncmp( specifier, "HEADER.FIELDS", specifier.size() ) == 0 ) {
      // BODY[HEADER.FIELDS(References)] {n}
      //kDebug( 7116 ) << "imapParser::parseBody - HEADER.FIELDS:"
      // << QCString(label.data(), label.size()+1);
      if ( qstrncmp( label, "REFERENCES", label.size() ) == 0 ) {
       mailHeader *envelope = 0;
       if ( lastHandled ) {
         envelope = lastHandled->getHeader();
       }

       if ( !envelope || seenUid.isEmpty() ) {
         kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
         // don't know where to put it, throw it away
         parseLiteral( inWords, true );
       } else {
         QByteArray references = parseLiteral( inWords, true );
         int start = references.indexOf( '<' );
         int end = references.lastIndexOf( '>' );
         if ( start < end ) {
           references = references.mid( start, end - start + 1 );
         }
         envelope->setReferences( references.simplified() );
       }
      } else { // not a header we care about throw it away
        parseLiteral( inWords, true );
      }
    } else {
      if ( specifier.contains( ".MIME" ) ) {
        mailHeader *envelope = new mailHeader;
        QString theHeader = parseLiteral( inWords, false );
        mimeIOQString myIO;
        myIO.setString( theHeader );
        envelope->parseHeader( myIO );
        if ( lastHandled ) {
          lastHandled->setHeader( envelope );
        }
        return;
      }
      // throw it away
      kDebug( 7116 ) << "imapParser::parseBody - discarding" << seenUid.toLatin1();
      parseLiteral( inWords, true );
    }
  } else { // no part specifier
    mailHeader *envelope = 0;
    if ( lastHandled ) {
      envelope = lastHandled->getHeader();
    }

    if ( !envelope || seenUid.isEmpty() ) {
      kDebug( 7116 ) << "imapParser::parseBody - discarding" << envelope << seenUid.toLatin1();
      // don't know where to put it, throw it away
      parseSentence( inWords );
    } else {
      kDebug( 7116 ) << "imapParser::parseBody - reading" << envelope << seenUid.toLatin1();
      // fill it up with data
      QString section;
      mimeHeader *body = parseBodyStructure( inWords, section, envelope );
      if ( body != envelope ) {
        delete body;
      }
    }
  }
}

void imapParser::parseFetch (ulong /* value */, parseString & inWords)
{
  if ( inWords[0] != '(' ) {
    return;
  }
  inWords.pos++;
  skipWS( inWords );

  delete lastHandled;
  lastHandled = 0;

  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    if ( inWords[0] == '(' ) {
      parseSentence( inWords );
    } else {
      const QByteArray word = parseLiteral( inWords, false, true );

      switch ( word[0] ) {
      case 'E':
        if ( word == "ENVELOPE" ) {
          mailHeader *envelope = 0;

          if ( lastHandled ) {
            envelope = lastHandled->getHeader();
          } else {
            lastHandled = new imapCache();
          }

          if ( envelope && !envelope->getMessageId().isEmpty() ) {
            // we have seen this one already
            // or don't know where to put it
            parseSentence( inWords );
          } else {
            envelope = parseEnvelope( inWords );
            if ( envelope ) {
              envelope->setPartSpecifier( seenUid + ".0" );
              lastHandled->setHeader( envelope );
              lastHandled->setUid( seenUid.toULong() );
            }
          }
        }
        break;

      case 'B':
        if ( word == "BODY" ) {
          parseBody( inWords );
        } else if ( word == "BODY[]" ) {
          // Do the same as with "RFC822"
          parseLiteral( inWords, true );
        } else if ( word == "BODYSTRUCTURE" ) {
          mailHeader *envelope = 0;

          if ( lastHandled ) {
            envelope = lastHandled->getHeader();
          }

          // fill it up with data
          QString section;
          mimeHeader *body = parseBodyStructure( inWords, section, envelope );
          QByteArray data;
          QDataStream stream( &data, QIODevice::WriteOnly );
          if ( body ) {
            body->serialize( stream );
          }
          parseRelay( data );
          delete body;
        }
        break;

      case 'U':
        if ( word == "UID" ) {
          seenUid = parseOneWord( inWords );
          mailHeader *envelope = 0;
          if ( lastHandled ) {
            envelope = lastHandled->getHeader();
          } else {
            lastHandled = new imapCache();
          }

          if ( seenUid.isEmpty() ) {
            // unknown what to do
            kDebug( 7116 ) << "imapParser::parseFetch - UID empty";
          } else {
            lastHandled->setUid( seenUid.toULong() );
          }
          if ( envelope ) {
            envelope->setPartSpecifier( seenUid );
          }
        }
        break;

      case 'R':
        if ( word == "RFC822.SIZE" ) {
          ulong size;
          parseOneNumber( inWords, size );

          if ( !lastHandled ) {
            lastHandled = new imapCache();
          }
          lastHandled->setSize( size );
        } else if ( word.startsWith( "RFC822" ) ) { //krazy:exclude=strings
          // might be RFC822 RFC822.TEXT RFC822.HEADER
          parseLiteral( inWords, true );
        }
        break;

      case 'I':
        if ( word == "INTERNALDATE" ) {
          const QByteArray date = parseOneWord( inWords );
          if ( !lastHandled ) {
            lastHandled = new imapCache();
          }
          lastHandled->setDate( date );
        }
        break;

      case 'F':
        if ( word == "FLAGS" ) {
          //kDebug( 7116 ) << "GOT FLAGS" << inWords.cstr();
          if ( !lastHandled ) {
            lastHandled = new imapCache();
          }
          lastHandled->setFlags( imapInfo::_flags( inWords.cstr() ) );
        }
        break;

      default:
        parseLiteral( inWords );
        break;
      }
    }
  }

  // see if we have more to come
  while ( !inWords.isEmpty() && inWords[0] != ')' ) {
    //eat the extensions to this part
    if ( inWords[0] == '(' ) {
      parseSentence( inWords );
    } else {
      parseLiteral( inWords );
    }
  }

  if ( inWords.isEmpty() || inWords[0] != ')' ) {
    return;
  }
  inWords.pos++;
  skipWS( inWords );
}

// default parser
void imapParser::parseSentence (parseString & inWords)
{
  bool first = true;
  int stack = 0;

  //find the first nesting parentheses

  while ( !inWords.isEmpty() && ( stack != 0 || first ) ) {
    first = false;
    skipWS( inWords );

    unsigned char ch = inWords[0];
    switch ( ch ) {
    case '(':
      inWords.pos++;
      ++stack;
      break;
    case ')':
      inWords.pos++;
      --stack;
      break;
    case '[':
      inWords.pos++;
      ++stack;
      break;
    case ']':
      inWords.pos++;
      --stack;
      break;
    default:
      parseLiteral( inWords );
      skipWS( inWords );
      break;
    }
  }
  skipWS( inWords );
}

void imapParser::parseRecent (ulong value, parseString & result)
{
  selectInfo.setRecent( value );
  result.pos = result.data.size();
}

void imapParser::parseNamespace (parseString & result)
{
  if ( result[0] != '(' ) {
    return;
  }

  QString delimEmpty;
  if ( namespaceToDelimiter.contains( QString() ) ) {
    delimEmpty = namespaceToDelimiter[QString()];
  }

  namespaceToDelimiter.clear();
  imapNamespaces.clear();

  // remember what section we're in (user, other users, shared)
  int ns = -1;
  bool personalAvailable = false;
  while ( !result.isEmpty() ) {
    if ( result[0] == '(' ) {
      result.pos++; // tie off (
      if ( result[0] == '(' ) {
        // new namespace section
        result.pos++; // tie off (
        ++ns;
      }
      // namespace prefix
      QString prefix = QString::fromLatin1( parseOneWord( result ) );
      // delimiter
      QString delim = QString::fromLatin1( parseOneWord( result ) );
      kDebug( 7116 ) << "imapParser::parseNamespace ns='" << prefix << "',delim='" << delim << "'";
      if ( ns == 0 ) {
        // at least one personal ns
        personalAvailable = true;
      }
      QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim;
      imapNamespaces.append( nsentry );
      if ( prefix.right( 1 ) == delim ) {
        // strip delimiter to get a correct entry for comparisons
        prefix.resize( prefix.length() );
      }
      namespaceToDelimiter[prefix] = delim;

      result.pos++; // tie off )
      skipWS( result );
    } else if ( result[0] == ')' ) {
      result.pos++; // tie off )
      skipWS( result );
    } else if ( result[0] == 'N' ) {
      // drop NIL
      ++ns;
      parseOneWord( result );
    } else {
      // drop whatever it is
      parseOneWord( result );
    }
  }
  if ( !delimEmpty.isEmpty() ) {
    // remember default delimiter
    namespaceToDelimiter[QString()] = delimEmpty;
    if ( !personalAvailable ) {
      // at least one personal ns would be nice
      kDebug( 7116 ) << "imapParser::parseNamespace - registering own personal ns";
      QString nsentry = "0==" + delimEmpty;
      imapNamespaces.append( nsentry );
    }
  }
}

int imapParser::parseLoop ()
{
  parseString result;

  if ( !parseReadLine( result.data ) ) {
    return -1;
  }

  //kDebug( 7116 ) << result.cstr(); // includes \n

  if ( result.data.isEmpty() ) {
    return 0;
  }
  if ( !sentQueue.count() ) {
    // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE
    kDebug( 7116 ) << "imapParser::parseLoop - unhandledResponse:" << result.cstr();
    unhandled << result.cstr();
  } else {
    CommandPtr current = sentQueue.at( 0 );
    switch ( result[0] ) {
    case '*':
      result.data.resize( result.data.size() - 2 );  // tie off CRLF
      parseUntagged( result );
      break;
    case '+':
      continuation = result.data;
      break;
    default:
      {
        QByteArray tag = parseLiteral( result );
        if ( current->id() == tag.data() ) {
          result.data.resize( result.data.size() - 2 );  // tie off CRLF
          QByteArray resultCode = parseLiteral( result ); //the result
          current->setResult( resultCode );
          current->setResultInfo( result.cstr() );
          current->setComplete();

          sentQueue.removeAll( current );
          completeQueue.append( current );
          if ( result.length() ) {
            parseResult( resultCode, result, current->command() );
          }
        } else {
          kDebug( 7116 ) << "imapParser::parseLoop - unknown tag '" << tag << "'";
          QByteArray cstr = tag + ' ' + result.cstr();
          result.data = cstr;
          result.pos = 0;
          result.data.resize( cstr.length() );
        }
      }
      break;
    }
  }
  return 1;
}

void
imapParser::parseRelay (const QByteArray & buffer)
{
  Q_UNUSED( buffer );
  qWarning( "imapParser::parseRelay - virtual function not reimplemented - data lost" );
}

void
imapParser::parseRelay (ulong len)
{
  Q_UNUSED( len );
  qWarning( "imapParser::parseRelay - virtual function not reimplemented - announcement lost" );
}

bool imapParser::parseRead (QByteArray & buffer, long len, long relay)
{
  Q_UNUSED( buffer );
  Q_UNUSED( len );
  Q_UNUSED( relay );
  qWarning( "imapParser::parseRead - virtual function not reimplemented - no data read" );
  return false;
}

bool imapParser::parseReadLine (QByteArray & buffer, long relay)
{
  Q_UNUSED( buffer );
  Q_UNUSED( relay );
  qWarning( "imapParser::parseReadLine - virtual function not reimplemented - no data read" );
  return false;
}

void
imapParser::parseWriteLine (const QString & str)
{
  Q_UNUSED( str );
  qWarning( "imapParser::parseWriteLine - virtual function not reimplemented - no data written" );
}

void
imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section,
                      QString & _type, QString & _uid, QString & _validity, QString & _info)
{
  QStringList parameters;

  _box = _url.path();
  kDebug( 7116 ) << "imapParser::parseURL" << _box;
  int paramStart = _box.indexOf( "/;" );
  if ( paramStart > -1 ) {
    QString paramString = _box.right( _box.length() - paramStart - 2 );
    parameters = paramString.split( ';', QString::SkipEmptyParts );  //split parameters
    _box.truncate( paramStart ); // strip parameters
  }
  // extract parameters
  for ( QStringList::ConstIterator it( parameters.constBegin() );
       it != parameters.constEnd(); ++it ) {
    QString temp = ( *it );

    // if we have a '/' separator we'll just nuke it
    int pt = temp.indexOf( '/' );
    if ( pt > 0 ) {
      temp.truncate( pt );
    }
    if ( temp.startsWith( QLatin1String( "section=" ), Qt::CaseInsensitive ) ) {
      _section = temp.right( temp.length() - 8 );
    } else if ( temp.startsWith( QLatin1String( "type=" ), Qt::CaseInsensitive ) ) {
      _type = temp.right( temp.length() - 5 );
    } else if ( temp.startsWith( QLatin1String( "uid=" ), Qt::CaseInsensitive ) ) {
      _uid = temp.right( temp.length() - 4 );
    } else if ( temp.startsWith( QLatin1String( "uidvalidity=" ), Qt::CaseInsensitive ) ) {
      _validity = temp.right( temp.length() - 12 );
    } else if ( temp.startsWith( QLatin1String( "info=" ), Qt::CaseInsensitive ) ) {
      _info = temp.right( temp.length() - 5 );
    }
  }
//  kDebug( 7116 ) << "URL: section=" << _section << ", type=" << _type << ", uid=" << _uid;
//  kDebug( 7116 ) << "URL: user()" << _url.user();
//  kDebug( 7116 ) << "URL: path()" << _url.path();
//  kDebug( 7116 ) << "URL: encodedPathAndQuery()" << _url.encodedPathAndQuery();

  if ( !_box.isEmpty() ) {
    // strip /
    if ( _box[0] == '/' ) {
      _box = _box.right( _box.length() - 1 );
    }
    if ( !_box.isEmpty() && _box[_box.length() - 1] == '/' ) {
      _box.truncate( _box.length() - 1 );
    }
  }
  kDebug( 7116 ) << "URL: box=" << _box << ", section=" << _section << ", type="
                 << _type << ", uid=" << _uid << ", validity=" << _validity
                 << ", info=" << _info;
}


QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) {

  if ( !inWords.isEmpty() && inWords[0] == '{' ) {
    QByteArray retVal;
    int runLen = inWords.find('}', 1);
    if ( runLen > 0 ) {
      bool proper;
      long runLenSave = runLen + 1;
      QByteArray tmpstr( runLen, '\0' );
      inWords.takeMidNoResize( tmpstr, 1, runLen - 1 );
      runLen = tmpstr.toULong( &proper );
      inWords.pos += runLenSave;
      if ( proper ) {
        //now get the literal from the server
        if ( relay ) {
          parseRelay( runLen );
        }
        QByteArray rv;
        parseRead( rv, runLen, relay ? runLen : 0 );
        rv.resize( qMax( runLen, rv.size() ) ); // what's the point?
        retVal = rv;
        inWords.clear();
        parseReadLine( inWords.data ); // must get more

        // no duplicate data transfers
        relay = false;
      } else {
        kDebug( 7116 ) << "imapParser::parseLiteral - error parsing {} -" /*<< strLen*/;
      }
    } else {
      inWords.clear();
      kDebug( 7116 ) << "imapParser::parseLiteral - error parsing unmatched {";
    }
    skipWS( inWords );
    return retVal;
  }
  return parseOneWord( inWords, stopAtBracket );
}

// does not know about literals ( {7} literal )
QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket)
{
  uint len = inWords.length();
  if ( len == 0 ) {
    return QByteArray();
  }

  if ( len > 0 && inWords[0] == '"' ) {
    unsigned int i = 1;
    bool quote = false;
    while ( i < len && ( inWords[i] != '"' || quote ) ) {
      if ( inWords[i] == '\\' ) {
        quote = !quote;
      } else {
        quote = false;
      }
      i++;
    }
    if ( i < len ) {
      QByteArray retVal;
      retVal.resize( i );
      inWords.pos++;
      inWords.takeLeftNoResize( retVal, i - 1 );
      len = i - 1;
      int offset = 0;
      for ( unsigned int j = 0; j < len; j++ ) {
        if ( retVal[j] == '\\' ) {
          offset++;
          j++;
        }
        retVal[j - offset] = retVal[j];
      }
      retVal.resize( len - offset );
      inWords.pos += i;
      skipWS( inWords );
      return retVal;
    } else {
      kDebug( 7116 ) << "imapParser::parseOneWord - error parsing unmatched \"";
      QByteArray retVal = inWords.cstr();
      inWords.clear();
      return retVal;
    }
  } else {
    // not quoted
    unsigned int i;
    // search for end
    for ( i = 0; i < len; ++i ) {
      char ch = inWords[i];
      if ( ch <= ' ' || ch == '(' || ch == ')' ||
           ( stopAtBracket && ( ch == '[' || ch == ']' ) ) ) {
        break;
      }
    }

    QByteArray retVal;
    retVal.resize( i );
    inWords.takeLeftNoResize( retVal, i );
    inWords.pos += i;

    if ( retVal == "NIL" ) {
      retVal.truncate( 0 );
    }
    skipWS( inWords );
    return retVal;
  }
}

bool imapParser::parseOneNumber (parseString & inWords, ulong & num)
{
  bool valid;
  num = parseOneWord( inWords, true ).toULong( &valid );
  return valid;
}

bool imapParser::hasCapability (const QString & cap)
{
  QString c = cap.toLower();
//  kDebug( 7116 ) << "imapParser::hasCapability - Looking for '" << cap << "'";
  for ( QStringList::ConstIterator it = imapCapabilities.constBegin();
       it != imapCapabilities.constEnd(); ++it ) {
//    kDebug( 7116 ) << "imapParser::hasCapability - Examining '" << ( *it ) << "'";
    if ( !( kasciistricmp( c.toLatin1(), ( *it ).toAscii() ) ) ) {
      return true;
    }
  }
  return false;
}

void imapParser::removeCapability (const QString & cap)
{
  imapCapabilities.removeAll( cap.toLower() );
}

QString imapParser::namespaceForBox( const QString & box )
{
  kDebug( 7116 ) << "imapParse::namespaceForBox" << box;
  QString myNamespace;
  if ( !box.isEmpty() ) {
    const QList<QString> list = namespaceToDelimiter.keys();
    QString cleanPrefix;
    for ( QList<QString>::ConstIterator it = list.begin(); it != list.end(); ++it ) {
      if ( !( *it ).isEmpty() && box.contains( *it ) ) {
        return ( *it );
      }
    }
  }
  return myNamespace;
}
