Logo Search packages:      
Sourcecode: filezilla version File versions

httpcontrolsocket.cpp

#include "FileZilla.h"
#include "ControlSocket.h"
#include "httpcontrolsocket.h"

// Connect is special for HTTP: It is done on a per-command basis, so we need
// to establish a connection before each command.
class CHttpConnectOpData : public COpData
{
public:
      CHttpConnectOpData()
            : COpData(cmd_connect)
      {
      }

      virtual ~CHttpConnectOpData()
      {
      }

      wxString host;
      unsigned int port;
};

class CHttpOpData
{
public:
      CHttpOpData(COpData* pOpData)
            : m_pOpData(pOpData)
      {
            m_gotHeader = false;
            m_responseCode = -1;
            m_redirectionCount = 0;
            m_transferEncoding = unknown;

            m_chunkData.getTrailer = false;
            m_chunkData.size = 0;
            m_chunkData.terminateChunk = false;

            m_totalSize = -1;
            m_receivedData = 0;
      }

      bool m_gotHeader;
      int m_responseCode;
      wxString m_responseString;
      wxString m_newLocation;
      wxString m_newHostWithPort;
      int m_redirectionCount;

      wxLongLong m_totalSize;
      wxLongLong m_receivedData;
      
      COpData* m_pOpData;

      enum transferEncodings
      {
            identity,
            chunked,
            unknown
      };
      enum transferEncodings m_transferEncoding;

      struct t_chunkData
      {
            bool getTrailer;
            bool terminateChunk;
            wxLongLong size;
      } m_chunkData;
};

class CHttpFileTransferOpData : public CFileTransferOpData, public CHttpOpData
{
public:
      CHttpFileTransferOpData()
            : CHttpOpData(this)
      {
            pFile = 0;
      }

      virtual ~CHttpFileTransferOpData()
      {
            delete pFile;
      }

      wxFile* pFile;
};

CHttpControlSocket::CHttpControlSocket(CFileZillaEnginePrivate *pEngine)
      : CRealControlSocket(pEngine)
{
      m_pRecvBuffer = 0;
      m_pAddress = 0;
}

CHttpControlSocket::~CHttpControlSocket()
{
      delete [] m_pRecvBuffer;
      delete m_pAddress;
}

int CHttpControlSocket::SendNextCommand(int prevResult /*=FZ_REPLY_OK*/)
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::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_transfer:
            return FileTransferSend(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;
}

int CHttpControlSocket::ContinueConnect(const wxIPV4address *address)
{
      LogMessage(__TFILE__, __LINE__, this, Debug_Verbose, _T("CHttpControlSocket::ContinueConnect(%p) m_pEngine=%p"), address, m_pEngine);
      if (GetCurrentCommandId() != cmd_connect ||
            !m_pCurrentServer)
      {
            LogMessage(Debug_Warning, _T("Invalid context for call to ContinueConnect(), cmd=%d, m_pCurrentServer=%p"), GetCurrentCommandId(), m_pCurrentServer);
            return DoClose(FZ_REPLY_INTERNALERROR);
      }
      
      if (!address)
      {
            if (m_pCurrentServer)
            {
                  delete m_pCurrentServer;
                  m_pCurrentServer = 0;
            }

            LogMessage(::Error, _("Invalid hostname or host not found"));
            return ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_CRITICALERROR);
      }

      if (m_pCurOpData && m_pCurOpData->opId == cmd_connect)
      {
            CHttpConnectOpData *pData = static_cast<CHttpConnectOpData *>(m_pCurOpData);
            pData->host = address->IPAddress();
            return DoInternalConnect();
      }

      if (m_pAddress)
            delete m_pAddress;
      m_pAddress = new wxIPV4address(*address);

      // Now we got the server's IP address for later use, e.g. transfer commands
      ResetOperation(FZ_REPLY_OK);
      return FZ_REPLY_OK;
}

bool CHttpControlSocket::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;
                  }

                  CHttpFileTransferOpData *pData = static_cast<CHttpFileTransferOpData *>(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:
                        ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
                        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
                        {
                              ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
                              break;
                        }
                        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;
      default:
            LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown request %d"), pNotification->GetRequestID());
            ResetOperation(FZ_REPLY_INTERNALERROR);
            return false;
      }

      return true;
}

void CHttpControlSocket::OnReceive()
{
      if (!m_pRecvBuffer)
      {
            m_pRecvBuffer = new char[m_recvBufferLen];
            m_recvBufferPos = 0;
      }

      unsigned int len = m_recvBufferLen - m_recvBufferPos;
      Read(m_pRecvBuffer + m_recvBufferPos, len);
      if (Error())
      {
            if (LastError() != wxSOCKET_WOULDBLOCK)
                  ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
            return;
      }
            
      int read;
      if (!(read = LastCount()))
      {
            ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
            return;
      }

      m_pEngine->SetActive(true);

      if (!m_pCurOpData || m_pCurOpData->opId == cmd_connect)
      {
            // Just ignore all further data
            m_recvBufferPos = 0;
            return;
      }

      m_recvBufferPos += read;

      if (!m_pHttpOpData->m_gotHeader)
            ParseHeader(m_pHttpOpData);
      else if (m_pHttpOpData->m_transferEncoding == CHttpOpData::chunked)
            OnChunkedData(m_pHttpOpData);
      else
      {
            m_pHttpOpData->m_receivedData += m_recvBufferPos;
            ProcessData(m_pRecvBuffer, m_recvBufferPos);
            m_recvBufferPos = 0;
      }
}

void CHttpControlSocket::OnConnect()
{
      LogMessage(Status, _("Connection established, sending HTTP request"));
      ResetOperation(FZ_REPLY_OK);
}

enum filetransferStates
{
      filetransfer_init = 0,
      filetransfer_waitfileexists,
      filetransfer_transfer
};

int CHttpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath,
                                            const wxString &remoteFile, bool download,
                                            const CFileTransferCommand::t_transferSettings& transferSettings)
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::FileTransfer()"));
      
      LogMessage(Status, _("Downloading %s"), remotePath.FormatFilename(remoteFile).c_str());

      if (!download)
      {
            ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
            return FZ_REPLY_ERROR;
      }

      if (m_pCurOpData)
      {
            LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("deleting nonzero pData"));
            delete m_pCurOpData;
      }

      CHttpFileTransferOpData *pData = new CHttpFileTransferOpData;
      m_pCurOpData = pData;
      m_pHttpOpData = pData;

      pData->localFile = localFile;
      pData->remotePath = remotePath;
      pData->remoteFile = remoteFile;
      pData->download = download;

      if (localFile != _T(""))
      {
            pData->opState = filetransfer_waitfileexists;
            int res = CheckOverwriteFile();
            if (res != FZ_REPLY_OK)
                  return res;

            pData->opState = filetransfer_transfer;

            pData->pFile = new wxFile();

            // Create local directory
            wxFileName fn(pData->localFile);
            wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);

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

      int res = InternalConnect(m_pAddress->IPAddress(), m_pCurrentServer->GetPort());
      if (res != FZ_REPLY_OK)
            return res;

      return FileTransferSend();
}

int CHttpControlSocket::FileTransferSend(int prevResult /*=FZ_RESULT_OK*/)
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::FileTransferSend(%d)"), prevResult);

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

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

      if (prevResult != FZ_REPLY_OK)
      {
            ResetOperation(prevResult);
            return FZ_REPLY_ERROR;
      }

      if (pData->opState == filetransfer_waitfileexists)
      {
            pData->opState = filetransfer_transfer;
            pData->pFile = new wxFile();

            // Create local directory
            wxFileName fn(pData->localFile);
            wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL);

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

            int res = InternalConnect(m_pAddress->IPAddress(), m_pCurrentServer->GetPort());
            if (res != FZ_REPLY_OK)
                  return res;
      }

      wxString location;
      wxString hostWithPort;
      if (pData->m_newLocation == _T(""))
      {
            location = _T("http://") + m_pCurrentServer->GetHost() + pData->remotePath.FormatFilename(pData->remoteFile).c_str();
            hostWithPort = wxString::Format(_T("%s:%d"), m_pCurrentServer->GetHost().c_str(), m_pCurrentServer->GetPort());
      }
      else
      {
            location = pData->m_newLocation;
            hostWithPort = pData->m_newHostWithPort;
      }

      wxString action = wxString::Format(_T("GET %s HTTP/1.1"), location.c_str());
      LogMessageRaw(Command, action);

      wxString command = wxString::Format(_T("%s\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: close\r\n\r\n"), action.c_str(), hostWithPort.c_str(), wxString(PACKAGE_STRING, wxConvLocal).c_str());

      const wxWX2MBbuf str = command.mb_str();
      if (!Send(str, strlen(str)))
            return FZ_REPLY_ERROR;

      return FZ_REPLY_WOULDBLOCK;
}

int CHttpControlSocket::InternalConnect(const wxString& host, unsigned short port)
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::InternalConnect()"));

      CHttpConnectOpData* pData = new CHttpConnectOpData;
      pData->pNextOpData = m_pCurOpData;
      m_pCurOpData = pData;
      pData->port = port;

      if (!IsIpAddress(host))
      {
            LogMessage(Status, _("Resolving IP-Address for %s"), host.c_str());
            CAsyncHostResolver *resolver = new CAsyncHostResolver(m_pEngine, ConvertDomainName(host));
            m_pEngine->AddNewAsyncHostResolver(resolver);

            resolver->Create();
            resolver->Run();
            return FZ_REPLY_WOULDBLOCK;
      }
      
      pData->host = host;
      return DoInternalConnect();
}

int CHttpControlSocket::DoInternalConnect()
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::DoInternalConnect()"));

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

      CHttpConnectOpData *pData = static_cast<CHttpConnectOpData *>(m_pCurOpData);
      LogMessage(Status, _("Connecting to %s:%d..."), m_pAddress->IPAddress().c_str(), pData->port);

      if (m_pBackend)
            delete m_pBackend;
      m_pBackend = new CSocketBackend(this, this);

      wxIPV4address addr;
      addr.Hostname(pData->host);
      addr.Service(pData->port);

      bool res = wxSocketClient::Connect(addr, false);

      if (res)
            return FZ_REPLY_OK;
      else if (LastError() != wxSOCKET_WOULDBLOCK)
            return ResetOperation(FZ_REPLY_ERROR);

      return FZ_REPLY_WOULDBLOCK;
}

int CHttpControlSocket::FileTransferParseResponse(char* p, unsigned int len)
{
      LogMessage(Debug_Verbose, _T("CHttpControlSocket::FileTransferParseResponse(%p, %d)"), p, len);

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

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

      if (!p)
      {
            ResetOperation(FZ_REPLY_OK);
            return FZ_REPLY_OK;
      }

      if (!m_pTransferStatus)
      {
            InitTransferStatus(pData->m_totalSize.GetLo() + ((wxFileOffset)pData->m_totalSize.GetHi() << 32), 0, false);
            SetTransferStatusStartTime();
      }

      if (pData->localFile == _T(""))
      {
            char* q = new char[len];
            memcpy(q, p, len);
            m_pEngine->AddNotification(new CDataNotification(q, len));
      }
      else
      {
            wxASSERT(pData->pFile);

            if (pData->pFile->Write(p, len) != len)
            {
                  LogMessage(::Error, _("Failed to write to file %s"), pData->localFile.c_str());
                  ResetOperation(FZ_REPLY_ERROR);
                  return FZ_REPLY_ERROR;
            }
      }

      UpdateTransferStatus(len);

      return FZ_REPLY_WOULDBLOCK;
}

int CHttpControlSocket::ParseHeader(CHttpOpData* pData)
{
      // Parse the HTTP header.
      // We do just the neccessary parsing and silently ignore most header fields
      // Redirects are supported though if the server sends the Location field.

      while (true)
      {
            // Find line ending
            unsigned int i = 0;
            for (i = 0; (i + 1) < m_recvBufferPos; i++)
            {
                  if (m_pRecvBuffer[i] == '\r')
                  {
                        if (m_pRecvBuffer[i + 1] != '\n')
                        {
                              LogMessage(::Error, _("Malformed reply, server not sending proper lineendings"));
                              ResetOperation(FZ_REPLY_ERROR);
                              return FZ_REPLY_ERROR;
                        }
                        break;
                  }
            }
            if ((i + 1) >= m_recvBufferPos)
            {
                  if (m_recvBufferPos == m_recvBufferLen)
                  {
                        // We don't support header lines larger than 4096
                        LogMessage(::Error, _("Too long header line"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
                  return FZ_REPLY_WOULDBLOCK;
            }

            m_pRecvBuffer[i] = 0;
            const wxString& line = wxString(m_pRecvBuffer, wxConvLocal);
            if (line != _T(""))
                  LogMessageRaw(Response, line);

            if (pData->m_responseCode == -1)
            {
                  pData->m_responseString = line;
                  if (m_recvBufferPos < 16 || memcmp(m_pRecvBuffer, "HTTP/1.", 7))
                  {
                        // Invalid HTTP Status-Line
                        LogMessage(::Error, _("Invalid HTTP Response"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }

                  if (m_pRecvBuffer[9] < '1' || m_pRecvBuffer[9] > '5' ||
                        m_pRecvBuffer[10] < '0' || m_pRecvBuffer[10] > '9' ||
                        m_pRecvBuffer[11] < '0' || m_pRecvBuffer[11] > '9')
                  {
                        // Invalid response code
                        LogMessage(::Error, _("Invalid response code"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }

                  pData->m_responseCode = (m_pRecvBuffer[9] - '0') * 100 + (m_pRecvBuffer[10] - '0') * 10 + m_pRecvBuffer[11] - '0';

                  if (pData->m_responseCode >= 400)
                  {
                        // Failed request
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }

                  if (pData->m_responseCode == 305)
                  {
                        // Unsupported redirect
                        LogMessage(::Error, _("Unsuppored redirect"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
            }
            else
            {
                  if (!i)
                  {
                        // End of header, data from now on

                        // Redirect if neccessary
                        if (pData->m_responseCode >= 300)
                        {
                              if (pData->m_redirectionCount++ == 5)
                              {
                                    LogMessage(::Error, _("Too many redirects"));
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }

                              ResetSocket();
                              ResetHttpData(pData);

                              wxString host;
                              int pos;
                              if ((pos = pData->m_newLocation.Find(_T("://"))) != -1)
                                    host = pData->m_newLocation.Mid(pos + 3);
                              else
                                    host = pData->m_newLocation;

                              if ((pos = host.Find(_T("/"))) != -1)
                                    host = host.Left(pos);

                              unsigned long port;
                              if ((pos = host.Find(':', true)) != -1)
                              {
                                    wxString strport = host.Mid(pos + 1);
                                    if (!strport.ToULong(&port) || port < 1 || port > 65535)
                                          port = 80;
                                    host = host.Left(pos);
                              }
                              else
                                    port = 80;

                              if (host == _T(""))
                              {
                                    // Unsupported redirect
                                    LogMessage(::Error, _("Redirection to invalid address"));
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }
                              pData->m_newHostWithPort = wxString::Format(_T("%s:%d"), host.c_str(), port);

                              return InternalConnect(host, port);
                        }

                        pData->m_gotHeader = true;

                        memmove(m_pRecvBuffer, m_pRecvBuffer + 2, m_recvBufferPos - 2);
                        m_recvBufferPos -= 2;

                        if (m_recvBufferPos)
                        {
                              int res;
                              if (pData->m_transferEncoding == pData->chunked)
                                    res = OnChunkedData(pData);
                              else
                              {
                                    pData->m_receivedData += m_recvBufferPos;
                                    res = ProcessData(m_pRecvBuffer, m_recvBufferPos);
                                    m_recvBufferPos = 0;
                              }
                              return res;
                        }
                        
                        return FZ_REPLY_WOULDBLOCK;
                  }
                  if (m_recvBufferPos > 12 && !memcmp(m_pRecvBuffer, "Location: ", 10))
                  {
                        pData->m_newLocation = wxString(m_pRecvBuffer + 10, wxConvLocal);
                  }
                  else if (m_recvBufferPos > 21 && !memcmp(m_pRecvBuffer, "Transfer-Encoding: ", 19))
                  {
                        if (!strcmp(m_pRecvBuffer + 19, "chunked"))
                              pData->m_transferEncoding = CHttpOpData::chunked;
                        else if (!strcmp(m_pRecvBuffer + 19, "identity"))
                              pData->m_transferEncoding = CHttpOpData::identity;
                        else
                              pData->m_transferEncoding = CHttpOpData::unknown;
                  }
                  else if (i > 16 && !memcmp(m_pRecvBuffer, "Content-Length: ", 16))
                  {
                        pData->m_totalSize = 0;
                        char* p = m_pRecvBuffer + 16;
                        while (*p)
                        {
                              if (*p < '0' || *p > '9')
                              {
                                    LogMessage(::Error, _("Malformed header: %s"), _("Invalid Content-Length"));
                                    ResetOperation(FZ_REPLY_ERROR);
                                    return FZ_REPLY_ERROR;
                              }
                              pData->m_totalSize = pData->m_totalSize * 10 + *p++ - '0';
                        }
                  }
            }

            memmove(m_pRecvBuffer, m_pRecvBuffer + i + 2, m_recvBufferPos - i - 2);
            m_recvBufferPos -= i + 2;

            if (!m_recvBufferPos)
                  break;
      }

      return FZ_REPLY_WOULDBLOCK;
}

int CHttpControlSocket::OnChunkedData(CHttpOpData* pData)
{
      char* p = m_pRecvBuffer;
      unsigned int len = m_recvBufferPos;

      while (true)
      {
            if (pData->m_chunkData.size != 0)
            {
                  unsigned int dataLen = len;
                  if (pData->m_chunkData.size < len)
                        dataLen = pData->m_chunkData.size.GetLo();
                  pData->m_receivedData += dataLen;
                  int res = ProcessData(p, dataLen);
                  if (res != FZ_REPLY_WOULDBLOCK)
                        return res;

            pData->m_chunkData.size -= dataLen;
                  p += dataLen;
                  len -= dataLen;

                  if (pData->m_chunkData.size == 0)
                        pData->m_chunkData.terminateChunk = true;

                  if (!len)
                        break;
            }

            // Find line ending
            unsigned int i = 0;
            for (i = 0; (i + 1) < len; i++)
            {
                  if (p[i] == '\r')
                  {
                        if (p[i + 1] != '\n')
                        {
                              LogMessage(::Error, _("Malformed chunk data: %s"), _("Wrong lineendings"));
                              ResetOperation(FZ_REPLY_ERROR);
                              return FZ_REPLY_ERROR;
                        }
                        break;
                  }
            }
            if ((i + 1) >= len)
            {
                  if (len == m_recvBufferLen)
                  {
                        // We don't support lines larger than 4096
                        LogMessage(::Error, _("Malformed chunk data: %s"), _("Line length exceeded"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
                  break;
            }

            p[i] = 0;

            if (pData->m_chunkData.terminateChunk)
            {
                  if (i)
                  {
                        // The chunk data has to end with CRLF. If i is nonzero,
                        // it didn't end with just CRLF.
                        LogMessage(::Error, _("Malformed chunk data: %s"), _("Chunk data improperly terminated"));
                        ResetOperation(FZ_REPLY_ERROR);
                        return FZ_REPLY_ERROR;
                  }
                  pData->m_chunkData.terminateChunk = false;
            }
            else if (pData->m_chunkData.getTrailer)
            {
                  if (!i)
                  {
                        // We're done
                        return ProcessData(0, 0);
                  }
                  
                  // Ignore the trailer
            }
            else
            {
                  // Read chunk size
                  char* q = p;
                  while (*q)
                  {
                        if (*q >= '0' && *q <= '9')
                        {
                              pData->m_chunkData.size *= 16;
                              pData->m_chunkData.size += *q - '0';
                        }
                        else if (*q >= 'A' && *q <= 'F')
                        {
                              pData->m_chunkData.size *= 10;
                              pData->m_chunkData.size += *q - 'A' + 10;
                        }
                        else if (*q >= 'a' && *q <= 'f')
                        {
                              pData->m_chunkData.size *= 10;
                              pData->m_chunkData.size += *q - 'a' + 10;
                        }
                        else if (*q == ';' || *q == ' ')
                              break;
                        else
                        {
                              // Invalid size
                              LogMessage(::Error, _("Malformed chunk data: %s"), _("Invalid chunk size"));
                              ResetOperation(FZ_REPLY_ERROR);
                              return FZ_REPLY_ERROR;
                        }
                        q++;
                  }
                  if (pData->m_chunkData.size == 0)
                        pData->m_chunkData.getTrailer = true;
            }

            p += i + 2;
            len -= i + 2;

            if (!len)
                  break;
      }

      if (p != m_pRecvBuffer)
      {
            memmove(m_pRecvBuffer, p, len);
            m_recvBufferPos = len;
      }

      return FZ_REPLY_WOULDBLOCK;
}

int CHttpControlSocket::ResetOperation(int nErrorCode)
{
      if (m_pCurOpData && m_pCurOpData->opId == cmd_transfer)
      {
            CHttpFileTransferOpData *pData = static_cast<CHttpFileTransferOpData *>(m_pCurOpData);
            delete pData->pFile;
            pData->pFile = 0;
      }

      if (!m_pCurOpData || !m_pCurOpData->pNextOpData)
      {
            if (nErrorCode == FZ_REPLY_OK)
                  LogMessage(Status, _("Disconnected from server"));
            else
                  LogMessage(::Error, _("Disconnected from server"));
            ResetSocket();
            m_pHttpOpData = 0;
      }

      return CControlSocket::ResetOperation(nErrorCode);
}

void CHttpControlSocket::OnClose()
{
      char tmp[1];
      for (Peek(tmp, 1); !Error() && LastCount(); Peek(tmp, 1))
            OnReceive();

      // HTTP socket isn't connected outside operations
      if (!m_pCurOpData)
            return;

      if (m_pCurOpData->pNextOpData)
      {
            ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
            return;
      }

      if (!m_pHttpOpData->m_gotHeader)
      {
            ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
            return;
      }

      if (m_pHttpOpData->m_transferEncoding == CHttpOpData::chunked)
      {
            if (!m_pHttpOpData->m_chunkData.getTrailer)
            {
                  ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
                  return;
            }
      }
      else
      {
            if (m_pHttpOpData->m_totalSize != -1 && m_pHttpOpData->m_receivedData != m_pHttpOpData->m_totalSize)
            {
                  ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED);
                  return;
            }
      }

      ProcessData(0, 0);
}

void CHttpControlSocket::ResetHttpData(CHttpOpData* pData)
{
      wxASSERT(pData);

      delete m_pRecvBuffer;
      m_pRecvBuffer = 0;

      pData->m_gotHeader = false;
      pData->m_responseCode = -1;
      pData->m_transferEncoding = CHttpOpData::unknown;

      pData->m_chunkData.getTrailer = false;
      pData->m_chunkData.size = 0;
      pData->m_chunkData.terminateChunk = false;

      pData->m_totalSize = -1;
      pData->m_receivedData = 0;
}

int CHttpControlSocket::ProcessData(char* p, int len)
{
      int res;
      enum Command commandId = GetCurrentCommandId();
      switch (commandId)
      {
      case cmd_transfer:
            res = FileTransferParseResponse(p, len);
            break;
      default:
            LogMessage(Debug_Warning, _T("No action for parsing data for command %d"), (int)commandId);
            ResetOperation(FZ_REPLY_INTERNALERROR);
            res = FZ_REPLY_ERROR;
            break;
      }

      wxASSERT(p || !m_pCurOpData);

      return res;
}

Generated by  Doxygen 1.6.0   Back to index