#include #include "ControlSocket.h" #include "engineprivate.h" #include "httpcontrolsocket.h" #include "tlssocket.h" #include #include #include #define FZ_REPLY_REDIRECTED FZ_REPLY_ALREADYCONNECTED // 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 CConnectOpData { public: CHttpConnectOpData() : tls(false) { } virtual ~CHttpConnectOpData() { } bool tls; }; class CHttpOpData { public: CHttpOpData(COpData* pOpData) : m_pOpData(pOpData) { } virtual ~CHttpOpData() = default; bool m_gotHeader{}; int m_responseCode{-1}; wxString m_responseString; wxURI m_newLocation; int m_redirectionCount{}; int64_t m_totalSize{-1}; int64_t m_receivedData{}; COpData* m_pOpData; enum transferEncodings { identity, chunked, unknown }; transferEncodings m_transferEncoding{unknown}; struct t_chunkData { bool getTrailer{}; bool terminateChunk{}; int64_t size{}; } m_chunkData; }; class CHttpFileTransferOpData : public CFileTransferOpData, public CHttpOpData { public: CHttpFileTransferOpData(bool is_download, const wxString& local_file, const wxString& remote_file, const CServerPath& remote_path) : CFileTransferOpData(is_download, local_file, remote_file, remote_path), CHttpOpData(this) { pFile = 0; } virtual ~CHttpFileTransferOpData() { delete pFile; } wxFile* pFile; }; CHttpControlSocket::CHttpControlSocket(CFileZillaEnginePrivate & engine) : CRealControlSocket(engine) { m_pRecvBuffer = 0; m_recvBufferPos = 0; m_pTlsSocket = 0; m_pHttpOpData = 0; } CHttpControlSocket::~CHttpControlSocket() { remove_handler(); DoClose(); delete [] m_pRecvBuffer; } int CHttpControlSocket::SendNextCommand() { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::SendNextCommand()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("SendNextCommand called without active operation")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (m_pCurOpData->waitForAsyncRequest) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Waiting for async request, ignoring SendNextCommand")); return FZ_REPLY_WOULDBLOCK; } switch (m_pCurOpData->opId) { case Command::transfer: return FileTransferSend(); default: LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } return FZ_REPLY_ERROR; } int CHttpControlSocket::ContinueConnect() { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Verbose, _T("CHttpControlSocket::ContinueConnect() &engine_=%p"), &engine_); if (GetCurrentCommandId() != Command::connect || !m_pCurrentServer) { LogMessage(MessageType::Debug_Warning, _T("Invalid context for call to ContinueConnect(), cmd=%d, m_pCurrentServer=%p"), GetCurrentCommandId(), m_pCurrentServer); return DoClose(FZ_REPLY_INTERNALERROR); } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } bool CHttpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification) { if (m_pCurOpData) { if (!m_pCurOpData->waitForAsyncRequest) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID()); return false; } m_pCurOpData->waitForAsyncRequest = false; } switch (pNotification->GetRequestID()) { case reqId_fileexists: { if (!m_pCurOpData || m_pCurOpData->opId != Command::transfer) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %f"), pNotification->GetRequestID()); return false; } CFileExistsNotification *pFileExistsNotification = static_cast(pNotification); return SetFileExistsAction(pFileExistsNotification); } break; case reqId_certificate: { if (!m_pTlsSocket || m_pTlsSocket->GetState() != CTlsSocket::TlsState::verifycert) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID()); return false; } CCertificateNotification* pCertificateNotification = static_cast(pNotification); m_pTlsSocket->TrustCurrentCert(pCertificateNotification->m_trusted); } break; default: LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown request %d"), pNotification->GetRequestID()); ResetOperation(FZ_REPLY_INTERNALERROR); return false; } return true; } void CHttpControlSocket::OnReceive() { DoReceive(); } int CHttpControlSocket::DoReceive() { do { const enum CSocket::SocketState state = m_pSocket->GetState(); if (state != CSocket::connected && state != CSocket::closing) return 0; if (!m_pRecvBuffer) { m_pRecvBuffer = new char[m_recvBufferLen]; m_recvBufferPos = 0; } unsigned int len = m_recvBufferLen - m_recvBufferPos; int error; int read = m_pBackend->Read(m_pRecvBuffer + m_recvBufferPos, len, error); if (read == -1) { if (error != EAGAIN) { ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED); } return 0; } SetActive(CFileZillaEngine::recv); if (!m_pCurOpData || m_pCurOpData->opId == Command::connect) { // Just ignore all further data m_recvBufferPos = 0; return 0; } m_recvBufferPos += read; if (!m_pHttpOpData->m_gotHeader) { if (!read) { ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED); return 0; } int res = ParseHeader(m_pHttpOpData); if ((res & FZ_REPLY_REDIRECTED) == FZ_REPLY_REDIRECTED) return FZ_REPLY_REDIRECTED; if (res != FZ_REPLY_WOULDBLOCK) return 0; } else if (m_pHttpOpData->m_transferEncoding == CHttpOpData::chunked) { if (!read) { ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED); return 0; } OnChunkedData(m_pHttpOpData); } else { if (!read) { wxASSERT(!m_recvBufferPos); ProcessData(0, 0); return 0; } else { m_pHttpOpData->m_receivedData += m_recvBufferPos; ProcessData(m_pRecvBuffer, m_recvBufferPos); m_recvBufferPos = 0; } } } while (m_pSocket); return 0; } void CHttpControlSocket::OnConnect() { wxASSERT(GetCurrentCommandId() == Command::connect); CHttpConnectOpData *pData = static_cast(m_pCurOpData); if (pData->tls) { if (!m_pTlsSocket) { LogMessage(MessageType::Status, _("Connection established, initializing TLS...")); delete m_pBackend; m_pTlsSocket = new CTlsSocket(this, *m_pSocket, this); m_pBackend = m_pTlsSocket; if (!m_pTlsSocket->Init()) { LogMessage(MessageType::Error, _("Failed to initialize TLS.")); DoClose(); return; } int res = m_pTlsSocket->Handshake(); if (res == FZ_REPLY_ERROR) DoClose(); } else { LogMessage(MessageType::Status, _("TLS connection established, sending HTTP request")); ResetOperation(FZ_REPLY_OK); } return; } else { LogMessage(MessageType::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&) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::FileTransfer()")); LogMessage(MessageType::Status, _("Downloading %s"), remotePath.FormatFilename(remoteFile)); if (!download) { ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED); return FZ_REPLY_ERROR; } if (m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("deleting nonzero pData")); delete m_pCurOpData; } CHttpFileTransferOpData *pData = new CHttpFileTransferOpData(download, localFile, remoteFile, remotePath); m_pCurOpData = pData; m_pHttpOpData = pData; m_current_uri = wxURI(m_pCurrentServer->FormatServer() + pData->remotePath.FormatFilename(pData->remoteFile)); if (!localFile.empty()) { pData->localFileSize = fz::local_filesys::get_size(fz::to_native(pData->localFile)); pData->opState = filetransfer_waitfileexists; int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; pData->opState = filetransfer_transfer; res = OpenFile(pData); if( res != FZ_REPLY_OK ) return res; } else pData->opState = filetransfer_transfer; int res = InternalConnect(m_pCurrentServer->GetHost(), m_pCurrentServer->GetPort(), m_pCurrentServer->GetProtocol() == HTTPS); if (res != FZ_REPLY_OK) return res; return FileTransferSend(); } int CHttpControlSocket::FileTransferSubcommandResult(int prevResult) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::FileTransferSubcommandResult(%d)"), prevResult); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (prevResult != FZ_REPLY_OK) { ResetOperation(prevResult); return FZ_REPLY_ERROR; } return FileTransferSend(); } int CHttpControlSocket::FileTransferSend() { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::FileTransferSend()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if( !m_current_uri.HasScheme() || !m_current_uri.HasServer() || !m_current_uri.HasPath() ) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Invalid URI: %s"), m_current_uri.BuildURI()); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CHttpFileTransferOpData *pData = static_cast(m_pCurOpData); if (pData->opState == filetransfer_waitfileexists) { pData->opState = filetransfer_transfer; int res = OpenFile(pData); if( res != FZ_REPLY_OK) return res; res = InternalConnect(m_pCurrentServer->GetHost(), m_pCurrentServer->GetPort(), m_pCurrentServer->GetProtocol() == HTTPS); if (res != FZ_REPLY_OK) return res; } wxString location = m_current_uri.GetPath(); if( m_current_uri.HasQuery() ) { location += _T("?") + m_current_uri.GetQuery(); } wxString action = wxString::Format(_T("GET %s HTTP/1.1"), location ); LogMessageRaw(MessageType::Command, action); wxString hostWithPort = m_current_uri.GetServer(); if( m_current_uri.HasPort() ) { hostWithPort += _T(":") + m_current_uri.GetPort(); } wxString command = wxString::Format(_T("%s\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: close\r\n"), action, hostWithPort, wxString(PACKAGE_STRING, wxConvLocal)); if( pData->resume ) { command += wxString::Format(_T("Range: bytes=%") + wxString(wxFileOffsetFmtSpec) + _T("d-\r\n"), pData->localFileSize); } command += _T("\r\n"); const wxWX2MBbuf str = command.mb_str(); if (!Send(str, strlen(str))) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CHttpControlSocket::InternalConnect(wxString host, unsigned short port, bool tls) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::InternalConnect()")); CHttpConnectOpData* pData = new CHttpConnectOpData; pData->pNextOpData = m_pCurOpData; m_pCurOpData = pData; pData->port = port; pData->tls = tls; if (fz::get_address_type(host.ToStdWstring()) == fz::address_type::unknown) { LogMessage(MessageType::Status, _("Resolving address of %s"), host); } pData->host = host; return DoInternalConnect(); } int CHttpControlSocket::DoInternalConnect() { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::DoInternalConnect()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CHttpConnectOpData *pData = static_cast(m_pCurOpData); delete m_pBackend; m_pBackend = new CSocketBackend(this, *m_pSocket, engine_.GetRateLimiter()); int res = m_pSocket->Connect(pData->host, pData->port); if (!res) return FZ_REPLY_OK; if (res && res != EINPROGRESS) return ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_WOULDBLOCK; } int CHttpControlSocket::FileTransferParseResponse(char* p, unsigned int len) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::FileTransferParseResponse(%p, %d)"), p, len); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CHttpFileTransferOpData *pData = static_cast(m_pCurOpData); if (!p) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } if (engine_.transfer_status_.empty()) { engine_.transfer_status_.Init(pData->m_totalSize, 0, false); engine_.transfer_status_.SetStartTime(); } if (pData->localFile.empty()) { char* q = new char[len]; memcpy(q, p, len); engine_.AddNotification(new CDataNotification(q, len)); } else { wxASSERT(pData->pFile); if (pData->pFile->Write(p, len) != len) { LogMessage(MessageType::Error, _("Failed to write to file %s"), pData->localFile); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } engine_.transfer_status_.Update(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. for (;;) { // 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(MessageType::Error, _("Malformed reply, server not sending proper line endings")); 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(MessageType::Error, _("Too long header line")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return FZ_REPLY_WOULDBLOCK; } m_pRecvBuffer[i] = 0; wxString const line = wxString(m_pRecvBuffer, wxConvLocal); if (!line.empty()) LogMessageRaw(MessageType::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(MessageType::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(MessageType::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 == 416 ) { CHttpFileTransferOpData* pTransfer = static_cast(pData->m_pOpData); if( pTransfer->resume ) { // Sad, the server does not like our attempt to resume. // Get full file instead. pTransfer->resume = false; int res = OpenFile(pTransfer); if( res != FZ_REPLY_OK ) { return res; } pData->m_newLocation = m_current_uri; pData->m_responseCode = 300; } } if (pData->m_responseCode >= 400) { // Failed request ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (pData->m_responseCode == 305) { // Unsupported redirect LogMessage(MessageType::Error, _("Unsupported 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++ == 6) { LogMessage(MessageType::Error, _("Too many redirects")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } ResetSocket(); ResetHttpData(pData); if( !pData->m_newLocation.HasScheme() || !pData->m_newLocation.HasServer() || !pData->m_newLocation.HasPath() ) { LogMessage(MessageType::Error, _("Redirection to invalid or unsupported URI: %s"), m_current_uri.BuildURI()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } enum ServerProtocol protocol = CServer::GetProtocolFromPrefix(pData->m_newLocation.GetScheme()); if( protocol != HTTP && protocol != HTTPS ) { LogMessage(MessageType::Error, _("Redirection to invalid or unsupported address: %s"), pData->m_newLocation.BuildURI()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } long port = CServer::GetDefaultPort(protocol); if( pData->m_newLocation.HasPort() && (!pData->m_newLocation.GetPort().ToLong(&port) || port < 1 || port > 65535) ) { LogMessage(MessageType::Error, _("Redirection to invalid or unsupported address: %s"), pData->m_newLocation.BuildURI()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } m_current_uri = pData->m_newLocation; // International domain names wxString host = ConvertDomainName(m_current_uri.GetServer()); int res = InternalConnect(host, static_cast(port), protocol == HTTPS); if (res == FZ_REPLY_WOULDBLOCK) res |= FZ_REPLY_REDIRECTED; return res; } if( pData->m_pOpData && pData->m_pOpData->opId == Command::transfer) { CHttpFileTransferOpData* pTransfer = static_cast(pData->m_pOpData); if( pTransfer->resume && pData->m_responseCode != 206 ) { pTransfer->resume = false; int res = OpenFile(pTransfer); if( res != FZ_REPLY_OK ) { return res; } } } 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 = wxURI(wxString(m_pRecvBuffer + 10, wxConvLocal)); pData->m_newLocation.Resolve(m_current_uri); } 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(MessageType::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; for (;;) { if (pData->m_chunkData.size != 0) { unsigned int dataLen = len; if (pData->m_chunkData.size < len) dataLen = static_cast(pData->m_chunkData.size); 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(MessageType::Error, _("Malformed chunk data: %s"), _("Wrong line endings")); 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(MessageType::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(MessageType::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 for( char* q = p; *q && *q != ';' && *q != ' '; ++q ) { pData->m_chunkData.size *= 16; if (*q >= '0' && *q <= '9') { pData->m_chunkData.size += *q - '0'; } else if (*q >= 'A' && *q <= 'F') { pData->m_chunkData.size += *q - 'A' + 10; } else if (*q >= 'a' && *q <= 'f') { pData->m_chunkData.size += *q - 'a' + 10; } else { // Invalid size LogMessage(MessageType::Error, _("Malformed chunk data: %s"), _("Invalid chunk size")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } 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 == Command::transfer) { CHttpFileTransferOpData *pData = static_cast(m_pCurOpData); delete pData->pFile; pData->pFile = 0; } if (!m_pCurOpData || !m_pCurOpData->pNextOpData) { if (m_pBackend) { if (nErrorCode == FZ_REPLY_OK) LogMessage(MessageType::Status, _("Disconnected from server")); else LogMessage(MessageType::Error, _("Disconnected from server")); } ResetSocket(); m_pHttpOpData = 0; } return CControlSocket::ResetOperation(nErrorCode); } void CHttpControlSocket::OnClose(int error) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::OnClose(%d)"), error); if (error) { LogMessage(MessageType::Error, _("Disconnected from server: %s"), CSocket::GetErrorDescription(error)); ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED); return; } // 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; Command commandId = GetCurrentCommandId(); switch (commandId) { case Command::transfer: res = FileTransferParseResponse(p, len); break; default: LogMessage(MessageType::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; } int CHttpControlSocket::ParseSubcommandResult(int prevResult) { LogMessage(MessageType::Debug_Verbose, _T("CHttpControlSocket::SendNextCommand(%d)"), prevResult); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("SendNextCommand called without active operation")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } switch (m_pCurOpData->opId) { case Command::transfer: return FileTransferSubcommandResult(prevResult); default: LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } return FZ_REPLY_ERROR; } int CHttpControlSocket::Disconnect() { DoClose(); return FZ_REPLY_OK; } int CHttpControlSocket::OpenFile(CHttpFileTransferOpData* pData) { delete pData->pFile; pData->pFile = new wxFile(); CreateLocalDir(pData->localFile); if (!pData->pFile->Open(pData->localFile, pData->resume ? wxFile::write_append : wxFile::write)) { LogMessage(MessageType::Error, _("Failed to open \"%s\" for writing"), pData->localFile); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } wxFileOffset end = pData->pFile->SeekEnd(); if (!end) { pData->resume = false; } pData->localFileSize = fz::local_filesys::get_size(fz::to_native(pData->localFile)); return FZ_REPLY_OK; }