Logo Search packages:      
Sourcecode: filezilla version File versions

ftpcontrolsocket.cpp

#include "FileZilla.h"
#include "ftpcontrolsocket.h"
#include "transfersocket.h"
#include "directorylistingparser.h"
#include "directorycache.h"
#include "iothread.h"
#include <wx/regex.h>
#include "externalipresolver.h"
#include "servercapabilities.h"
#include "tlssocket.h"
#include "pathcache.h"
#include <algorithm>

#define LOGON_WELCOME   0
#define LOGON_AUTH_TLS  1
#define LOGON_AUTH_SSL  2
#define LOGON_LOGON           3
#define LOGON_SYST            4
#define LOGON_FEAT            5
#define LOGON_CLNT            6
#define LOGON_OPTSUTF8  7
#define LOGON_PBSZ            8
#define LOGON_PROT            9
#define LOGON_CUSTOMCOMMANDS 10
#define LOGON_DONE            11

BEGIN_EVENT_TABLE(CFtpControlSocket, CRealControlSocket)
EVT_FZ_EXTERNALIPRESOLVE(wxID_ANY, CFtpControlSocket::OnExternalIPAddress)
END_EVENT_TABLE();

CRawTransferOpData::CRawTransferOpData()
      : COpData(cmd_rawtransfer)
{
      bTriedPasv = bTriedActive = false;
      bPasv = true;
}

CFtpTransferOpData::CFtpTransferOpData()
{
      transferEndReason = successful;
      tranferCommandSent = false;
      resumeOffset = 0;
      binary = true;
}

CFtpFileTransferOpData::CFtpFileTransferOpData()
{
      pIOThread = 0;
      fileDidExist = true;
}

CFtpFileTransferOpData::~CFtpFileTransferOpData()
{
      if (pIOThread)
      {
            CIOThread *pThread = pIOThread;
            pIOThread = 0;
            pThread->Destroy();
            delete pThread;
      }
}

enum filetransferStates
{
      filetransfer_init = 0,
      filetransfer_waitcwd,
      filetransfer_waitlist,
      filetransfer_size,
      filetransfer_mdtm,
      filetransfer_resumetest,
      filetransfer_transfer,
      filetransfer_waittransfer,
      filetransfer_waitresumetest
};

enum rawtransferStates
{
      rawtransfer_init = 0,
      rawtransfer_type,
      rawtransfer_port_pasv,
      rawtransfer_rest,
      rawtransfer_transfer,
      rawtransfer_waitfinish,
      rawtransfer_waittransferpre,
      rawtransfer_waittransfer,
      rawtransfer_waitsocket
};

class CFtpLogonOpData : public COpData
{
public:
      CFtpLogonOpData()
            : COpData(cmd_connect)
      {
            logonSequencePos = 0;
            logonType = 0;
            nCommand = 0;

            waitChallenge = false;
            gotPassword = false;
            waitForAsyncRequest = false;
            gotFirstWelcomeLine = false;

            customCommandIndex = 0;

            for (int i = 0; i < LOGON_DONE; i++)
                  neededCommands[i] = 1;
      }

      virtual ~CFtpLogonOpData()
      {
      }

      int logonSequencePos;
      int logonType;
      int nCommand; // Next command to send in the current logon sequence

      wxString challenge; // Used for interactive logons
      bool waitChallenge;
      bool waitForAsyncRequest;
      bool gotPassword;
      bool gotFirstWelcomeLine;

      unsigned int customCommandIndex;

      int neededCommands[LOGON_DONE];
};

CFtpControlSocket::CFtpControlSocket(CFileZillaEnginePrivate *pEngine) : CRealControlSocket(pEngine)
{
      m_pIPResolver = 0;
      m_pTransferSocket = 0;
      m_sentRestartOffset = false;
      m_bufferLen = 0;
      m_repliesToSkip = 0;
      m_pendingReplies = 1;
      m_pTlsSocket = 0;
      m_protectDataChannel = false;
      m_lastTypeBinary = -1;
}

CFtpControlSocket::~CFtpControlSocket()
{
}

void CFtpControlSocket::OnReceive()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::OnReceive()"));

      m_pBackend->Read(m_receiveBuffer + m_bufferLen, RECVBUFFERSIZE - m_bufferLen);
      
      if (m_pBackend->Error())
      {
            if (m_pBackend->LastError() != wxSOCKET_WOULDBLOCK)
            {
                  LogMessage(::Error, _("Disconnected from server"));
                  DoClose();
            }
            return;
      }

      int numread = m_pBackend->LastCount();
      if (!numread)
            return;

      m_pEngine->SetActive(true);

      char* start = m_receiveBuffer;
      m_bufferLen += numread;

      for (int i = start - m_receiveBuffer; i < m_bufferLen; i++)
      {
            char& p = m_receiveBuffer[i];
            if (p == '\r' ||
                  p == '\n' ||
                  p == 0)
            {
                  int len = i - (start - m_receiveBuffer);
                  if (!len)
                  {
                        start++;
                        continue;
                  }

                  if (len > MAXLINELEN)
                        len = MAXLINELEN;
                  p = 0;
                  wxString line = ConvToLocal(start);
                  start = m_receiveBuffer + i + 1;

                  ParseLine(line);

                  // Abort if connection got closed
                  if (!m_pCurrentServer)
                        return;
            }
      }
      memmove(m_receiveBuffer, start, m_bufferLen - (start - m_receiveBuffer));
      m_bufferLen -= (start -m_receiveBuffer);
      if (m_bufferLen > MAXLINELEN)
            m_bufferLen = MAXLINELEN;
}

void CFtpControlSocket::ParseLine(wxString line)
{
      LogMessageRaw(Response, line);
      SetAlive();

      if (m_pCurOpData && m_pCurOpData->opId == cmd_connect)
      {
            CFtpLogonOpData* pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData);
            if (pData->waitChallenge)
            {
                  wxString& challenge = pData->challenge;
                  if (challenge != _T(""))
#ifdef __WXMSW__
                        challenge += _T("\r\n");
#else
                        challenge += _T("\n");
#endif
                  challenge += line;
            }
            else if (pData->opState == LOGON_FEAT)
            {
                  wxString up = line.Upper();
                  if (up == _T(" UTF8"))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, yes);
                  else if (up == _T(" CLNT") || up.Left(6) == _T(" CLNT ")) 
                        CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, yes);
                  else if (up == _T(" MLSD") || up.Left(6) == _T(" MLSD "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes);
                  else if (up == _T(" MLST") || up.Left(6) == _T(" MLST "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes);
                  else if (up == _T(" MODE Z") || up.Left(6) == _T(" MODE Z "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, mode_z_support, yes);
                  else if (up == _T(" MFMT") || up.Left(6) == _T(" MFMT "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, mfmt_command, yes);
                  else if (up == _T(" PRET") || up.Left(6) == _T(" PRET "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, pret_command, yes);
                  else if (up == _T(" MDTM") || up.Left(6) == _T(" MDTM "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, mdtm_command, yes);
                  else if (up == _T(" SIZE") || up.Left(6) == _T(" SIZE "))
                        CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes);
            }
            else if (pData->opState == LOGON_WELCOME)
            {
                  if (!pData->gotFirstWelcomeLine)
                  {
                        if (line.Upper().Left(3) == _T("SSH"))
                        {
                              LogMessage(::Error, _("Cannot establish FTP connection to an SFTP server. Please select proper protocol."));
                              DoClose(FZ_REPLY_CRITICALERROR);
                              return;
                        }
                        pData->gotFirstWelcomeLine = true;
                  }
            }
      }
      //Check for multi-line responses
      if (line.Len() > 3)
      {
            if (m_MultilineResponseCode != _T(""))
            {
                  if (line.Left(4) == m_MultilineResponseCode)
                  {
                        // end of multi-line found
                        m_MultilineResponseCode.Clear();
                        m_Response = line;
                        ParseResponse();
                        m_Response = _T("");
                  }
            }
            // start of new multi-line
            else if (line.GetChar(3) == '-')
            {
                  // DDD<SP> is the end of a multi-line response
                  m_MultilineResponseCode = line.Left(3) + _T(" ");
            }
            else
            {
                  m_Response = line;
                  ParseResponse();
                  m_Response = _T("");
            }
      }
}

void CFtpControlSocket::OnConnect()
{
      m_lastTypeBinary = -1;

      SetAlive();
      if (m_pCurrentServer->GetProtocol() == FTPS)
      {
            if (!m_pTlsSocket)
            {
                  LogMessage(Status, _("Connection established, initializing TLS..."));

                  wxASSERT(!m_pTlsSocket);
                  delete m_pBackend;
                  m_pTlsSocket = new CTlsSocket(this, this, this);
                  m_pBackend = m_pTlsSocket;

                  if (!m_pTlsSocket->Init())
                  {
                        LogMessage(::Error, _("Failed to initialize TLS."));
                        DoClose();
                        return;
                  }

                  int res = m_pTlsSocket->Handshake();
                  if (res == FZ_REPLY_ERROR)
                        DoClose();

                  return;
            }
            else
                  LogMessage(Status, _("TLS/SSL connection established, waiting for welcome message..."));
      }
      else if (m_pCurrentServer->GetProtocol() == FTPES && m_pTlsSocket)
      {
            LogMessage(Status, _("TLS/SSL connection established."));
            return;
      }
      else
            LogMessage(Status, _("Connection established, waiting for welcome message..."));
      m_pendingReplies = 1;
      m_repliesToSkip = 0;
      Logon();
}

void CFtpControlSocket::ParseResponse()
{
      if (m_Response[0] != '1')
      {
            if (m_pendingReplies > 0)
                  m_pendingReplies--;
            else
            {
                  LogMessage(Debug_Warning, _T("Unexpected reply, no reply was pending."));
                  return;
            }
      }

      if (m_repliesToSkip)
      {
            LogMessage(Debug_Info, _T("Skipping reply after cancelled operation."));
            if (m_Response[0] != '1')
                  m_repliesToSkip--;
            return;
      }

      enum Command commandId = GetCurrentCommandId();
      switch (commandId)
      {
      case cmd_connect:
            LogonParseResponse();
            break;
      case cmd_list:
            ListParseResponse();
            break;
      case cmd_cwd:
            ChangeDirParseResponse();
            break;
      case cmd_transfer:
            FileTransferParseResponse();
            break;
      case cmd_raw:
            RawCommandParseResponse();
            break;
      case cmd_delete:
            DeleteParseResponse();
            break;
      case cmd_removedir:
            RemoveDirParseResponse();
            break;
      case cmd_mkdir:
            MkdirParseResponse();
            break;
      case cmd_rename:
            RenameParseResponse();
            break;
      case cmd_chmod:
            ChmodParseResponse();
            break;
      case cmd_rawtransfer:
            TransferParseResponse();
            break;
      case cmd_none:
            LogMessage(Debug_Verbose, _T("Out-of-order reply, ignoring."));
            break;
      default:
            LogMessage(Debug_Warning, _T("No action for parsing replies to command %d"), (int)commandId);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            break;
      }
}

const int LO = -2, ER = -1;
const int NUMLOGIN = 9; // currently supports 9 different login sequences
int logonseq[NUMLOGIN][20] = {
      // this array stores all of the logon sequences for the various firewalls
      // in blocks of 3 nums. 1st num is command to send, 2nd num is next point in logon sequence array
      // if 200 series response is rec'd from server as the result of the command, 3rd num is next
      // point in logon sequence if 300 series rec'd
      {0,LO,3, 1,LO,6, 12,LO,ER}, // no firewall
      {3,6,3,  4,6,ER, 5,9,9, 0,LO,12, 1,LO,ER}, // SITE hostname
      {3,6,3,  4,6,ER, 6,LO,9, 1,LO,ER}, // USER after logon
      {7,3,3,  0,LO,6, 1,LO,ER}, //proxy OPEN
      {3,6,3,  4,6,ER, 0,LO,9, 1,LO,ER}, // Transparent
      {6,LO,3, 1,LO,ER}, // USER remoteID@remotehost
      {8,6,3,  4,6,ER, 0,LO,9, 1,LO,ER}, //USER fireID@remotehost
      {9,ER,3, 1,LO,6, 2,LO,ER}, //USER remoteID@remotehost fireID
      {10,LO,3,11,LO,6,2,LO,ER} // USER remoteID@fireID@remotehost
};

int CFtpControlSocket::Logon()
{
      const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType();
      if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != no)
            m_useUTF8 = true;
      else if (encoding == ENCODING_UTF8)
            m_useUTF8 = true;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::LogonParseResponse()
{
      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("LogonParseResponse without m_pCurOpData called"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_INTERNALERROR;
      }

      CFtpLogonOpData *pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData);

      int code = GetReplyCode();

      if (pData->opState == LOGON_WELCOME)
      {
            if (code != 2 && code != 3)
            {
                  DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0);
                  return FZ_REPLY_DISCONNECTED;
            }
      }
      else if (pData->opState == LOGON_AUTH_TLS ||
                   pData->opState == LOGON_AUTH_SSL)
      {
            if (code != 2 && code != 3)
            {
                  if (pData->opState == LOGON_AUTH_SSL)
                  {
                        DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0);
                        return FZ_REPLY_DISCONNECTED;
                        return false;
                  }
            }
            else
            {
                  LogMessage(Status, _("Initializing TLS..."));

                  wxASSERT(!m_pTlsSocket);
                  delete m_pBackend;
                  
                  m_pTlsSocket = new CTlsSocket(this, this, this);
                  m_pBackend = m_pTlsSocket;

                  if (!m_pTlsSocket->Init())
                  {
                        LogMessage(::Error, _("Failed to initialize TLS."));
                        DoClose(FZ_REPLY_INTERNALERROR);
                        return FZ_REPLY_ERROR;
                  }

                  int res = m_pTlsSocket->Handshake();
                  if (res == FZ_REPLY_ERROR)
                  {
                        DoClose();
                        return FZ_REPLY_ERROR;
                  }

                  pData->neededCommands[LOGON_AUTH_SSL] = 0;
            }
      }
      else if (pData->opState == LOGON_LOGON)
      {
            if (code != 2 && code != 3)
            {
                  if (m_pCurrentServer->GetEncodingType() == ENCODING_AUTO && m_useUTF8)
                  {
                        // Fall back to local charset for the case that the server might not
                        // support UTF8 and the login data contains non-ascii characters.
                        bool asciiOnly = true;
                        for (unsigned int i = 0; i < m_pCurrentServer->GetUser().Length(); i++)
                              if ((unsigned int)m_pCurrentServer->GetUser()[i] > 127)
                                    asciiOnly = false;
                        for (unsigned int i = 0; i < m_pCurrentServer->GetPass().Length(); i++)
                              if ((unsigned int)m_pCurrentServer->GetPass()[i] > 127)
                                    asciiOnly = false;
                        for (unsigned int i = 0; i < m_pCurrentServer->GetAccount().Length(); i++)
                              if ((unsigned int)m_pCurrentServer->GetAccount()[i] > 127)
                                    asciiOnly = false;
                        if (!asciiOnly)
                        {
                              LogMessage(Status, _("Login data contains non-ascii characters and server might not be UTF-8 aware. Trying local charset."), 0);
                              m_useUTF8 = false;
                              pData->nCommand = logonseq[pData->logonType][0];
                              pData->logonSequencePos = 0;
                              return LogonSend();
                        }
                  }

                  int error = FZ_REPLY_DISCONNECTED;
                  if (pData->nCommand == 1 && code == 5)
                        error |= FZ_REPLY_PASSWORDFAILED;
                  DoClose(error);
                  return FZ_REPLY_ERROR;
            }

            pData->waitChallenge = false;
            pData->logonSequencePos = logonseq[pData->logonType][pData->logonSequencePos + code - 1];

            switch(pData->logonSequencePos)
            {
            case ER: // ER means something has gone wrong
                  DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0);
                  return FZ_REPLY_ERROR;
            case LO: //LO means we are logged on
                  break;
            default:
                  pData->nCommand = logonseq[pData->logonType][pData->logonSequencePos];
                  return LogonSend();
            }
      }
      else if (pData->opState == LOGON_SYST)
      {
            if (code == 2)
                  CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, yes, m_Response.Mid(4));
            else
                  CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, no);

            if (code == 2 && m_Response.Length() > 7 && m_Response.Mid(3, 4) == _T(" MVS"))
            {
                  if (m_pCurrentServer->GetType() == DEFAULT)
                        m_pCurrentServer->SetType(MVS);
            }

            if (m_Response.Find(_T("FileZilla")) != -1)
            {
                  pData->neededCommands[LOGON_CLNT] = 0;
                  pData->neededCommands[LOGON_OPTSUTF8] = 0;
            }
      }
      else if (pData->opState == LOGON_FEAT)
      {
            if (code == 2)
            {
                  CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, yes);
                  if (CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes)
                        CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, no);
                  if (CServerCapabilities::GetCapability(*m_pCurrentServer, clnt_command) != yes)
                        CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, no);
            }
            else
                  CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, no);

            const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType();
            if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes)
                  m_useUTF8 = false;
      }
      /*
      else if (pData->opState == LOGON_CLNT)
      {
            // Don't check return code, it has no meaning for us
      }
      else if (pData->opState == LOGON_OPTSUTF8)
      {
            // If server obeys RFC 2640 this command had no effect, return code
            // is irrelevant
      }
      else if (pData->opState == LOGON_PBSZ)
      {
            // Nothing to do
      }
      */
      else if (pData->opState == LOGON_PROT)
      {
            if (code == 2 || code == 3)
                  m_protectDataChannel = true;
      }
      else if (pData->opState == LOGON_CUSTOMCOMMANDS)
      {
            pData->customCommandIndex++;
            if (pData->customCommandIndex < m_pCurrentServer->GetPostLoginCommands().size())
                  return LogonSend();
      }

      while (true)
      {
            pData->opState++;

            if (pData->opState == LOGON_DONE)
            {
                  LogMessage(Status, _("Connected"));
                  ResetOperation(FZ_REPLY_OK);
                  return true;
            }

            if (!pData->neededCommands[pData->opState])
                  continue;
            else if (pData->opState == LOGON_SYST)
            {
                  wxString system;
                  enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), syst_command, &system);
                  if (cap == unknown)
                        break;
                  else if (cap == yes)
                  {
                        if (system.Left(3) == _T("MVS") && m_pCurrentServer->GetType() == DEFAULT)
                              m_pCurrentServer->SetType(MVS);

                        if (system.Find(_T("FileZilla")) != -1)
                        {
                              pData->neededCommands[LOGON_CLNT] = 0;
                              pData->neededCommands[LOGON_OPTSUTF8] = 0;
                        }
                  }
            }
            else if (pData->opState == LOGON_FEAT)
            {
                  enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), feat_command);
                  if (cap == unknown)
                        break;
            }
            else if (pData->opState == LOGON_CLNT)
            {
                  if (!m_useUTF8)
                        continue;

                  if (CServerCapabilities::GetCapability(*GetCurrentServer(), clnt_command) == yes)
                        break;
            }
            else if (pData->opState == LOGON_OPTSUTF8)
            {
                  if (!m_useUTF8)
                        continue;

                  if (CServerCapabilities::GetCapability(*GetCurrentServer(), utf8_command) == yes)
                        break;
            }
            else
                  break;
      }

      return LogonSend();
}

int CFtpControlSocket::LogonSend()
{
      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("LogonParseResponse without m_pCurOpData called"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_INTERNALERROR;
      }

      CFtpLogonOpData *pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData);

      bool res;
      switch (pData->opState)
      {
      case LOGON_AUTH_TLS:
            res = Send(_T("AUTH TLS"));
            break;
      case LOGON_AUTH_SSL:
            res = Send(_T("AUTH SSL"));
            break;
      case LOGON_SYST:
            res = Send(_T("SYST"));
            break;
      case LOGON_LOGON:
            switch (pData->nCommand)
            {
            case 0:
                  res = Send(_T("USER ") + m_pCurrentServer->GetUser());

                  if (m_pCurrentServer->GetLogonType() == INTERACTIVE)
                  {
                        pData->waitChallenge = true;
                        pData->challenge = _T("");
                  }
                  break;
            case 1:
                  if (pData->challenge != _T(""))
                  {
                        CInteractiveLoginNotification *pNotification = new CInteractiveLoginNotification(pData->challenge);
                        pNotification->server = *m_pCurrentServer;
                        pNotification->requestNumber = m_pEngine->GetNextAsyncRequestNumber();
                        pData->waitForAsyncRequest = true;
                        pData->challenge = _T("");
                        m_pEngine->AddNotification(pNotification);

                        return FZ_REPLY_WOULDBLOCK;
                  }

                  res = Send(_T("PASS ") + m_pCurrentServer->GetPass(), true);
                  break;
            case 12:
                  if (m_pCurrentServer->GetAccount() == _T(""))
                  {
                        LogMessage(::Error, _("Server requires an account. Please specify an account using the Site Manager"));
                        DoClose(FZ_REPLY_DISCONNECTED);
                        res = false;
                        break;
                  }

                  res = Send(_T("ACCT ") + m_pCurrentServer->GetAccount());
                  break;

            default:
                  ResetOperation(FZ_REPLY_INTERNALERROR);
                  res = false;
                  break;
            }
            break;
      case LOGON_FEAT:
            res = Send(_T("FEAT"));
            break;
      case LOGON_CLNT:
            // Some servers refuse to enable UTF8 if client does not send CLNT command
            // to fix compatibility with Internet Explorer, but in the process breaking
            // compatibility with other clients.
            // Rather than forcing MS to fix Internet Explorer, letting other clients
            // suffer is a questionable decision in my opinion.
            res = Send(_T("CLNT FileZilla"));
            break;
      case LOGON_OPTSUTF8:
            // Handle servers that disobey RFC 2640 by having UTF8 in their FEAT
            // response but do not use UTF8 unless OPTS UTF8 ON gets send.
            // However these servers obey a conflicting ietf draft:
            // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt
            // Example servers are, amongst others, G6 FTP Server and RaidenFTPd.
            res = Send(_T("OPTS UTF8 ON"));
            break;
      case LOGON_PBSZ:
            res = Send(_T("PBSZ 0"));
            break;
      case LOGON_PROT:
            res = Send(_T("PROT P"));
            break;
      case LOGON_CUSTOMCOMMANDS:
            if (pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size())
            {
                  LogMessage(Debug_Warning, _T("pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size()"));
                  DoClose(FZ_REPLY_INTERNALERROR);
                  return FZ_REPLY_ERROR;
            }
            res = Send(m_pCurrentServer->GetPostLoginCommands()[pData->customCommandIndex]);
      default:
            return FZ_REPLY_ERROR;
      }

      if (!res)
            return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::GetReplyCode() const
{
      if (m_Response == _T(""))
            return 0;

      if (m_Response[0] < '0' || m_Response[0] > '9')
            return 0;

      return m_Response[0] - '0';
}

bool CFtpControlSocket::Send(wxString str, bool maskArgs /*=false*/)
{
      int pos;
      if (maskArgs && (pos = str.Find(_T(" "))) != -1)
      {
            wxString stars('*', str.Length() - pos - 1);
            LogMessageRaw(Command, str.Left(pos + 1) + stars);
      }
      else
            LogMessageRaw(Command, str);

      str += _T("\r\n");
      wxCharBuffer buffer = ConvToServer(str);
      if (!buffer)
      {
            LogMessage(::Error, _T("Failed to convert command to 8 bit charset"));
            return false;
      }
      unsigned int len = (unsigned int)strlen(buffer);
      bool res = CRealControlSocket::Send(buffer, len);
      if (res)
            m_pendingReplies++;
      return res;
}

class CFtpListOpData : public COpData, public CFtpTransferOpData
{
public:
      CFtpListOpData()
            : COpData(cmd_list)
      {
            viewHiddenCheck = false;
            viewHidden = false;
            m_pDirectoryListingParser = 0;
      }

      virtual ~CFtpListOpData()
      {
            delete m_pDirectoryListingParser;
      }

      CServerPath path;
      wxString subDir;

      CDirectoryListingParser* m_pDirectoryListingParser;
      
      CDirectoryListing directoryListing;

      // Set to true to get a directory listing even if a cache
      // lookup can be made after finding out true remote directory
      bool refresh;

      bool viewHiddenCheck;
      bool viewHidden; // Uses LIST -a command
};

enum listStates
{
      list_init = 0,
      list_waitcwd,
      list_waittransfer,
};

int CFtpControlSocket::List(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, bool refresh /*=false*/)
{
      LogMessage(Status, _("Retrieving directory listing..."));

      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("List called from other command"));
      }
      CFtpListOpData *pData = new CFtpListOpData;
      pData->pNextOpData = m_pCurOpData;
      m_pCurOpData = pData;

      pData->opState = list_waitcwd;

      if (path.GetType() == DEFAULT)
            path.SetType(m_pCurrentServer->GetType());
      pData->path = path;
      pData->subDir = subDir;
      pData->refresh = refresh;

      int res = ChangeDir(path, subDir);
      if (res != FZ_REPLY_OK)
            return res;

      if (!pData->refresh)
      {
            wxASSERT(!pData->pNextOpData);

            // Do a cache lookup now that we know the correct directory
            CDirectoryCache cache;

            int hasUnsureEntries;
            bool found = cache.DoesExist(*m_pCurrentServer, m_CurrentPath, _T(""), hasUnsureEntries);
            if (found)
            {
                  if (!pData->path.IsEmpty() && pData->subDir != _T(""))
                        cache.AddParent(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);

                  m_pEngine->SendDirectoryListingNotification(m_CurrentPath, true, false, false);
                  ResetOperation(FZ_REPLY_OK);

                  return FZ_REPLY_OK;
            }
      }

      // Try to lock cache
      if (!TryLockCache(m_CurrentPath))
            return FZ_REPLY_WOULDBLOCK;

      delete m_pTransferSocket;
      m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list);
      pData->m_pDirectoryListingParser = new CDirectoryListingParser(this, *m_pCurrentServer);
      m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;

      InitTransferStatus(-1, 0, true);

      pData->opState = list_waittransfer;
#if 0 // Disabled for now
      if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command) == yes)
            return Transfer(_T("MLSD"), pData);
      else
#endif
      {
            if (m_pEngine->GetOptions()->GetOptionVal(OPTION_VIEW_HIDDEN_FILES))
            {
                  enum capabilities cap = CServerCapabilities::GetCapability(*m_pCurrentServer, list_hidden_support);
                  if (cap == unknown)
                        pData->viewHiddenCheck = true;
                  else if (cap == yes)
                        pData->viewHidden = true;
                  else
                        LogMessage(Debug_Info, _("View hidden option set, but unsupported by server"));
            }

            if (pData->viewHidden)
                  return Transfer(_T("LIST -a"), pData);
            else
                  return Transfer(_T("LIST"), pData);
      }
}

int CFtpControlSocket::ListSend(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::ListSend(%d)"), prevResult);

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData);
      LogMessage(Debug_Debug, _T("  state = %d"), pData->opState);

      if (pData->opState == list_waitcwd)
      {
            if (prevResult != FZ_REPLY_OK)
            {
                  ResetOperation(prevResult);
                  return FZ_REPLY_ERROR;
            }

            if (!pData->refresh)
            {
                  wxASSERT(!pData->pNextOpData);

                  // Do a cache lookup now that we know the correct directory
                  CDirectoryCache cache;
                  int hasUnsureEntries;
                  bool found = cache.DoesExist(*m_pCurrentServer, m_CurrentPath, _T(""), hasUnsureEntries);
                  if (found)
                  {
                        if (!pData->path.IsEmpty() && pData->subDir != _T(""))
                              cache.AddParent(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);

                        // Continue with refresh if listing has unsure entries
                        if (!hasUnsureEntries)
                        {
                              m_pEngine->SendDirectoryListingNotification(m_CurrentPath, true, false, false);

                              ResetOperation(FZ_REPLY_OK);

                              return FZ_REPLY_OK;
                        }
                  }
            }

            if (!HasLock())
            {
                  if (!TryLockCache(m_CurrentPath))
                        return FZ_REPLY_WOULDBLOCK;
            }

            delete m_pTransferSocket;
            m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list);
            pData->m_pDirectoryListingParser = new CDirectoryListingParser(this, *m_pCurrentServer);
            m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;

            InitTransferStatus(-1, 0, true);

            pData->opState = list_waittransfer;
#if 0 // Disabled for now
            if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command) == yes)
                  return Transfer(_T("MLSD"), pData);
            else
#endif
            {
                  if (m_pEngine->GetOptions()->GetOptionVal(OPTION_VIEW_HIDDEN_FILES))
                  {
                        enum capabilities cap = CServerCapabilities::GetCapability(*m_pCurrentServer, list_hidden_support);
                        if (cap == unknown)
                              pData->viewHiddenCheck = true;
                        else if (cap == yes)
                              pData->viewHidden = true;
                        else
                              LogMessage(Debug_Info, _("View hidden option set, but unsupported by server"));
                  }

                  if (pData->viewHidden)
                        return Transfer(_T("LIST -a"), pData);
                  else
                        return Transfer(_T("LIST"), pData);
            }
      }
      else if (pData->opState == list_waittransfer)
      {
            if (prevResult == FZ_REPLY_OK)
            {
                  CDirectoryListing listing = pData->m_pDirectoryListingParser->Parse(m_CurrentPath);

                  if (pData->viewHiddenCheck)
                  {
                        if (!pData->viewHidden)
                        {
                              // Repeat with LIST -a
                              pData->viewHidden = true;
                              pData->directoryListing = listing;

                              // Reset status
                              pData->transferEndReason = successful;
                              pData->tranferCommandSent = false;
                              delete m_pTransferSocket;
                              m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list);
                              pData->m_pDirectoryListingParser->Reset();
                              m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;

                              return Transfer(_T("LIST -a"), pData);
                        }
                        else
                        {
                              if (CheckInclusion(listing, pData->directoryListing))
                              {
                                    LogMessage(Debug_Info, _T("Server seems to support LIST -a"));
                                    CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes);
                              }
                              else
                              {
                                    LogMessage(Debug_Info, _T("Server does not seem to support LIST -a"));
                                    CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);
                                    listing = pData->directoryListing;
                              }
                        }
                  }
                  
                  SetAlive();

                  CDirectoryCache cache;
                  cache.Store(listing, *m_pCurrentServer, pData->path, pData->subDir);
                  
                  m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);

                  ResetOperation(FZ_REPLY_OK);
                  return FZ_REPLY_OK;
            }
            else
            {
                  if (pData->tranferCommandSent && IsMisleadingListResponse())
                  {
                        if (pData->viewHiddenCheck)
                        {
                              if (pData->viewHidden)
                              {
                                    if (pData->directoryListing.GetCount())
                                    {
                                          // Less files with LIST -a
                                          // Not supported
                                          LogMessage(Debug_Info, _T("Server does not seem to support LIST -a"));
                                          CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);
                                    }
                                    else
                                    {
                                          LogMessage(Debug_Info, _T("Server seems to support LIST -a"));
                                          CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes);
                                    }
                              }
                              else
                              {
                                    // Reset status
                                    pData->transferEndReason = successful;
                                    pData->tranferCommandSent = false;
                                    delete m_pTransferSocket;
                                    m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list);
                                    pData->m_pDirectoryListingParser->Reset();
                                    m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;

                                    // Repeat with LIST -a
                                    pData->viewHidden = true;
                                    return Transfer(_T("LIST -a"), pData);
                              }
                        }
                        
                        CDirectoryCache cache;
                        cache.Store(pData->directoryListing, *m_pCurrentServer, pData->path, pData->subDir);

                        m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);

                        ResetOperation(FZ_REPLY_OK);
                        return FZ_REPLY_OK;
                  }
                  else
                  {
                        if (pData->viewHiddenCheck)
                        {
                              // If server does not support LIST -a, the server might reject this command
                              // straight away. In this case, back to the previously retrieved listing.
                              // On other failures like timeouts and such, return an error
                              if (pData->viewHidden &&
                                    pData->transferEndReason == transfer_command_failure_immediate)
                              {
                                    CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);

                                    CDirectoryCache cache;
                                    cache.Store(pData->directoryListing, *m_pCurrentServer, pData->path, pData->subDir);

                                    m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);

                                    ResetOperation(FZ_REPLY_OK);
                                    return FZ_REPLY_OK;
                              }
                        }

                        if (prevResult & FZ_REPLY_ERROR)
                              m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, true);
                  }

                  ResetOperation(FZ_REPLY_ERROR);
                  return FZ_REPLY_ERROR;
            }
      }

      LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("invalid opstate"));
      ResetOperation(FZ_REPLY_INTERNALERROR);
      return FZ_REPLY_ERROR;
}

int CFtpControlSocket::ListParseResponse()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::ListParseResponse()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      LogMessage(Debug_Warning, _T("ListParseResponse should never be called"));
      ResetOperation(FZ_REPLY_INTERNALERROR);
      return FZ_REPLY_ERROR;
}

int CFtpControlSocket::ResetOperation(int nErrorCode)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::ResetOperation(%d)"), nErrorCode);

      CTransferSocket* pTransferSocket = m_pTransferSocket;
      m_pTransferSocket = 0;
      delete pTransferSocket;
      delete m_pIPResolver;
      m_pIPResolver = 0;

      m_repliesToSkip = m_pendingReplies;

      if (m_pCurOpData && m_pCurOpData->opId == cmd_transfer)
      {
            CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
            if (pData->tranferCommandSent)
            {
                  if (pData->transferEndReason != transfer_command_failure_immediate || GetReplyCode() != 5)
                        pData->transferInitiated = true;
                  else
                  {
                        if (nErrorCode == FZ_REPLY_ERROR)
                              nErrorCode |= FZ_REPLY_CRITICALERROR;
                  }
            }
            if (nErrorCode != FZ_REPLY_OK && pData->download && !pData->fileDidExist)
            {
                  wxStructStat stats;
                  if (!wxStat(pData->localFile, &stats) && !stats.st_size)
                  {
                        // Download failed and a new local file was created before, but
                        // nothing has been written to it. Remove it again, so we don't
                        // leave a bunch of empty files all over the place.
                        LogMessage(Debug_Verbose, _T("Deleting empty file"));
                        delete pData->pIOThread;
                        pData->pIOThread = 0;
                        wxRemoveFile(pData->localFile);
                  }
            }
      }

      if (m_pCurOpData && m_pCurOpData->opId == cmd_rawtransfer && 
            nErrorCode != FZ_REPLY_OK)
      {
            CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
            if (pData->pOldData->transferEndReason == successful)
            {
                  if ((nErrorCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
                        pData->pOldData->transferEndReason = timeout;
                  else if (!pData->pOldData->tranferCommandSent)
                        pData->pOldData->transferEndReason = pre_transfer_command_failure;
                  else
                        pData->pOldData->transferEndReason = failure;
            }
      }

      return CControlSocket::ResetOperation(nErrorCode);
}

int CFtpControlSocket::SendNextCommand(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::SendNextCommand(%d)"), prevResult);
      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("SendNextCommand called without active operation"));
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      if (m_pCurOpData->waitForAsyncRequest)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Waiting for async request, ignoring SendNextCommand"));
            return FZ_REPLY_WOULDBLOCK;
      }

      switch (m_pCurOpData->opId)
      {
      case cmd_list:
            return ListSend(prevResult);
      case cmd_connect:
            return LogonSend();
      case cmd_cwd:
            return ChangeDirSend();
      case cmd_transfer:
            return FileTransferSend(prevResult);
      case cmd_mkdir:
            return MkdirSend();
      case cmd_rename:
            return RenameSend(prevResult);
      case cmd_chmod:
            return ChmodSend(prevResult);
      case cmd_rawtransfer:
            return TransferSend(prevResult);
      case cmd_raw:
            return RawCommandSend();
      case cmd_delete:
            return DeleteSend(prevResult);
      case cmd_removedir:
            return RemoveDirSend(prevResult);
      default:
            LogMessage(__TFILE__, __LINE__, this, ::Debug_Warning, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            break;
      }

      return FZ_REPLY_ERROR;
}

class CFtpChangeDirOpData : public CChangeDirOpData
{
public:
      CFtpChangeDirOpData()
      {
            tried_cdup = false;
      }

      virtual ~CFtpChangeDirOpData()
      {
      }

      bool tried_cdup;
};

enum cwdStates
{
      cwd_init = 0,
      cwd_pwd,
      cwd_cwd,
      cwd_pwd_cwd,
      cwd_cwd_subdir,
      cwd_pwd_subdir
};

int CFtpControlSocket::ChangeDir(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/)
{
      enum cwdStates state = cwd_init;

      if (path.GetType() == DEFAULT)
            path.SetType(m_pCurrentServer->GetType());

      CServerPath target;
      if (path.IsEmpty())
      {
            if (m_CurrentPath.IsEmpty())
                  state = cwd_pwd;
            else
                  return FZ_REPLY_OK;
      }
      else
      {
            if (subDir != _T(""))
            {
                  // Check if the target is in cache already
                  CPathCache cache;
                  target = cache.Lookup(*m_pCurrentServer, path, subDir);
                  if (!target.IsEmpty())
                  {
                        if (m_CurrentPath == target)
                              return FZ_REPLY_OK;

                        path = target;
                        subDir = _T("");
                        state = cwd_cwd;
                  }
                  else
                  {
                        // Target unknown, check for the parent's target
                        target = cache.Lookup(*m_pCurrentServer, path, _T(""));
                        if (m_CurrentPath == path || (!target.IsEmpty() && target == m_CurrentPath))
                        {
                              target.Clear();
                              state = cwd_cwd_subdir;
                        }
                        else
                              state = cwd_cwd;
                  }
            }
            else
            {
                  CPathCache cache;
                  target = cache.Lookup(*m_pCurrentServer, path, _T(""));
                  if (m_CurrentPath == path || (!target.IsEmpty() && target == m_CurrentPath))
                        return FZ_REPLY_OK;
                  state = cwd_cwd;
            }
      }

      CFtpChangeDirOpData *pData = new CFtpChangeDirOpData;
      pData->pNextOpData = m_pCurOpData;
      pData->opState = state;
      pData->path = path;
      pData->subDir = subDir;
      pData->target = target;

      m_pCurOpData = pData;

      return SendNextCommand();
}

int CFtpControlSocket::ChangeDirParseResponse()
{
      if (!m_pCurOpData)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }
      CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData);

      int code = GetReplyCode();
      bool error = false;
      switch (pData->opState)
      {
      case cwd_pwd:
            if (code != 2 && code != 3)
                  error = true;
            else if (ParsePwdReply(m_Response))
            {
                  ResetOperation(FZ_REPLY_OK);
                  return FZ_REPLY_OK;
            }
            else
                  error = true;
            break;
      case cwd_cwd:
            if (code != 2 && code != 3)
            {
                  // Create remote directory if part of a file upload
                  if (pData->pNextOpData && pData->pNextOpData->opId == cmd_transfer &&
                        !static_cast<CFtpFileTransferOpData *>(pData->pNextOpData)->download && !pData->triedMkd)
                  {
                        pData->triedMkd = true;
                        int res = Mkdir(pData->path);
                        if (res != FZ_REPLY_OK)
                              return res;
                  }
                  else
                        error = true;
            }
            else
            {
                  if (pData->target.IsEmpty())
                        pData->opState = cwd_pwd_cwd;
                  else
                  {
                        m_CurrentPath = pData->target;
                        if (pData->subDir == _T(""))
                        {
                              ResetOperation(FZ_REPLY_OK);
                              return FZ_REPLY_OK;
                        }

                        pData->target.Clear();
                        pData->opState = cwd_cwd_subdir;
                  }
            }
            break;
      case cwd_pwd_cwd:
            if (code != 2 && code != 3)
            {
                  LogMessage(Debug_Warning, _T("PWD failed, assuming path is '%s'."), pData->path.GetPath().c_str());
                  m_CurrentPath = pData->path;

                  if (pData->target.IsEmpty())
                  {
                        CPathCache cache;
                        cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path);
                  }

                  if (pData->subDir == _T(""))
                  {
                        ResetOperation(FZ_REPLY_OK);
                        return FZ_REPLY_OK;
                  }
                  else
                        pData->opState = cwd_cwd_subdir;
            }
            else if (ParsePwdReply(m_Response, false, pData->path))
            {
                  if (pData->target.IsEmpty())
                  {
                        CPathCache cache;
                        cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path);
                  }
                  if (pData->subDir == _T(""))
                  {
                        ResetOperation(FZ_REPLY_OK);
                        return FZ_REPLY_OK;
                  }
                  else
                        pData->opState = cwd_cwd_subdir;
            }
            else
                  error = true;
            break;
      case cwd_cwd_subdir:
            if (code != 2 && code != 3)
            {
                  if (pData->subDir == _T("..") && !pData->tried_cdup && m_Response.Left(2) == _T("50"))
                  {
                        // CDUP command not implemented, try again using CWD ..
                        pData->tried_cdup = true;
                  }
                  else
                        error = true;
            }
            else
                  pData->opState = cwd_pwd_subdir;
            break;
      case cwd_pwd_subdir:
            {
                  CServerPath assumedPath = pData->path;
                  if (pData->subDir == _T(".."))
                  {
                        if (!assumedPath.HasParent())
                              assumedPath.Clear();
                        else
                              assumedPath = assumedPath.GetParent();
                  }
                  else
                        assumedPath.AddSegment(pData->subDir);

                  if (code != 2 && code != 3)
                  {
                        if (!assumedPath.IsEmpty())
                        {
                              LogMessage(Debug_Warning, _T("PWD failed, assuming path is '%s'."), assumedPath.GetPath().c_str());
                              m_CurrentPath = assumedPath;

                              if (pData->target.IsEmpty())
                              {
                                    CPathCache cache;
                                    cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);
                              }

                              ResetOperation(FZ_REPLY_OK);
                              return FZ_REPLY_OK;
                        }
                        else
                        {
                              LogMessage(Debug_Warning, _T("PWD failed, unable to guess current path."));
                              error = true;
                        }
                  }           
                  else if (ParsePwdReply(m_Response, false, assumedPath))
                  {
                        if (pData->target.IsEmpty())
                        {
                              CPathCache cache;
                              cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);
                        }

                        ResetOperation(FZ_REPLY_OK);
                        return FZ_REPLY_OK;
                  }
                  else
                        error = true;
            }
            break;
      }

      if (error)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return ChangeDirSend();
}

int CFtpControlSocket::ChangeDirSend()
{
      if (!m_pCurOpData)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }
      CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData);

      wxString cmd;
      switch (pData->opState)
      {
      case cwd_pwd:
      case cwd_pwd_cwd:
      case cwd_pwd_subdir:
            cmd = _T("PWD");
            break;
      case cwd_cwd:
            cmd = _T("CWD ") + pData->path.GetPath();
            m_CurrentPath.Clear();
            break;
      case cwd_cwd_subdir:
            if (pData->subDir == _T(""))
            {
                  ResetOperation(FZ_REPLY_INTERNALERROR);
                  return FZ_REPLY_ERROR;
            }
            else if (pData->subDir == _T("..") && !pData->tried_cdup)
                  cmd = _T("CDUP");
            else
                  cmd = _T("CWD ") + pData->subDir;
            m_CurrentPath.Clear();
            break;
      }

      if (cmd != _T(""))
            if (!Send(cmd))
                  return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath,
                                                      const wxString &remoteFile, bool download,
                                                      const CFileTransferCommand::t_transferSettings& transferSettings)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::FileTransfer()"));
      
      if (localFile == _T(""))
      {
            if (!download)
                  ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
            else
                  ResetOperation(FZ_REPLY_SYNTAXERROR);
            return FZ_REPLY_ERROR;
      }

      if (download)
      {
            wxString filename = remotePath.FormatFilename(remoteFile);
            LogMessage(Status, _("Starting download of %s"), filename.c_str());
      }
      else
      {
            LogMessage(Status, _("Starting upload of %s"), localFile.c_str());
      }
      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("deleting nonzero pData"));
            delete m_pCurOpData;
      }

      CFtpFileTransferOpData *pData = new CFtpFileTransferOpData;
      m_pCurOpData = pData;

      pData->localFile = localFile;
      pData->remotePath = remotePath;
      pData->remoteFile = remoteFile;
      pData->download = download;
      pData->transferSettings = transferSettings;
      pData->binary = transferSettings.binary;

      pData->opState = filetransfer_waitcwd;

      if (pData->remotePath.GetType() == DEFAULT)
            pData->remotePath.SetType(m_pCurrentServer->GetType());

      wxStructStat buf;
      int result;
      result = wxStat(pData->localFile, &buf);
      if (!result)
      {
            pData->localFileSize = buf.st_size;
      }

      int res = ChangeDir(pData->remotePath);
      if (res != FZ_REPLY_OK)
            return res;

      pData->opState = filetransfer_waitlist;

      CDirentry entry;
      bool dirDidExist;
      bool matchedCase;
      CDirectoryCache cache;
      bool found = cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
      bool shouldList = false;
      if (!found)
      {
            if (!dirDidExist)
                  shouldList = true;
      }
      else
      {
            if (entry.unsure)
                  shouldList = true;
            else
            {
                  if (matchedCase)
                  {
                        pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32);

                        pData->opState = filetransfer_resumetest;
                        res = CheckOverwriteFile();
                        if (res != FZ_REPLY_OK)
                              return res;
                  }
                  else
                        pData->opState = filetransfer_size;
            }
      }
      if (shouldList)
      {
            res = List(CServerPath(), _T(""), true);
            if (res != FZ_REPLY_OK)
                  return res;
      }

      pData->opState = filetransfer_resumetest;

      res = CheckOverwriteFile();
      if (res != FZ_REPLY_OK)
            return res;

      return SendNextCommand();
}

int CFtpControlSocket::FileTransferParseResponse()
{
      LogMessage(Debug_Verbose, _T("FileTransferParseResponse()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
      if (pData->opState == list_init)
            return FZ_REPLY_ERROR;

      int code = GetReplyCode();
      bool error = false;
      switch (pData->opState)
      {
      case filetransfer_size:
            if (code != 2 && code != 3)
            {
                  if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == yes ||
                        !m_Response.Mid(4).CmpNoCase(_T("file not found")) ||
                        (pData->remotePath.FormatFilename(pData->remoteFile).Lower().Find(_T("file not found")) == -1 &&
                         m_Response.Lower().Find(_T("file not found")) != -1))
                  {
                        // Server supports SIZE command but command failed. Most likely MDTM will fail as well, so
                        // skip it.
                        pData->opState = filetransfer_resumetest;

                        int res = CheckOverwriteFile();
                        if (res != FZ_REPLY_OK)
                              return res;
                  }
                  else
                        pData->opState = filetransfer_mdtm;
            }
            else
            {
                  pData->opState = filetransfer_mdtm;
                  if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 4)
                  {
                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == unknown)
                              CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes);
                        wxString str = m_Response.Mid(4);
                        wxFileOffset size = 0;
                        const wxChar *buf = str.c_str();
                        while (*buf)
                        {
                              if (*buf < '0' || *buf > '9')
                                    break;

                              size *= 10;
                              size += *buf - '0';
                              buf++;
                        }
                        pData->remoteFileSize = size;
                  }
                  else
                        LogMessage(Debug_Info, _T("Invalid SIZE reply"));
            }
            break;
      case filetransfer_mdtm:
            pData->opState = filetransfer_resumetest;
            if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 16)
            {
                  wxDateTime date;
                  const wxChar *res = date.ParseFormat(m_Response.Mid(4), _T("%Y%m%d%H%M"));
                  if (res && date.IsValid())
                        pData->fileTime = date;
            }

            {
                  int res = CheckOverwriteFile();
                  if (res != FZ_REPLY_OK)
                        return res;
            }

            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown op state"));
            error = true;
            break;
      }

      if (error)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return FileTransferSend();
}

int CFtpControlSocket::FileTransferSend(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("FileTransferSend()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);

      if (pData->opState == filetransfer_waitcwd)
      {
            if (prevResult == FZ_REPLY_OK)
            {
                  pData->opState = filetransfer_waitlist;

                  CDirentry entry;
                  bool dirDidExist;
                  bool matchedCase;
                  CDirectoryCache cache;
                  bool found = cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
                  bool shouldList = false;
                  if (!found)
                  {
                        if (!dirDidExist)
                              shouldList = true;
                  }
                  else
                  {
                        if (entry.unsure)
                              shouldList = true;
                        else
                        {
                              if (matchedCase)
                              {
                                    pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32);

                                    pData->opState = filetransfer_resumetest;
                                    int res = CheckOverwriteFile();
                                    if (res != FZ_REPLY_OK)
                                          return res;
                              }
                              else
                                    pData->opState = filetransfer_size;
                        }
                  }
                  if (shouldList)
                  {
                        int res = List(CServerPath(), _T(""), true);
                        if (res != FZ_REPLY_OK)
                              return res;
                  }

                  pData->opState = filetransfer_resumetest;

                  int res = CheckOverwriteFile();
                  if (res != FZ_REPLY_OK)
                        return res;
            }
            else
            {
                  pData->tryAbsolutePath = true;
                  pData->opState = filetransfer_size;
            }
      }
      else if (pData->opState == filetransfer_waitlist)
      {
            if (prevResult == FZ_REPLY_OK)
            {
                  CDirentry entry;
                  bool dirDidExist;
                  bool matchedCase;
                  CDirectoryCache cache;
                  bool found = cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
                  if (!found)
                  {
                        if (!dirDidExist)
                    pData->opState = filetransfer_size;
                        else
                        {
                              pData->opState = filetransfer_resumetest;
                              int res = CheckOverwriteFile();
                              if (res != FZ_REPLY_OK)
                                    return res;
                        }
                  }
                  else
                  {
                        if (matchedCase && !entry.unsure)
                        {
                              pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32);

                              pData->opState = filetransfer_resumetest;
                              int res = CheckOverwriteFile();
                              if (res != FZ_REPLY_OK)
                                    return res;
                        }
                        else
                              pData->opState = filetransfer_size;
                  }
            }
            else
                  pData->opState = filetransfer_size;
      }
      else if (pData->opState == filetransfer_waittransfer)
      {
            ResetOperation(prevResult);
            return prevResult;
      }
      else if (pData->opState == filetransfer_waitresumetest)
      {
            if (prevResult != FZ_REPLY_OK)
            {
                  if (pData->transferEndReason == failed_resumetest)
                  {
                        if (pData->localFileSize > ((wxFileOffset)1 << 32))
                        {
                              CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, yes);
                              LogMessage(::Error, _("Server does not support resume of files > 4GB."));
                        }
                        else
                        {
                              CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, yes);
                              LogMessage(::Error, _("Server does not support resume of files > 2GB."));
                        }

                        ResetOperation(prevResult | FZ_REPLY_CRITICALERROR);
                        return FZ_REPLY_ERROR;
                  }
                  else
                        ResetOperation(prevResult);
                  return prevResult;
            }
            if (pData->localFileSize > ((wxFileOffset)1 << 32))
                  CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, no);
            else
                  CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, no);

            pData->opState = filetransfer_transfer;
      }

      wxString cmd;
      switch (pData->opState)
      {
      case filetransfer_size:
            cmd = _T("SIZE ");
            cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
            break;
      case filetransfer_mdtm:
            cmd = _T("MDTM ");
            cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
            break;
      case filetransfer_resumetest:
      case filetransfer_transfer:
            if (m_pTransferSocket)
            {
                  LogMessage(__TFILE__, __LINE__, this, Debug_Verbose, _T("m_pTransferSocket != 0"));
                  delete m_pTransferSocket;
                  m_pTransferSocket = 0;
            }

            {
                  wxFile* pFile = new wxFile;
                  if (pData->download)
                  {
                        // Be quiet
                        wxLogNull nullLog;

                        wxFileOffset startOffset = 0;

                        // Potentially racy
                        bool didExist = wxFile::Exists(pData->localFile);

                        if (pData->resume)
                        {
                              if (!pFile->Open(pData->localFile, wxFile::write_append))
                              {
                                    delete pFile;
                                    LogMessage(::Error, _("Failed to open \"%s\" for appending / writing"), pData->localFile.c_str());
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }

                              pData->fileDidExist = didExist;

                              startOffset = pFile->SeekEnd(0);
                              if (startOffset == wxInvalidOffset)
                              {
                                    delete pFile;
                                    LogMessage(::Error, _("Could not seek to the end of the file"));
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }
                              pData->localFileSize = pFile->Length();

                              // Check resume capabilities
                              if (pData->opState == filetransfer_resumetest)
                              {
                                    int res = FileTransferTestResumeCapability();
                                    if ((res & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
                                    {
                                          delete pFile;
                                          // Server does not support resume but remote and local filesizes are equal
                                          return FZ_REPLY_OK;
                                    }
                                    if (res != FZ_REPLY_OK)
                                    {
                                          delete pFile;
                                          return res;
                                    }
                              }
                        }
                        else
                        {
                              // Create local directory
                              wxFileName fn(pData->localFile);
                              wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);

                              if (!pFile->Open(pData->localFile, wxFile::write))
                              {
                                    delete pFile;
                                    LogMessage(::Error, _("Failed to open \"%s\" for writing"), pData->localFile.c_str());
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }

                              pData->fileDidExist = didExist;
                              pData->localFileSize = pFile->Length();
                        }

                        if (pData->resume)
                              pData->resumeOffset = pData->localFileSize;
                        else
                              pData->resumeOffset = 0;

                        InitTransferStatus(pData->remoteFileSize, startOffset, false);
                  }
                  else
                  {
                        if (!pFile->Open(pData->localFile, wxFile::read))
                        {
                              delete pFile;
                              LogMessage(::Error, _("Failed to open \"%s\" for reading"), pData->localFile.c_str());
                              ResetOperation(FZ_REPLY_ERROR);
                              return FZ_REPLY_ERROR;
                        }

                        wxFileOffset startOffset;
                        if (pData->resume)
                        {
                              if (pData->remoteFileSize > 0)
                              {
                                    startOffset = pData->remoteFileSize;

                                    // Assume native 64 bit type exists
                                    if (pFile->Seek(startOffset, wxFromStart) == wxInvalidOffset)
                                    {
                                          delete pFile;
                                          LogMessage(::Error, _("Could not seek to offset %s within file"), wxLongLong(startOffset).ToString().c_str());
                                          ResetOperation(FZ_REPLY_ERROR);
                                          return FZ_REPLY_ERROR;
                                    }
                              }
                              else
                                    startOffset = 0;
                        }
                        else
                              startOffset = 0;

                        wxFileOffset len = pFile->Length();
                        InitTransferStatus(len, startOffset, false);
                  }
                  pData->pIOThread = new CIOThread;
                  if (!pData->pIOThread->Create(pFile, !pData->download, pData->binary))
                  {
                        // CIOThread will delete pFile
                        delete pData->pIOThread;
                        pData->pIOThread = 0;
                        LogMessage(::Error, _("Could not spawn IO thread"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
            }

            m_pTransferSocket = new CTransferSocket(m_pEngine, this, pData->download ? download : upload);
            m_pTransferSocket->m_binaryMode = pData->transferSettings.binary;

            if (pData->download)
                  cmd = _T("RETR ");
            else if (pData->resume)
                  cmd = _T("APPE ");
            else
                  cmd = _T("STOR ");
            cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);

            pData->opState = filetransfer_waittransfer;
            return Transfer(cmd, pData);
      default:
            LogMessage(::Debug_Warning, _T("Unhandled opState: %d"), pData->opState);
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      if (cmd != _T(""))
            if (!Send(cmd))
                  return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

void CFtpControlSocket::TransferEnd()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferEnd()"));

      // If m_pTransferSocket is zero, the message was sent by the previous command.
      // We can safely ignore it.
      // It does not cause problems, since before creating the next transfer socket, other
      // messages which were added to the queue later than this one will be processed first.
      if (!m_pCurOpData || !m_pTransferSocket || GetCurrentCommandId() != cmd_rawtransfer)
      {
            LogMessage(Debug_Verbose, _T("Call to TransferEnd at unusual time, ignoring"));
            return;
      }

      enum TransferEndReason reason = m_pTransferSocket->GetTransferEndreason();
      if (reason == none)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Call to TransferEnd at unusual time"));
            return;
      }

      if (reason == successful)
            SetAlive();

      CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
      pData->pOldData->transferEndReason = reason;

      switch (m_pCurOpData->opState)
      {
      case rawtransfer_transfer:
            pData->opState = rawtransfer_waittransferpre;
            break;
      case rawtransfer_waitfinish:
            pData->opState = rawtransfer_waittransfer;
            break;
      case rawtransfer_waitsocket:
            ResetOperation((reason == successful) ? FZ_REPLY_OK : FZ_REPLY_ERROR);
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("TransferEnd at unusual op state, ignoring"));
            break;
      }
}

bool CFtpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification)
{
      switch (pNotification->GetRequestID())
      {
      case reqId_fileexists:
            {
                  if (!m_pCurOpData || m_pCurOpData->opId != cmd_transfer)
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No or invalid operation in progress, ignoring request reply %f"), pNotification->GetRequestID());
                        return false;
                  }

                  CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);

                  if (!pData->waitForAsyncRequest)
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID());
                        return false;
                  }
                  pData->waitForAsyncRequest = false;

                  CFileExistsNotification *pFileExistsNotification = reinterpret_cast<CFileExistsNotification *>(pNotification);
                  switch (pFileExistsNotification->overwriteAction)
                  {
                  case CFileExistsNotification::overwrite:
                        SendNextCommand();
                        break;
                  case CFileExistsNotification::overwriteNewer:
                        if (!pFileExistsNotification->localTime.IsValid() || !pFileExistsNotification->remoteTime.IsValid())
                              SendNextCommand();
                        else if (pFileExistsNotification->download && pFileExistsNotification->localTime.IsEarlierThan(pFileExistsNotification->remoteTime))
                              SendNextCommand();
                        else if (!pFileExistsNotification->download && pFileExistsNotification->localTime.IsLaterThan(pFileExistsNotification->remoteTime))
                              SendNextCommand();
                        else
                        {
                              if (pData->download)
                              {
                                    wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
                                    LogMessage(Status, _("Skipping download of %s"), filename.c_str());
                              }
                              else
                              {
                                    LogMessage(Status, _("Skipping upload of %s"), pData->localFile.c_str());
                              }
                              ResetOperation(FZ_REPLY_OK);
                        }
                        break;
                  case CFileExistsNotification::resume:
                        if (pData->download && pData->localFileSize != -1)
                              pData->resume = true;
                        else if (!pData->download && pData->remoteFileSize != -1)
                              pData->resume = true;
                        SendNextCommand();
                        break;
                  case CFileExistsNotification::rename:
                        if (pData->download)
                        {
                              wxFileName fn = pData->localFile;
                              fn.SetFullName(pFileExistsNotification->newName);
                              pData->localFile = fn.GetFullPath();

                              wxStructStat buf;
                              int result;
                              result = wxStat(pData->localFile, &buf);
                              if (!result)
                                    pData->localFileSize = buf.st_size;
                              else
                                    pData->localFileSize = -1;

                              if (CheckOverwriteFile() == FZ_REPLY_OK)
                                    SendNextCommand();
                        }
                        else
                        {
                              pData->remoteFile = pFileExistsNotification->newName;

                              CDirectoryListing listing;
                              CDirectoryCache cache;
                              bool found = cache.Lookup(listing, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, true);
                              if (!found)
                              {
                                    pData->opState = filetransfer_size;
                                    SendNextCommand();
                              }
                              else
                              {
                                    bool differentCase = false;
                                    bool found = false;
                                    for (unsigned int i = 0; i < listing.GetCount(); i++)
                                    {
                                          if (!listing[i].name.CmpNoCase(pData->remoteFile))
                                          {
                                                if (listing[i].name != pData->remoteFile)
                                                      differentCase = true;
                                                else
                                                {
                                                      wxLongLong size = listing[i].size;
                                                      pData->remoteFileSize = size.GetLo() + ((wxFileOffset)size.GetHi() << 32);
                                                      found = true;
                                                      break;
                                                }
                                          }
                                    }
                                    if (found)
                                    {
                                          if (CheckOverwriteFile() == FZ_REPLY_OK)
                                                SendNextCommand();
                                    }
                                    else if (differentCase)
                                    {
                                          pData->opState = filetransfer_size;
                                          SendNextCommand();
                                    }
                                    else
                                          SendNextCommand();
                              }

                        }
                        break;
                  case CFileExistsNotification::skip:
                        if (pData->download)
                        {
                              wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
                              LogMessage(Status, _("Skipping download of %s"), filename.c_str());
                        }
                        else
                        {
                              LogMessage(Status, _("Skipping upload of %s"), pData->localFile.c_str());
                        }
                        ResetOperation(FZ_REPLY_OK);
                        break;
                  default:
                        LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown file exists action: %d"), pFileExistsNotification->overwriteAction);
                        ResetOperation(FZ_REPLY_INTERNALERROR);
                        return false;
                  }
            }
            break;
      case reqId_interactiveLogin:
            {
                  if (!m_pCurOpData || m_pCurOpData->opId != cmd_connect)
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID());
                        return false;
                  }

                  CFtpLogonOpData* pData = static_cast<CFtpLogonOpData*>(m_pCurOpData);

                  if (!pData->waitForAsyncRequest)
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID());
                        return false;
                  }
                  pData->waitForAsyncRequest = false;

                  CInteractiveLoginNotification *pInteractiveLoginNotification = reinterpret_cast<CInteractiveLoginNotification *>(pNotification);
                  if (!pInteractiveLoginNotification->passwordSet)
                  {
                        ResetOperation(FZ_REPLY_CANCELED);
                        return false;
                  }
                  m_pCurrentServer->SetUser(m_pCurrentServer->GetUser(), pInteractiveLoginNotification->server.GetPass());
                  pData->gotPassword = true;
                  SendNextCommand();
            }
            break;
      case reqId_certificate:
            {
                  if (!m_pTlsSocket || m_pTlsSocket->GetState() != CTlsSocket::verifycert)
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID());
                        return false;                       
                  }

                  CCertificateNotification* pCertificateNotification = reinterpret_cast<CCertificateNotification *>(pNotification);
                  m_pTlsSocket->TrustCurrentCert(pCertificateNotification->m_trusted);
            }
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown request %d"), pNotification->GetRequestID());
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return false;
      }

      return true;
}

class CRawCommandOpData : public COpData
{
public:
      CRawCommandOpData(const wxString& command)
            : COpData(cmd_raw)
      {
            m_command = command;
      }

      wxString m_command;
};

int CFtpControlSocket::RawCommand(const wxString& command)
{
      wxASSERT(command != _T(""));

      m_pCurOpData = new CRawCommandOpData(command);

      return SendNextCommand();
}

int CFtpControlSocket::RawCommandSend()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::RawCommandSend"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CDirectoryCache cache;
      cache.InvalidateServer(*m_pCurrentServer);
      m_CurrentPath = CServerPath();

      CRawCommandOpData *pData = static_cast<CRawCommandOpData *>(m_pCurOpData);

      if (!Send(pData->m_command))
            return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::RawCommandParseResponse()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::RawCommandParseResponse"));

      int code = GetReplyCode();
      if (code == 2 || code == 3)
      {
            ResetOperation(FZ_REPLY_OK);
            return FZ_REPLY_OK;
      }
      else
      {
        ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }
}

class CFtpDeleteOpData : public COpData
{
public:
      CFtpDeleteOpData()
            : COpData(cmd_delete)
      {
      }

      virtual ~CFtpDeleteOpData() { }

      CServerPath path;
      wxString file;
      bool omitPath;
};

int CFtpControlSocket::Delete(const CServerPath& path /*=CServerPath()*/, const wxString& file /*=_T("")*/)
{
      wxASSERT(!m_pCurOpData);
      CFtpDeleteOpData *pData = new CFtpDeleteOpData();
      m_pCurOpData = pData;
      pData->path = path;
      pData->file = file;
      pData->omitPath = true;

      int res = ChangeDir(pData->path);
      if (res != FZ_REPLY_OK)
            return res;

      return SendNextCommand();
}

int CFtpControlSocket::DeleteSend(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::DeleteSend(%d)"), prevResult);

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);

      if (prevResult != FZ_REPLY_OK)
            pData->omitPath = false;

      wxString filename = pData->path.FormatFilename(pData->file, pData->omitPath);
      if (filename == _T(""))
      {
            LogMessage(::Error, _T("Filename cannot be constructed for folder %s and filename %s"), pData->path.GetPath().c_str(), pData->file.c_str());
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      CDirectoryCache cache;
      cache.InvalidateFile(*m_pCurrentServer, pData->path, pData->file);

      if (!Send(_T("DELE ") + filename))
            return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::DeleteParseResponse()
{     
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::DeleteParseResponse()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);

      int code = GetReplyCode();
      if (code != 2 && code != 3)
            return ResetOperation(FZ_REPLY_ERROR);

      CDirectoryCache cache;
      cache.RemoveFile(*m_pCurrentServer, pData->path, pData->file);
      m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false);

      return ResetOperation(FZ_REPLY_OK);
}

class CFtpRemoveDirOpData : public COpData
{
public:
      CFtpRemoveDirOpData()
            : COpData(cmd_removedir)
      {
      }

      virtual ~CFtpRemoveDirOpData() { }

      CServerPath path;
      CServerPath fullPath;
      wxString subDir;
      bool omitPath;
};

int CFtpControlSocket::RemoveDir(const CServerPath& path, const wxString& subDir)
{
      wxASSERT(!m_pCurOpData);
      CFtpRemoveDirOpData *pData = new CFtpRemoveDirOpData();
      m_pCurOpData = pData;
      pData->path = path;
      pData->subDir = subDir;
      pData->omitPath = true;
      pData->fullPath = path;

      if (!pData->fullPath.AddSegment(subDir))
      {
            LogMessage(::Error, _T("Path cannot be constructed for folder %s and subdir %s"), path.GetPath().c_str(), subDir.c_str());
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      int res = ChangeDir(pData->path);
      if (res != FZ_REPLY_OK)
            return res;

      return SendNextCommand();
}

int CFtpControlSocket::RemoveDirSend(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::RemoveDirSend()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData);

      if (prevResult != FZ_REPLY_OK)
            pData->omitPath = false;

      CDirectoryCache cache;
      cache.InvalidateFile(*m_pCurrentServer, pData->path, pData->subDir);

      CPathCache pathCache;
      CServerPath path = pathCache.Lookup(*m_pCurrentServer, pData->path, pData->subDir);
      if (path.IsEmpty())
      {
            path = pData->path;
            path.AddSegment(pData->subDir);
      }
      m_pEngine->InvalidateCurrentWorkingDirs(path);

      pathCache.InvalidatePath(*m_pCurrentServer, pData->path, pData->subDir);

      if (pData->omitPath)
      {
            if (!Send(_T("RMD ") + pData->subDir))
                  return FZ_REPLY_ERROR;
      }
      else
            if (!Send(_T("RMD ") + pData->fullPath.GetPath()))
                  return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::RemoveDirParseResponse()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::RemoveDirParseResponse()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData);

      int code = GetReplyCode();
      if (code != 2 && code != 3)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      CDirectoryCache cache;
      cache.RemoveDir(*m_pCurrentServer, pData->path, pData->subDir);
      m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false);

      return ResetOperation(FZ_REPLY_OK);
}

enum mkdStates
{
      mkd_init = 0,
      mkd_findparent,
      mkd_mkdsub,
      mkd_cwdsub,
      mkd_tryfull
};

int CFtpControlSocket::Mkdir(const CServerPath& path)
{
      /* Directory creation works like this: First find a parent directory into
       * which we can CWD, then create the subdirs one by one. If either part
       * fails, try MKD with the full path directly.
       */

      if (!m_pCurOpData)
            LogMessage(Status, _("Creating directory '%s'..."), path.GetPath().c_str());

      CMkdirOpData *pData = new CMkdirOpData;
      pData->opState = mkd_findparent;
      pData->path = path;

      if (!m_CurrentPath.IsEmpty())
      {
            // Unless the server is broken, a directory already exists if current directory is a subdir of it.
            if (m_CurrentPath == path || m_CurrentPath.IsSubdirOf(path, false))
                  return FZ_REPLY_OK;

            if (m_CurrentPath.IsParentOf(path, false))
                  pData->commonParent = m_CurrentPath;
            else
                  pData->commonParent = path.GetCommonParent(m_CurrentPath);
      }

      pData->currentPath = path.GetParent();
      pData->segments.push_back(path.GetLastSegment());

      if (pData->currentPath == m_CurrentPath)
            pData->opState = mkd_mkdsub;

      pData->pNextOpData = m_pCurOpData;
      m_pCurOpData = pData;

      return SendNextCommand();
}

int CFtpControlSocket::MkdirParseResponse()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::MkdirParseResonse"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
      LogMessage(Debug_Debug, _T("  state = %d"), pData->opState);

      int code = GetReplyCode();
      bool error = false;
      switch (pData->opState)
      {
      case mkd_findparent:
            if (code == 2 || code == 3)
            {
                  m_CurrentPath = pData->currentPath;
                  pData->opState = mkd_mkdsub;
            }
            else if (pData->currentPath == pData->commonParent)
                  pData->opState = mkd_tryfull;
            else if (pData->currentPath.HasParent())
            {
                  const CServerPath& parent = pData->currentPath.GetParent();
                  pData->segments.push_front(pData->currentPath.GetLastSegment());
                  pData->currentPath = parent;
            }
            else
                  pData->opState = mkd_tryfull;
            break;
      case mkd_mkdsub:
            if (code != 2 && code != 3)
            {
                  // Don't fall back to using the full path if the error message
                  // is "already exists".
                  // Case 1: Full response a known "already exists" message.
                  // Case 2: Substrng of response contains "already exists". pData->path may not 
                  //         contain this substring as the path might be returned in the reply.
                  if (m_Response.Mid(4).CmpNoCase(_T("directory already exists")) &&
                        (pData->path.GetPath().Lower().Find(_T("already exists")) != -1 ||
                         m_Response.Lower().Find(_T("already exists")) == -1)
                        )
                  {
                        pData->opState = mkd_tryfull;
                        break;
                  }
            }
            
            {
                  if (pData->segments.empty())
                  {
                        LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("  pData->segments is empty"));
                        ResetOperation(FZ_REPLY_INTERNALERROR);
                        return FZ_REPLY_ERROR;
                  }
                  CDirectoryCache cache;
                  cache.UpdateFile(*m_pCurrentServer, pData->currentPath, pData->segments.front(), true, CDirectoryCache::dir);
                  m_pEngine->SendDirectoryListingNotification(pData->currentPath, false, true, false);

                  pData->currentPath.AddSegment(pData->segments.front());
                  pData->segments.pop_front();

                  if (pData->segments.empty())
                  {
                        ResetOperation(FZ_REPLY_OK);
                        return FZ_REPLY_OK;
                  }
                  else
                        pData->opState = mkd_cwdsub;
            }
            break;
      case mkd_cwdsub:
            if (code == 2 || code == 3)
            {
                  m_CurrentPath = pData->currentPath;
                  pData->opState = mkd_mkdsub;
            }
            else
                  pData->opState = mkd_tryfull;
            break;
      case mkd_tryfull:
            if (code != 2 && code != 3)
                  error = true;
            else
            {
                  ResetOperation(FZ_REPLY_OK);
                  return FZ_REPLY_OK;
            }
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("unknown op state: %d"), pData->opState);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (error)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return MkdirSend();
}

int CFtpControlSocket::MkdirSend()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::MkdirSend"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
      LogMessage(Debug_Debug, _T("  state = %d"), pData->opState);

      bool res;
      switch (pData->opState)
      {
      case mkd_findparent:
      case mkd_cwdsub:
            m_CurrentPath.Clear();
            res = Send(_T("CWD ") + pData->currentPath.GetPath());
            break;
      case mkd_mkdsub:
            res = Send(_T("MKD ") + pData->segments.front());
            break;
      case mkd_tryfull:
            res = Send(_T("MKD ") + pData->path.GetPath());
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("unknown op state: %d"), pData->opState);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (!res)
            return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

class CFtpRenameOpData : public COpData
{
public:
      CFtpRenameOpData(const CRenameCommand& command)
            : COpData(cmd_rename), m_cmd(command)
      {
            m_useAbsolute = false;
      }

      virtual ~CFtpRenameOpData() { }

      CRenameCommand m_cmd;
      bool m_useAbsolute;
};

enum renameStates
{
      rename_init = 0,
      rename_rnfrom,
      rename_rnto
};

int CFtpControlSocket::Rename(const CRenameCommand& command)
{
      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData not empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      LogMessage(Status, _("Renaming '%s' to '%s'"), command.GetFromPath().FormatFilename(command.GetFromFile()).c_str(), command.GetToPath().FormatFilename(command.GetToFile()).c_str());

      CFtpRenameOpData *pData = new CFtpRenameOpData(command);
      pData->opState = rename_rnfrom;
      m_pCurOpData = pData;

      int res = ChangeDir(command.GetFromPath());
      if (res != FZ_REPLY_OK)
            return res;

      return SendNextCommand();
}

int CFtpControlSocket::RenameParseResponse()
{
      CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData);
      if (!pData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      int code = GetReplyCode();
      if (code != 2 && code != 3)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      if (pData->opState == rename_rnfrom)
            pData->opState = rename_rnto;
      else
      {
            CDirectoryCache cache;

            const CServerPath& fromPath = pData->m_cmd.GetFromPath();
            const CServerPath& toPath = pData->m_cmd.GetToPath();
            cache.Rename(*m_pCurrentServer, fromPath, pData->m_cmd.GetFromFile(), toPath, pData->m_cmd.GetToFile());

            m_pEngine->SendDirectoryListingNotification(fromPath, false, true, false);
            if (fromPath != toPath)
                  m_pEngine->SendDirectoryListingNotification(toPath, false, true, false);

            ResetOperation(FZ_REPLY_OK);
            return FZ_REPLY_OK;
      }

      return RenameSend();
}

int CFtpControlSocket::RenameSend(int prevResult /*=FZ_REPLY_OK*/)
{
      CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData);
      if (!pData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (prevResult != FZ_REPLY_OK)
            pData->m_useAbsolute = true;

      bool res;
      switch (pData->opState)
      {
      case rename_rnfrom:
            res = Send(_T("RNFR ") + pData->m_cmd.GetFromPath().FormatFilename(pData->m_cmd.GetFromFile(), !pData->m_useAbsolute));
            break;
      case rename_rnto:
            {
                  CDirectoryCache cache;
                  bool wasDir = false;
                  cache.InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile(), &wasDir);
                  cache.InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());

                  CPathCache pathCache;

                  if (wasDir)
                  {
                        CServerPath path = pathCache.Lookup(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
                        if (path.IsEmpty())
                        {
                              path = pData->m_cmd.GetFromPath();
                              path.AddSegment(pData->m_cmd.GetFromFile());
                        }
                        m_pEngine->InvalidateCurrentWorkingDirs(path);
                  }

                  pathCache.InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
                  pathCache.InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());

                  res = Send(_T("RNTO ") + pData->m_cmd.GetToPath().FormatFilename(pData->m_cmd.GetToFile(), !pData->m_useAbsolute && pData->m_cmd.GetFromPath() == pData->m_cmd.GetToPath()));
                  break;
            }
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("unknown op state: %d"), pData->opState);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (!res)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return FZ_REPLY_WOULDBLOCK;
}

class CFtpChmodOpData : public COpData
{
public:
      CFtpChmodOpData(const CChmodCommand& command)
            : COpData(cmd_chmod), m_cmd(command)
      {
            m_useAbsolute = false;
      }

      virtual ~CFtpChmodOpData() { }

      CChmodCommand m_cmd;
      bool m_useAbsolute;
};

enum chmodStates
{
      chmod_init = 0,
      chmod_chmod
};

int CFtpControlSocket::Chmod(const CChmodCommand& command)
{
      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData not empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      LogMessage(Status, _("Set permissions of '%s' to '%s'"), command.GetPath().FormatFilename(command.GetFile()).c_str(), command.GetPermission().c_str());

      CFtpChmodOpData *pData = new CFtpChmodOpData(command);
      pData->opState = chmod_chmod;
      m_pCurOpData = pData;

      int res = ChangeDir(command.GetPath());
      if (res != FZ_REPLY_OK)
            return res;

      return SendNextCommand();
}

int CFtpControlSocket::ChmodParseResponse()
{
      CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData);
      if (!pData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      int code = GetReplyCode();
      if (code != 2 && code != 3)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      CDirectoryCache cache;
      cache.UpdateFile(*m_pCurrentServer, pData->m_cmd.GetPath(), pData->m_cmd.GetFile(), false, CDirectoryCache::unknown);

      ResetOperation(FZ_REPLY_OK);
      return FZ_REPLY_OK;
}

int CFtpControlSocket::ChmodSend(int prevResult /*=FZ_REPLY_OK*/)
{
      CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData);
      if (!pData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (prevResult != FZ_REPLY_OK)
            pData->m_useAbsolute = true;

      bool res;
      switch (pData->opState)
      {
      case chmod_chmod:
            res = Send(_T("SITE CHMOD ") + pData->m_cmd.GetPermission() + _T(" ") + pData->m_cmd.GetPath().FormatFilename(pData->m_cmd.GetFile(), !pData->m_useAbsolute));
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("unknown op state: %d"), pData->opState);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (!res)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return FZ_REPLY_WOULDBLOCK;
}

bool CFtpControlSocket::IsMisleadingListResponse() const
{
      // Some servers are broken. Instead of an empty listing, some MVS servers
      // for example they return "550 no members found"
      // Other servers return "550 No files found."

      if (!m_Response.CmpNoCase(_T("550 No members found.")))
            return true;

      if (!m_Response.CmpNoCase(_T("550 No data sets found.")))
            return true;

      if (!m_Response.CmpNoCase(_T("550 No files found.")))
            return true;

      return false;
}

bool CFtpControlSocket::ParsePasvResponse(CRawTransferOpData* pData)
{
      // Validate ip address
      wxString digit = _T("0*[0-9]{1,3}");
      const wxChar* dot = _T(",");
      wxString exp = _T("( |\\()(") + digit + dot + digit + dot + digit + dot + digit + dot + digit + dot + digit + _T(")( |\\)|$)");
      wxRegEx regex;
      regex.Compile(exp);

      if (!regex.Matches(m_Response))
            return false;

      pData->host = regex.GetMatch(m_Response, 2);

      int i = pData->host.Find(',', true);
      long number;
      if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number))
            return false;

      pData->port = number; //get ls byte of server socket
      pData->host = pData->host.Left(i);
      i = pData->host.Find(',', true);
      if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number))
            return false;

      pData->port += 256 * number; //add ms byte of server socket
      pData->host = pData-> host.Left(i);
      pData->host.Replace(_T(","), _T("."));

      const wxString peerIP = GetPeerIP();
      if (!IsRoutableAddress(pData->host) && IsRoutableAddress(peerIP))
      {
            if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_PASVREPLYFALLBACKMODE) || pData->bTriedActive)
            {
                  LogMessage(Debug_Verbose, _("Server sent passive reply with unroutable address. Using server address instead."));
                  pData->host = peerIP;
            }
            else
                  return false;
      }

      return true;
}

int CFtpControlSocket::GetExternalIPAddress(wxString& address)
{
      int mode = m_pEngine->GetOptions()->GetOptionVal(OPTION_EXTERNALIPMODE);

      if (mode)
      {
            if (m_pEngine->GetOptions()->GetOptionVal(OPTION_NOEXTERNALONLOCAL) &&
                  !IsRoutableAddress(GetPeerIP()))
                  // Skip next block, use local address
                  goto getLocalIP;
      }

      if (mode == 1)
      {
            wxString ip = m_pEngine->GetOptions()->GetOption(OPTION_EXTERNALIP);
            if (ip != _T(""))
            {
                  address = ip;
                  return FZ_REPLY_OK;
            }

            LogMessage(::Debug_Warning, _("No external IP address set, trying default."));
      }
      else if (mode == 2)
      {
            if (!m_pIPResolver)
            {
                  const wxString& localAddress = GetLocalIP();

                  if (localAddress != _T("") && localAddress == m_pEngine->GetOptions()->GetOption(OPTION_LASTRESOLVEDIP))
                  {
                        LogMessage(::Debug_Verbose, _T("Using cached external IP address"));

                        address = localAddress;
                        return FZ_REPLY_OK;
                  }

                  wxString resolverAddress = m_pEngine->GetOptions()->GetOption(OPTION_EXTERNALIPRESOLVER);

                  LogMessage(::Debug_Info, _("Retrieving external IP address from %s"), resolverAddress.c_str());

                  m_pIPResolver = new CExternalIPResolver(this);
                  m_pIPResolver->GetExternalIP(resolverAddress);
                  if (!m_pIPResolver->Done())
                  {
                        LogMessage(::Debug_Verbose, _T("Waiting for resolver thread"));
                        return FZ_REPLY_WOULDBLOCK;
                  }
            }
            if (!m_pIPResolver->Successful())
            {
                  delete m_pIPResolver;
                  m_pIPResolver = 0;

                  LogMessage(::Debug_Warning, _("Failed to retrieve external ip address, using local address"));
            }
            else
            {
                  LogMessage(::Debug_Info, _T("Got external IP address"));
                  address = m_pIPResolver->GetIP();

                  m_pEngine->GetOptions()->SetOption(OPTION_LASTRESOLVEDIP, address);

                  delete m_pIPResolver;
                  m_pIPResolver = 0;

                  return FZ_REPLY_OK;
            }
      }

getLocalIP:

      address = GetLocalIP();
      if (address == _T(""))
      {
            LogMessage(::Error, _("Failed to retrieve local ip address."), 1);
            return FZ_REPLY_ERROR;
      }

      return FZ_REPLY_OK;
}

void CFtpControlSocket::OnExternalIPAddress(fzExternalIPResolveEvent& event)
{
      LogMessage(::Debug_Verbose, _T("CFtpControlSocket::OnExternalIPAddress()"));
      if (!m_pIPResolver)
      {
            LogMessage(::Debug_Info, _T("Ignoring event"));
            return;
      }

      SendNextCommand();
}

int CFtpControlSocket::Transfer(const wxString& cmd, CFtpTransferOpData* oldData)
{
      wxASSERT(oldData);
      oldData->tranferCommandSent = false;

      CRawTransferOpData *pData = new CRawTransferOpData;
      pData->pNextOpData = m_pCurOpData;
      m_pCurOpData = pData;

      pData->cmd = cmd;
      pData->pOldData = oldData;
      pData->pOldData->transferEndReason = successful;

      switch (m_pCurrentServer->GetPasvMode())
      {
      case MODE_PASSIVE:
            pData->bPasv = true;
            break;
      case MODE_ACTIVE:
            pData->bPasv = false;
            break;
      default:
            pData->bPasv = m_pEngine->GetOptions()->GetOptionVal(OPTION_USEPASV) != 0;
            break;
      }

      if ((pData->pOldData->binary && m_lastTypeBinary == 1) ||
            (!pData->pOldData->binary && m_lastTypeBinary == 0))
            pData->opState = rawtransfer_port_pasv;
      else
            pData->opState = rawtransfer_type;

      return SendNextCommand();
}

int CFtpControlSocket::TransferParseResponse()
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferParseResponse()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
      if (pData->opState == rawtransfer_init)
            return FZ_REPLY_ERROR;

      int code = GetReplyCode();

      LogMessage(Debug_Debug, _T("  code = %d"), code);
      LogMessage(Debug_Debug, _T("  state = %d"), pData->opState);

      bool error = false;
      switch (pData->opState)
      {
      case rawtransfer_type:
            if (code != 2 && code != 2)
                  error = true;
            else
            {
                  pData->opState = rawtransfer_port_pasv;
                  m_lastTypeBinary = pData->pOldData->binary ? 1 : 0;
            }
            break;
      case rawtransfer_port_pasv:
            if (code != 2 && code != 3)
            {
                  if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK))
                  {
                        error = true;
                        break;
                  }

                  if (pData->bTriedPasv)
                        if (pData->bTriedActive)
                              error = true;
                        else
                              pData->bPasv = false;
                  else
                        pData->bPasv = true;
                  break;
            }
            if (pData->bPasv)
            {
                  if (!ParsePasvResponse(pData))
                  {
                        if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK))
                        {
                              error = true;
                              break;
                        }

                        if (!pData->bTriedActive)
                              pData->bPasv = false;
                        else
                              error = true;
                        break;
                  }
            }
            if (pData->pOldData->resumeOffset > 0 || m_sentRestartOffset)
                  pData->opState = rawtransfer_rest;
            else
                  pData->opState = rawtransfer_transfer;
            break;
      case rawtransfer_rest:
            if (pData->pOldData->resumeOffset == 0)
                  m_sentRestartOffset = false;
            if (pData->pOldData->resumeOffset != 0 && code != 2 && code != 3)
                  error = true;
            else
                  pData->opState = rawtransfer_transfer;
            break;
      case rawtransfer_transfer:
            if (code != 1)
            {
                  pData->pOldData->transferEndReason = transfer_command_failure_immediate;
                  error = true;
            }
            else
                  pData->opState = rawtransfer_waitfinish;
            break;
      case rawtransfer_waittransferpre:
            if (code != 1)
            {
                  pData->pOldData->transferEndReason = transfer_command_failure_immediate;
                  error = true;
            }
            else
                  pData->opState = rawtransfer_waittransfer;
            break;
      case rawtransfer_waitfinish:
            if (code != 2 && code != 3)
            {
                  pData->pOldData->transferEndReason = transfer_command_failure;
                  error = true;
            }
            else
                  pData->opState = rawtransfer_waitsocket;
            break;
      case rawtransfer_waittransfer:
            if (code != 2 && code != 3)
            {
                  pData->pOldData->transferEndReason = transfer_command_failure;
                  error = true;
            }
            else
            {
                  if (pData->pOldData->transferEndReason != successful)
                  {
                        error = true;
                        break;
                  }

                  ResetOperation(FZ_REPLY_OK);
                  return FZ_REPLY_OK;
            }
            break;
      case rawtransfer_waitsocket:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Extra reply received during rawtransfer_waitsocket."));
            error = true;
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown op state"));
            error = true;
      }
      if (error)
      {
            ResetOperation(FZ_REPLY_ERROR);
            return FZ_REPLY_ERROR;
      }

      return TransferSend();
}

int CFtpControlSocket::TransferSend(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferSend(%d)"), prevResult);

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      if (!m_pTransferSocket)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pTransferSocket"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
      LogMessage(Debug_Debug, _T("  state = %d"), pData->opState);

      wxString cmd;
      switch (pData->opState)
      {
      case rawtransfer_type:
            m_lastTypeBinary = -1;
            if (pData->pOldData->binary)
                  cmd = _T("TYPE I");
            else
                  cmd = _T("TYPE A");
            break;
      case rawtransfer_port_pasv:
            if (pData->bPasv)
            {
                  pData->bTriedPasv = true;
                  cmd = _T("PASV");
            }
            else
            {
                  wxString address;
                  int res = GetExternalIPAddress(address);
                  if (res == FZ_REPLY_WOULDBLOCK)
                        return res;
                  else if (res == FZ_REPLY_OK)
                  {
                        wxString portArgument = m_pTransferSocket->SetupActiveTransfer(address);
                if (portArgument != _T(""))
                        {
                              pData->bTriedActive = true;
                              cmd = _T("PORT " + portArgument);
                              break;
                        }
                  }

                  if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK) || pData->bTriedPasv)
                  {
                        LogMessage(::Error, _("Failed to create listening socket for active mode transfer"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
                  LogMessage(::Debug_Warning, _("Failed to create listening socket for active mode transfer"));
                  pData->bTriedActive = true;
                  pData->bTriedPasv = true;
                  pData->bPasv = true;
                  cmd = _T("PASV");
            }
            break;
      case rawtransfer_rest:
            cmd = _T("REST ") + pData->pOldData->resumeOffset.ToString();
            if (pData->pOldData->resumeOffset > 0)
                  m_sentRestartOffset = true;
            break;
      case rawtransfer_transfer:
            if (pData->bPasv)
            {
                  if (!m_pTransferSocket->SetupPassiveTransfer(pData->host, pData->port))
                  {
                        LogMessage(::Error, _("Could not establish connection to server"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
            }

            cmd = pData->cmd;
            pData->pOldData->tranferCommandSent = true;

            SetTransferStatusStartTime();
            m_pTransferSocket->SetActive();
            break;
      case rawtransfer_waitfinish:
      case rawtransfer_waittransferpre:
      case rawtransfer_waittransfer:
      case rawtransfer_waitsocket:
            break;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("invalid opstate"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }
      if (cmd != _T(""))
            if (!Send(cmd))
                  return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CFtpControlSocket::FileTransferTestResumeCapability()
{
      LogMessage(Debug_Verbose, _T("FileTransferTestResumeCapability()"));

      if (!m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData"));
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return FZ_REPLY_ERROR;
      }

      CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);

      if (!pData->download)
            return FZ_REPLY_OK;

      for (int i = 0; i < 2; i++)
      {
            if (pData->localFileSize >= ((wxFileOffset)1 << (i ? 31 : 32)))
            {
                  switch (CServerCapabilities::GetCapability(*GetCurrentServer(), i ? resume2GBbug : resume4GBbug))
                  {
                  case yes:
                        if (pData->remoteFileSize == pData->localFileSize)
                        {
                              LogMessage(::Debug_Info, _("Server does not support resume of files > %d GB. End transfer since filesizes match."), i ? 2 : 4);
                              ResetOperation(FZ_REPLY_OK);
                              return FZ_REPLY_CANCELED;
                        }
                        LogMessage(::Error, _("Server does not support resume of files > %d GB."), i ? 2 : 4);
                        ResetOperation(FZ_REPLY_CRITICALERROR);
                        return FZ_REPLY_ERROR;
                  case unknown:
                        if (pData->remoteFileSize < pData->localFileSize)
                        {
                              // Don't perform test
                              break;
                        }
                        if (pData->remoteFileSize == pData->localFileSize)
                        {
                              LogMessage(::Debug_Info, _("Server may not support resume of files > %d GB. End transfer since filesizes match."), i ? 2 : 4);
                              ResetOperation(FZ_REPLY_OK);
                              return FZ_REPLY_CANCELED;
                        }
                        else if (pData->remoteFileSize > pData->localFileSize)
                        {
                              LogMessage(Status, _("Testing resume capabilities of server"));

                              pData->opState = filetransfer_waitresumetest;
                              pData->resumeOffset = pData->remoteFileSize - 1;

                              m_pTransferSocket = new CTransferSocket(m_pEngine, this, resumetest);

                              return Transfer(_T("RETR ") + pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath), pData);
                        }
                        break;
                  case no:
                        break;
                  }
            }
      }

      return FZ_REPLY_OK;
}

int CFtpControlSocket::Connect(const CServer &server)
{
      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("deleting nonzero pData"));
            delete m_pCurOpData;
      }
            
      CFtpLogonOpData* pData = new CFtpLogonOpData;
      pData->nCommand = logonseq[pData->logonType][0];

      if (server.GetProtocol() != FTPES)
      {
            pData->neededCommands[LOGON_AUTH_TLS] = 0;
            pData->neededCommands[LOGON_AUTH_SSL] = 0;
            if (server.GetProtocol() != FTPS)
            {
                  pData->neededCommands[LOGON_PBSZ] = 0;
                  pData->neededCommands[LOGON_PROT] = 0;
            }
      }
      if (server.GetPostLoginCommands().empty())
            pData->neededCommands[LOGON_CUSTOMCOMMANDS] = 0;

      m_pCurOpData = pData;

      return CRealControlSocket::Connect(server);
}

bool CFtpControlSocket::CheckInclusion(const CDirectoryListing& listing1, const CDirectoryListing& listing2)
{
      // Check if listing2 is contained within listing1

      if (listing2.GetCount() > listing1.GetCount())
            return false;

      std::vector<wxString> names1, names2;
      listing1.GetFilenames(names1);
      listing2.GetFilenames(names2);
      std::sort(names1.begin(), names1.end());
      std::sort(names2.begin(), names2.end());

      std::vector<wxString>::const_iterator iter1, iter2;
      iter1 = names1.begin();
      iter2 = names2.begin();
      while (iter2 != names2.begin())
      {
            if (iter1 == names1.end())
                  return false;

            if (*iter1 != *iter2)
            {
                  iter1++;
                  continue;
            }

            iter1++;
            iter2++;
      }

      return true;
}

Generated by  Doxygen 1.6.0   Back to index