#include "FileZilla.h" #include "logging_private.h" #include "ControlSocket.h" #include #include "asynchostresolver.h" #include "directorycache.h" DECLARE_EVENT_TYPE(fzOBTAINLOCK, -1) DEFINE_EVENT_TYPE(fzOBTAINLOCK) #ifdef _DEBUG #define new DEBUG_NEW #endif std::list CControlSocket::m_lockInfoList; BEGIN_EVENT_TABLE(CControlSocket, wxEvtHandler) EVT_TIMER(wxID_ANY, CControlSocket::OnTimer) EVT_COMMAND(wxID_ANY, fzOBTAINLOCK, CControlSocket::OnObtainLock) END_EVENT_TABLE(); COpData::COpData(enum Command op_Id) : opId(op_Id) { opState = 0; pNextOpData = 0; waitForAsyncRequest = false; } COpData::~COpData() { delete pNextOpData; } CControlSocket::CControlSocket(CFileZillaEnginePrivate *pEngine) : CLogging(pEngine) { m_pEngine = pEngine; m_pCurOpData = 0; m_pCurrentServer = 0; m_pTransferStatus = 0; m_transferStatusSendState = 0; m_pCSConv = 0; m_useUTF8 = false; m_timer.SetOwner(this); m_closed = false; m_invalidateCurrentPath = false; } CControlSocket::~CControlSocket() { DoClose(); delete m_pCSConv; m_pCSConv = 0; } int CControlSocket::Disconnect() { LogMessage(Status, _("Disconnected from server")); DoClose(); return FZ_REPLY_OK; } enum Command CControlSocket::GetCurrentCommandId() const { if (m_pCurOpData) return m_pCurOpData->opId; return m_pEngine->GetCurrentCommandId(); } int CControlSocket::ResetOperation(int nErrorCode) { LogMessage(Debug_Verbose, _T("CControlSocket::ResetOperation(%d)"), nErrorCode); if (nErrorCode & FZ_REPLY_WOULDBLOCK) { LogMessage(::Debug_Warning, _T("ResetOperation with FZ_REPLY_WOULDBLOCK in nErrorCode (%d)"), nErrorCode); } if (m_pCurOpData && m_pCurOpData->opId != cmd_rawtransfer) { UnlockCache(); } if (m_pCurOpData && m_pCurOpData->pNextOpData) { COpData *pNext = m_pCurOpData->pNextOpData; m_pCurOpData->pNextOpData = 0; delete m_pCurOpData; m_pCurOpData = pNext; if (nErrorCode == FZ_REPLY_OK || nErrorCode == FZ_REPLY_ERROR || nErrorCode == FZ_REPLY_CRITICALERROR) { return SendNextCommand(nErrorCode); } else return ResetOperation(nErrorCode); } if ((nErrorCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR) LogMessage(::Error, _("Critical error")); if (m_pCurOpData) { const enum Command commandId = m_pCurOpData->opId; switch (commandId) { case cmd_none: break; case cmd_connect: if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) LogMessage(::Error, _("Connection attempt interrupted by user")); else if (nErrorCode != FZ_REPLY_OK) LogMessage(::Error, _("Could not connect to server")); break; case cmd_list: if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) LogMessage(::Error, _("Directory listing aborted by user")); else if (nErrorCode != FZ_REPLY_OK) LogMessage(::Error, _("Failed to retrieve directory listing")); else LogMessage(Status, _("Directory listing successful")); break; case cmd_transfer: { CFileTransferOpData *pData = static_cast(m_pCurOpData); if (!pData->download && pData->transferInitiated) { if (!m_pCurrentServer) LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurrentServer is 0")); else { CDirectoryCache cache; bool updated = cache.UpdateFile(*m_pCurrentServer, pData->remotePath, pData->remoteFile, true, CDirectoryCache::file, (nErrorCode == FZ_REPLY_OK) ? pData->localFileSize : -1); if (updated) m_pEngine->SendDirectoryListingNotification(pData->remotePath, false, true, false); } } if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) LogMessage(::Error, _("Transfer aborted by user")); else if (nErrorCode == FZ_REPLY_OK) LogMessage(Status, _("File transfer successful")); } break; default: if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) LogMessage(::Error, _("Interrupted by user")); break; } delete m_pCurOpData; m_pCurOpData = 0; } ResetTransferStatus(); SetWait(false); if (m_invalidateCurrentPath) { m_CurrentPath.Clear(); m_invalidateCurrentPath = false; } return m_pEngine->ResetOperation(nErrorCode); } int CControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/) { if (m_closed) { wxASSERT(!m_pCurOpData); return nErrorCode; } m_closed = true; nErrorCode = ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED | nErrorCode); delete m_pCurrentServer; m_pCurrentServer = 0; return nErrorCode; } wxString CControlSocket::ConvertDomainName(wxString domain) { const wxWCharBuffer buffer = wxConvCurrent->cWX2WC(domain); int len = 0; while (buffer.data()[len]) len++; char *utf8 = new char[len * 2 + 2]; wxMBConvUTF8 conv; conv.WC2MB(utf8, buffer, len * 2 + 2); char *output; if (idna_to_ascii_8z(utf8, &output, IDNA_ALLOW_UNASSIGNED)) { delete [] utf8; LogMessage(::Debug_Warning, _T("Could not convert domain name")); return domain; } delete [] utf8; wxString result = wxConvCurrent->cMB2WX(output); free(output); return result; } void CControlSocket::Cancel() { if (GetCurrentCommandId() != cmd_none) { if (GetCurrentCommandId() == cmd_connect) DoClose(FZ_REPLY_CANCELED); else ResetOperation(FZ_REPLY_CANCELED); } } void CControlSocket::ResetTransferStatus() { delete m_pTransferStatus; m_pTransferStatus = 0; m_pEngine->AddNotification(new CTransferStatusNotification(0)); m_transferStatusSendState = 0; } void CControlSocket::InitTransferStatus(wxFileOffset totalSize, wxFileOffset startOffset, bool list) { if (startOffset < 0) startOffset = 0; delete m_pTransferStatus; m_pTransferStatus = new CTransferStatus(); m_pTransferStatus->list = list; m_pTransferStatus->totalSize = totalSize; m_pTransferStatus->startOffset = startOffset; m_pTransferStatus->currentOffset = startOffset; } void CControlSocket::SetTransferStatusStartTime() { if (!m_pTransferStatus) return; m_pTransferStatus->started = wxDateTime::Now(); } void CControlSocket::UpdateTransferStatus(wxFileOffset transferredBytes) { if (!m_pTransferStatus) return; m_pTransferStatus->currentOffset += transferredBytes; if (!m_transferStatusSendState) m_pEngine->AddNotification(new CTransferStatusNotification(new CTransferStatus(*m_pTransferStatus))); m_transferStatusSendState = 2; } bool CControlSocket::GetTransferStatus(CTransferStatus &status, bool &changed) { if (!m_pTransferStatus) { changed = false; m_transferStatusSendState = 0; return false; } status = *m_pTransferStatus; if (m_transferStatusSendState == 2) { changed = true; m_transferStatusSendState = 1; return true; } else { changed = false; m_transferStatusSendState = 0; return true; } } const CServer* CControlSocket::GetCurrentServer() const { return m_pCurrentServer; } bool CControlSocket::ParsePwdReply(wxString reply, bool unquoted /*=false*/, const CServerPath& defaultPath /*=CServerPath()*/) { if (!unquoted) { int pos1 = reply.Find('"'); int pos2 = reply.Find('"', true); if (pos1 == -1 || pos1 >= pos2) { int pos1 = reply.Find('\''); int pos2 = reply.Find('\'', true); if (pos1 != -1 && pos1 < pos2) LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Broken server sending single-quoted path instead of double-quoted path.")); } if (pos1 == -1 || pos1 >= pos2) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No quoted path found in pwd reply, trying first token as path")); pos1 = reply.Find(' '); if (pos1 != -1) { pos2 = reply.Mid(pos1 + 1).Find(' '); if (pos2 == -1) pos2 = (int)reply.Length(); reply = reply.Mid(pos1 + 1, pos2 - pos1 - 1); } else reply = _T(""); } else reply = reply.Mid(pos1 + 1, pos2 - pos1 - 1); } m_CurrentPath.SetType(m_pCurrentServer->GetType()); if (reply == _T("") || !m_CurrentPath.SetPath(reply)) { if (reply != _T("")) LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Failed to parse returned path.")); else LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Server returned empty path.")); if (!defaultPath.IsEmpty()) { LogMessage(Debug_Warning, _T("Assuming path is '%s'."), defaultPath.GetPath().c_str()); m_CurrentPath = defaultPath; return true; } return false; } return true; } int CControlSocket::CheckOverwriteFile() { if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFileTransferOpData *pData = static_cast(m_pCurOpData); if (pData->download) { if (!wxFile::Exists(pData->localFile)) return 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); // Ignore entries with wrong case if (found && !matchedCase) found = false; if (!pData->download) { if (!found && pData->remoteFileSize == -1 && !pData->fileTime.IsValid()) return FZ_REPLY_OK; } CFileExistsNotification *pNotification = new CFileExistsNotification; pNotification->download = pData->download; pNotification->localFile = pData->localFile; pNotification->remoteFile = pData->remoteFile; pNotification->remotePath = pData->remotePath; pNotification->localSize = pData->localFileSize; pNotification->remoteSize = pData->remoteFileSize; pNotification->ascii = !pData->transferSettings.binary; wxStructStat buf; int result; result = wxStat(pData->localFile, &buf); if (!result) { pNotification->localTime = wxDateTime(buf.st_mtime); if (!pNotification->localTime.IsValid()) pNotification->localTime = wxDateTime(buf.st_ctime); } if (pData->fileTime.IsValid()) pNotification->remoteTime = pData->fileTime; if (found) { if (!pData->fileTime.IsValid()) { if (entry.hasDate) { pNotification->remoteTime = entry.time; pData->fileTime = entry.time; } } } pNotification->requestNumber = m_pEngine->GetNextAsyncRequestNumber(); pData->waitForAsyncRequest = true; m_pEngine->AddNotification(pNotification); return FZ_REPLY_WOULDBLOCK; } CFileTransferOpData::CFileTransferOpData() : COpData(cmd_transfer), localFileSize(-1), remoteFileSize(-1), tryAbsolutePath(false), resume(false), transferInitiated(false) { } CFileTransferOpData::~CFileTransferOpData() { } wxString CControlSocket::ConvToLocal(const char* buffer) { if (m_useUTF8) { wxChar* out = ConvToLocalBuffer(buffer, wxConvUTF8); if (out) { wxString str = out; delete [] out; return str; } // Fall back to local charset on error if (m_pCurrentServer->GetEncodingType() != ENCODING_UTF8) { LogMessage(Status, _("Invalid character sequence received, disabling UTF-8. Select UTF-8 option in site manager to force UTF-8.")); m_useUTF8 = false; } } if (m_pCSConv) { wxChar* out = ConvToLocalBuffer(buffer, *m_pCSConv); if (out) { wxString str = out; delete [] out; return str; } } wxCSConv conv(_T("ISO-8859-1")); wxString str = conv.cMB2WX(buffer); if (str == _T("")) str = wxConvCurrent->cMB2WX(buffer); return str; } wxChar* CControlSocket::ConvToLocalBuffer(const char* buffer, wxMBConv& conv) { size_t len = conv.MB2WC(0, buffer, 0); if (!len || len == wxCONV_FAILED) return 0; wchar_t* unicode = new wchar_t[len + 1]; conv.MB2WC(unicode, buffer, len + 1); #if wxUSE_UNICODE return unicode; #else len = wxConvCurrent->WC2MB(0, unicode, 0); if (!len) { delete [] unicode; return 0; } wxChar* output = new wxChar[len + 1]; wxConvCurrent->WC2MB(output, unicode, len + 1); delete [] unicode; return output; #endif } wxChar* CControlSocket::ConvToLocalBuffer(const char* buffer) { if (m_useUTF8) { wxChar* res = ConvToLocalBuffer(buffer, wxConvUTF8); if (res && *res) return res; // Fall back to local charset on error if (m_pCurrentServer->GetEncodingType() != ENCODING_UTF8) { LogMessage(Status, _("Invalid character sequence received, disabling UTF-8. Select UTF-8 option in site manager to force UTF-8.")); m_useUTF8 = false; } } if (m_pCSConv) { wxChar* res = ConvToLocalBuffer(buffer, *m_pCSConv); if (res && *res) return res; } // Fallback: Conversion using current locale #if wxUSE_UNICODE wxChar* res = ConvToLocalBuffer(buffer, *wxConvCurrent); #else // No conversion needed, just copy wxChar* res = new wxChar[strlen(buffer) + 1]; strcpy(res, buffer); #endif return res; } wxCharBuffer CControlSocket::ConvToServer(const wxString& str) { if (m_useUTF8) { #if wxUSE_UNICODE wxCharBuffer buffer = wxConvUTF8.cWX2MB(str); #else wxWCharBuffer unicode = wxConvCurrent->cMB2WC(str); wxCharBuffer buffer; if (unicode) buffer = wxConvUTF8.cWC2MB(unicode); #endif if (buffer) return buffer; } if (m_pCSConv) { #if wxUSE_UNICODE wxCharBuffer buffer = m_pCSConv->cWX2MB(str); #else wxWCharBuffer unicode = wxConvCurrent->cMB2WC(str); wxCharBuffer buffer; if (unicode) buffer = m_pCSConv->cWC2MB(unicode); #endif if (buffer) return buffer; } wxCharBuffer buffer = wxConvCurrent->cWX2MB(str); if (!buffer) buffer = wxCSConv(_T("ISO8859-1")).cWX2MB(str); return buffer; } void CControlSocket::OnTimer(wxTimerEvent& event) { int timeout = m_pEngine->GetOptions()->GetOptionVal(OPTION_TIMEOUT); if (!timeout) return; if (m_pCurOpData && m_pCurOpData->waitForAsyncRequest) return; if (m_stopWatch.Time() > (timeout * 1000)) { LogMessage(::Error, _("Connection timed out")); DoClose(FZ_REPLY_TIMEOUT); wxASSERT(!m_timer.IsRunning()); } } void CControlSocket::SetAlive() { m_stopWatch.Start(); } void CControlSocket::SetWait(bool wait) { if (wait) { if (m_timer.IsRunning()) return; m_stopWatch.Start(); m_timer.Start(1000); } else if (m_timer.IsRunning()) m_timer.Stop(); } int CControlSocket::SendNextCommand(int prevResult /*=FZ_REPLY_OK*/) { ResetOperation(prevResult); return FZ_REPLY_ERROR; } const std::list::iterator CControlSocket::GetLockStatus() { std::list::iterator iter; for (iter = m_lockInfoList.begin(); iter != m_lockInfoList.end(); iter++) if (iter->pControlSocket == this) break; return iter; } bool CControlSocket::TryLockCache(const CServerPath& directory) { wxASSERT(m_pCurrentServer); std::list::iterator own = GetLockStatus(); if (own == m_lockInfoList.end()) { t_lockInfo info; info.directory = directory; info.pControlSocket = this; info.waiting = true; m_lockInfoList.push_back(info); own = --m_lockInfoList.end(); } else { wxASSERT(own->waiting); } // Try to find other instance holding the lock for (std::list::const_iterator iter = m_lockInfoList.begin(); iter != own; iter++) { if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer) continue; if (directory == iter->directory) { // Some other instance is holding the lock return false; } } own->waiting = false; return true; } void CControlSocket::UnlockCache() { std::list::iterator iter = GetLockStatus(); if (iter == m_lockInfoList.end()) return; CServerPath directory = iter->directory; m_lockInfoList.erase(iter); // Find other instance waiting for the lock for (std::list::const_iterator iter = m_lockInfoList.begin(); iter != m_lockInfoList.end(); iter++) { if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer) continue; if (iter->directory != directory) continue; // Send notification wxCommandEvent evt(fzOBTAINLOCK); iter->pControlSocket->AddPendingEvent(evt); break; } } bool CControlSocket::ObtainLockFromEvent() { std::list::iterator own = GetLockStatus(); if (own == m_lockInfoList.end()) return false; if (!own->waiting) return false; for (std::list::const_iterator iter = m_lockInfoList.begin(); iter != own; iter++) { if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer) continue; if (iter->directory == own->directory) return false; } own->waiting = false; return true; } bool CControlSocket::HasLock() { std::list::iterator own = GetLockStatus(); if (own == m_lockInfoList.end()) return false; if (own->waiting) return false; return true; } void CControlSocket::OnObtainLock(wxCommandEvent& event) { if (!ObtainLockFromEvent()) return; SendNextCommand(); UnlockCache(); } void CControlSocket::InvalidateCurrentWorkingDir(const CServerPath& path) { wxASSERT(!path.IsEmpty()); if (m_CurrentPath.IsEmpty()) return; if (m_CurrentPath == path || path.IsParentOf(m_CurrentPath, false)) { if (m_pCurOpData) m_invalidateCurrentPath = true; else m_CurrentPath.Clear(); } } // ------------------ // CRealControlSocket // ------------------ BEGIN_EVENT_TABLE(CRealControlSocket, CControlSocket) EVT_SOCKET(wxID_ANY, CRealControlSocket::OnSocketEvent) END_EVENT_TABLE() CRealControlSocket::CRealControlSocket(CFileZillaEnginePrivate *pEngine) : CControlSocket(pEngine), wxSocketClient(wxSOCKET_NOWAIT) { m_pBackend = new CSocketBackend(this, this); m_pSendBuffer = 0; m_nSendBufferLen = 0; m_onConnectCalled = false; } CRealControlSocket::~CRealControlSocket() { delete m_pBackend; m_pBackend = 0; } bool CRealControlSocket::Send(const char *buffer, int len) { SetWait(true); if (m_pSendBuffer) { char *tmp = m_pSendBuffer; m_pSendBuffer = new char[m_nSendBufferLen + len]; memcpy(m_pSendBuffer, tmp, m_nSendBufferLen); memcpy(m_pSendBuffer + m_nSendBufferLen, buffer, len); m_nSendBufferLen += len; delete [] tmp; } else { m_pBackend->Write(buffer, len); int numsent = 0; if (m_pBackend->Error()) { if (m_pBackend->LastError() != wxSOCKET_WOULDBLOCK) { DoClose(); return false; } } else numsent = m_pBackend->LastCount(); if (numsent) m_pEngine->SetActive(false); if (numsent < len) { char *tmp = m_pSendBuffer; m_pSendBuffer = new char[m_nSendBufferLen + len - numsent]; memcpy(m_pSendBuffer, tmp, m_nSendBufferLen); memcpy(m_pSendBuffer + m_nSendBufferLen, buffer, len - numsent); m_nSendBufferLen += len - numsent; delete [] tmp; } } return true; } void CRealControlSocket::OnSocketEvent(wxSocketEvent &event) { if (!m_pBackend) return; if (event.GetId() != m_pBackend->GetId()) return; switch (event.GetSocketEvent()) { case wxSOCKET_CONNECTION: m_onConnectCalled = true; OnConnect(); break; case wxSOCKET_INPUT: if (!m_onConnectCalled) { m_onConnectCalled = true; OnConnect(); } OnReceive(); break; case wxSOCKET_OUTPUT: OnSend(); break; case wxSOCKET_LOST: OnClose(); break; } } void CRealControlSocket::OnConnect() { } void CRealControlSocket::OnReceive() { } void CRealControlSocket::OnSend() { if (m_pSendBuffer) { if (!m_nSendBufferLen) { delete [] m_pSendBuffer; m_pSendBuffer = 0; return; } m_pBackend->Write(m_pSendBuffer, m_nSendBufferLen); if (m_pBackend->Error()) { if (m_pBackend->LastError() != wxSOCKET_WOULDBLOCK) DoClose(); return; } int numsent = m_pBackend->LastCount(); if (numsent) { SetAlive(); m_pEngine->SetActive(false); } if (numsent == m_nSendBufferLen) { m_nSendBufferLen = 0; delete [] m_pSendBuffer; m_pSendBuffer = 0; } else { memmove(m_pSendBuffer, m_pSendBuffer + numsent, m_nSendBufferLen - numsent); m_nSendBufferLen -= numsent; } } } void CRealControlSocket::OnClose() { LogMessage(Debug_Verbose, _T("CRealControlSocket::OnClose()")); if (GetCurrentCommandId() != cmd_connect) LogMessage(::Error, _("Disconnected from server")); DoClose(); } int CRealControlSocket::Connect(const CServer &server) { SetWait(true); if (server.GetEncodingType() == ENCODING_CUSTOM) m_pCSConv = new wxCSConv(server.GetCustomEncoding()); if (m_pCurrentServer) delete m_pCurrentServer; m_pCurrentServer = new CServer(server); const wxString & host = server.GetHost(); 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(); } else { wxIPV4address addr; addr.Hostname(host); return ContinueConnect(&addr); } return FZ_REPLY_WOULDBLOCK; } int CRealControlSocket::ContinueConnect(const wxIPV4address *address) { LogMessage(__TFILE__, __LINE__, this, Debug_Verbose, _T("CRealControlSocket::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) { LogMessage(::Error, _("Invalid hostname or host not found")); return DoClose(FZ_REPLY_ERROR | FZ_REPLY_CRITICALERROR); } const unsigned int port = m_pCurrentServer->GetPort(); LogMessage(Status, _("Connecting to %s:%d..."), address->IPAddress().c_str(), port); wxIPV4address addr = *address; addr.Service(port); bool res = wxSocketClient::Connect(addr, false); if (!res && LastError() != wxSOCKET_WOULDBLOCK) return DoClose(); return FZ_REPLY_WOULDBLOCK; } int CRealControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/) { ResetSocket(); return CControlSocket::DoClose(nErrorCode); } void CRealControlSocket::ResetSocket() { Close(); if (m_pSendBuffer) { delete [] m_pSendBuffer; m_pSendBuffer = 0; m_nSendBufferLen = 0; } m_onConnectCalled = false; delete m_pBackend; m_pBackend = 0; } wxString CRealControlSocket::GetLocalIP() const { wxIPV4address addr; if (!GetLocal(addr)) return _T(""); return addr.IPAddress(); } wxString CRealControlSocket::GetPeerIP() const { wxIPV4address addr; if (!GetPeer(addr)) return _T(""); return addr.IPAddress(); }