#include "FileZilla.h" #include "sftpcontrolsocket.h" #include #include #include "directorycache.h" #include "directorylistingparser.h" #include "pathcache.h" class CSftpFileTransferOpData : public CFileTransferOpData { public: CSftpFileTransferOpData() { } }; enum filetransferStates { filetransfer_init = 0, filetransfer_waitcwd, filetransfer_waitlist, filetransfer_transfer }; extern const wxEventType fzEVT_SFTP; typedef void (wxEvtHandler::*fzSftpEventFunction)(CSftpEvent&); #define EVT_SFTP(fn) \ DECLARE_EVENT_TABLE_ENTRY( \ fzEVT_SFTP, -1, -1, \ (wxObjectEventFunction)(wxEventFunction) wxStaticCastEvent( fzSftpEventFunction, &fn ), \ (wxObject *) NULL \ ), DEFINE_EVENT_TYPE(fzEVT_SFTP); CSftpEvent::CSftpEvent(sftpEventTypes type, const wxString& text) : wxEvent(wxID_ANY, fzEVT_SFTP) { m_type = type; m_text[0] = text; m_reqType = sftpReqUnknown; } CSftpEvent::CSftpEvent(sftpEventTypes type, sftpRequestTypes reqType, const wxString& text1, const wxString& text2 /*=_T("")*/, const wxString& text3 /*=_T("")*/, const wxString& text4 /*=_T("")*/) : wxEvent(wxID_ANY, fzEVT_SFTP) { wxASSERT(type == sftpRequest || reqType == sftpReqUnknown); m_type = type; m_reqType = reqType; m_text[0] = text1; m_text[1] = text2; m_text[2] = text3; m_text[3] = text4; } int CSftpEvent::GetNumber() const { long number = 0; m_text[0].ToLong(&number); return number; } BEGIN_EVENT_TABLE(CSftpControlSocket, CControlSocket) EVT_SFTP(CSftpControlSocket::OnSftpEvent) EVT_END_PROCESS(wxID_ANY, CSftpControlSocket::OnTerminate) END_EVENT_TABLE(); class CSftpInputThread : public wxThreadEx { public: CSftpInputThread(CSftpControlSocket* pOwner, wxProcess* pProcess) : wxThreadEx(wxTHREAD_JOINABLE), m_pProcess(pProcess), m_pOwner(pOwner) { } virtual ~CSftpInputThread() { } bool Init() { if (Create() != wxTHREAD_NO_ERROR) return false; Run(); return true; } protected: wxString ReadLine(wxInputStream* pInputStream, bool &error) { int read = 0; const int buffersize = 4096; char buffer[buffersize]; while(!pInputStream->Eof()) { char c; pInputStream->Read(&c, 1); if (pInputStream->LastRead() != 1) { if (pInputStream->Eof()) m_pOwner->LogMessage(Debug_Warning, _T("Unexpected EOF.")); else m_pOwner->LogMessage(Debug_Warning, _T("Uknown input stream error")); error = true; return _T(""); } if (c == '\n') break; if (read == buffersize - 1) { // Cap string length continue; } buffer[read++] = c; } if (pInputStream->Eof()) { m_pOwner->LogMessage(Debug_Warning, _T("Unexpected EOF.")); error = true; return _T(""); } if (read && buffer[read - 1] == '\r') read--; buffer[read] = 0; const wxString line = m_pOwner->ConvToLocal(buffer); if (read && line == _T("")) { m_pOwner->LogMessage(::Error, _T("Failed to convert reply to local character set.")); error = true; } return line; } virtual ExitCode Entry() { wxInputStream* pInputStream = m_pProcess->GetInputStream(); char eventType; bool error = false; while (!pInputStream->Eof() && !error) { pInputStream->Read(&eventType, 1); if (pInputStream->LastRead() != 1) break; eventType -= '0'; switch(eventType) { case sftpReply: case sftpListentry: case sftpRequestPreamble: case sftpRequestInstruction: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; CSftpEvent evt((sftpEventTypes)eventType, line); wxPostEvent(m_pOwner, evt); } break; case sftpError: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; m_pOwner->LogMessageRaw(::Error, line); } break; case sftpVerbose: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; m_pOwner->LogMessageRaw(Debug_Info, line); } break; case sftpStatus: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; m_pOwner->LogMessageRaw(Status, line); } break; case sftpDone: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; CSftpEvent evt((sftpEventTypes)eventType, line); wxPostEvent(m_pOwner, evt); } break; case sftpRequest: { const wxString& line = ReadLine(pInputStream, error); if (error) goto loopexit; int requestType = line[0] - '0'; if (requestType == sftpReqHostkey || requestType == sftpReqHostkeyChanged) { const wxString& strPort = ReadLine(pInputStream, error); if (error) goto loopexit; long port = 0; if (!strPort.ToLong(&port)) goto loopexit; const wxString& fingerprint = ReadLine(pInputStream, error); if (error) goto loopexit; m_pOwner->SendRequest(new CHostKeyNotification(line.Mid(1), port, fingerprint, requestType == sftpReqHostkeyChanged)); } else if (requestType == sftpReqPassword) { CSftpEvent evt(sftpRequest, sftpReqPassword, line.Mid(1)); wxPostEvent(m_pOwner, evt); } } break; case sftpRecv: m_pOwner->SetActive(true); break; case sftpSend: m_pOwner->SetActive(false); break; case sftpRead: case sftpWrite: { wxTextInputStream textStream(*pInputStream); wxString text = textStream.ReadLine(); if (pInputStream->LastRead() <= 0 || text == _T("")) goto loopexit; CSftpEvent evt((sftpEventTypes)eventType, text); wxPostEvent(m_pOwner, evt); } break; case sftpUsedQuotaRecv: case sftpUsedQuotaSend: { CSftpEvent evt((sftpEventTypes)eventType, _T("")); wxPostEvent(m_pOwner, evt); } break; default: { char tmp[2]; tmp[0] = eventType + '0'; tmp[1] = 0; m_pOwner->LogMessage(Debug_Info, _T("Unknown eventType: %s"), tmp); } break; } } loopexit: return (ExitCode)Close(); }; int Close() { return 0; } wxProcess* m_pProcess; CSftpControlSocket* m_pOwner; }; CSftpControlSocket::CSftpControlSocket(CFileZillaEnginePrivate *pEngine) : CControlSocket(pEngine) { m_useUTF8 = true; m_pProcess = 0; m_pInputThread = 0; m_pid = 0; m_inDestructor = false; m_termindatedInDestructor = false; } CSftpControlSocket::~CSftpControlSocket() { DoClose(); } class CSftpConnectOpData : public COpData { public: CSftpConnectOpData() : COpData(cmd_connect) { gotInitialReply = false; pLastChallenge = 0; criticalFailure = false; } virtual ~CSftpConnectOpData() { delete pLastChallenge; } wxString *pLastChallenge; bool gotInitialReply; bool criticalFailure; }; int CSftpControlSocket::Connect(const CServer &server) { LogMessage(Status, _("Connecting to %s:%d..."), server.GetHost().c_str(), server.GetPort()); SetWait(true); if (m_pCurrentServer) delete m_pCurrentServer; m_pCurrentServer = new CServer(server); m_pCurOpData = new CSftpConnectOpData; m_pProcess = new wxProcess(this); m_pProcess->Redirect(); CRateLimiter::Get()->AddObject(this); wxString executable = m_pEngine->GetOptions()->GetOption(OPTION_FZSFTP_EXECUTABLE); if (executable == _T("")) executable = _T("fzsftp"); LogMessage(Debug_Verbose, _T("Going to execute %s"), executable.c_str()); m_pid = wxExecute(executable + _T(" -v"), wxEXEC_ASYNC, m_pProcess); if (!m_pid) { delete m_pProcess; m_pProcess = 0; DoClose(); return FZ_REPLY_ERROR; } m_pInputThread = new CSftpInputThread(this, m_pProcess); if (!m_pInputThread->Init()) { delete m_pInputThread; m_pInputThread = 0; m_pProcess->Detach(); m_pProcess = 0; DoClose(); return FZ_REPLY_ERROR; } return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::ConnectParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::ConnectParseResponse(%s)"), reply.c_str()); if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpConnectOpData *pData = static_cast(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData of wrong type")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (pData->gotInitialReply) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } pData->gotInitialReply = true; bool res = Send(wxString::Format(_T("open \"%s@%s\" %d"), m_pCurrentServer->GetUser().c_str(), m_pCurrentServer->GetHost().c_str(), m_pCurrentServer->GetPort())); if (res) return FZ_REPLY_WOULDBLOCK; else return FZ_REPLY_ERROR; } void CSftpControlSocket::OnSftpEvent(CSftpEvent& event) { if (!m_pCurrentServer) return; switch (event.GetType()) { case sftpReply: LogMessageRaw(Response, event.GetText()); ProcessReply(true, event.GetText()); break; case sftpStatus: case sftpError: case sftpVerbose: wxFAIL_MSG(_T("given notification codes should have been handled by thread")); break; case sftpDone: { ProcessReply(event.GetText() == _T("1")); break; } case sftpRequestPreamble: m_requestPreamble = event.GetText(); break; case sftpRequestInstruction: m_requestInstruction = event.GetText(); break; case sftpRequest: switch(event.GetRequestType()) { case sftpReqPassword: if (!m_pCurOpData || m_pCurOpData->opId != cmd_connect) { LogMessage(Debug_Warning, _T("sftpReqPassword outside connect operation, ignoring.")); break; } if (m_pCurrentServer->GetLogonType() == INTERACTIVE) { wxString challenge; if (m_requestPreamble != _T("")) challenge += m_requestPreamble + _T("\n"); if (m_requestInstruction != _T("")) challenge += m_requestInstruction + _T("\n"); if (event.GetText() != _T("Password:")) challenge += event.GetText(); CInteractiveLoginNotification *pNotification = new CInteractiveLoginNotification(challenge); pNotification->server = *m_pCurrentServer; pNotification->requestNumber = m_pEngine->GetNextAsyncRequestNumber(); m_pEngine->AddNotification(pNotification); } else { CSftpConnectOpData *pData = reinterpret_cast(m_pCurOpData); const wxString newChallenge = m_requestPreamble + _T("\n") + m_requestInstruction; if (pData->pLastChallenge) { // Check for same challenge. Will most likely fail as well, so abort early. if (*pData->pLastChallenge == newChallenge) { LogMessage(::Error, _T("Authentication failed.")); DoClose(FZ_REPLY_CRITICALERROR); return; } delete pData->pLastChallenge; } pData->pLastChallenge = new wxString(newChallenge); const wxString pass = m_pCurrentServer->GetPass(); wxString show = _T("Pass: "); show.Append('*', pass.Length()); Send(pass, show); } break; default: wxFAIL_MSG(_T("given notification codes should have been handled by thread")); break; } break; case sftpListentry: ListParseEntry(event.GetText()); break; case sftpRead: case sftpWrite: UpdateTransferStatus(event.GetNumber()); break; case sftpUsedQuotaRecv: OnQuotaRequest(CRateLimiter::inbound); break; case sftpUsedQuotaSend: OnQuotaRequest(CRateLimiter::outbound); break; default: wxFAIL_MSG(_T("given notification codes not handled")); break; } } void CSftpControlSocket::OnTerminate(wxProcessEvent& event) { // Check if we're inside the destructor, if so, return, all cleanup will be // done there. if (m_inDestructor) { m_termindatedInDestructor = true; return; } if (!m_pInputThread) { event.Skip(); return; } CControlSocket::DoClose(); m_pInputThread->Wait(); delete m_pInputThread; m_pInputThread = 0; m_pid = 0; delete m_pProcess; m_pProcess = 0; } bool CSftpControlSocket::Send(wxString cmd, const wxString& show /*=_T("")*/) { SetWait(true); if (show != _T("")) LogMessageRaw(Command, show); else LogMessageRaw(Command, cmd); // Check for newlines in command // a command like "ls\nrm foo/bar" is dangerous if (cmd.Find('\n') != -1 || cmd.Find('\r') != -1) { LogMessage(Debug_Warning, _T("Command containing newline characters, aborting")); ResetOperation(FZ_REPLY_INTERNALERROR); return false; } cmd += _T("\n"); return AddToStream(cmd); } bool CSftpControlSocket::AddToStream(const wxString& cmd) { const wxCharBuffer str = ConvToServer(cmd); if (!m_pProcess) return false; wxOutputStream* pStream = m_pProcess->GetOutputStream(); if (!pStream) return false; unsigned int len = strlen(str); if (pStream->Write(str, len).LastWrite() != len) return false; return true; } bool CSftpControlSocket::SendRequest(CAsyncRequestNotification *pNotification) { pNotification->requestNumber = m_pEngine->GetNextAsyncRequestNumber(); m_pEngine->AddNotification(pNotification); return true; } bool CSftpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification) { const enum RequestId requestId = pNotification->GetRequestID(); switch(requestId) { 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; } CSftpFileTransferOpData *pData = static_cast(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(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 && pFileExistsNotification->localSize != -1) pData->resume = true; else if (!pData->download && pFileExistsNotification->remoteSize != -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) { 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) break; } } 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_hostkey: case reqId_hostkeyChanged: { if (GetCurrentCommandId() != cmd_connect || !m_pCurrentServer) { LogMessage(Debug_Info, _T("SetAsyncRequestReply called to wrong time")); return false; } CHostKeyNotification *pHostKeyNotification = reinterpret_cast(pNotification); wxString show; if (requestId == reqId_hostkey) show = _("Trust new Hostkey: "); else show = _("Trust changed Hostkey: "); if (!pHostKeyNotification->m_trust) { Send(_T(""), show + _("No")); if (m_pCurOpData && m_pCurOpData->opId == cmd_connect) { CSftpConnectOpData *pData = static_cast(m_pCurOpData); pData->criticalFailure = true; } } else if (pHostKeyNotification->m_alwaysTrust) Send(_T("y"), show + _("Yes")); else Send(_T("n"), show + _("Once")); } break; case reqId_interactiveLogin: { CInteractiveLoginNotification *pInteractiveLoginNotification = reinterpret_cast(pNotification); if (!pInteractiveLoginNotification->passwordSet) { DoClose(FZ_REPLY_CANCELED); return false; } const wxString pass = pInteractiveLoginNotification->server.GetPass(); m_pCurrentServer->SetUser(m_pCurrentServer->GetUser(), pass); wxString show = _T("Pass: "); show.Append('*', pass.Length()); Send(pass, show); } break; default: LogMessage(Debug_Warning, _T("Unknown async request reply id: %d"), requestId); return false; } return true; } class CSftpListOpData : public COpData { public: CSftpListOpData() : COpData(cmd_list) { pParser = 0; } virtual ~CSftpListOpData() { delete pParser; } CDirectoryListingParser* pParser; CServerPath path; wxString subDir; // Set to true to get a directory listing even if a cache // lookup can be made after finding out true remote directory bool refresh; }; enum listStates { list_init = 0, list_waitcwd, list_list }; int CSftpControlSocket::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")); } if (!m_pCurrentServer) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurrenServer == 0")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpListOpData *pData = new CSftpListOpData; 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; pData->opState = list_list; return ListSend(); } int CSftpControlSocket::ListParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::ListParseResponse(%s)"), reply.c_str()); if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpListOpData *pData = static_cast(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData of wrong type")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (pData->opState != list_list) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("ListParseResponse called at inproper time: %s"), pData->opState); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!pData->pParser) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("pData->pParser is 0")); return FZ_REPLY_INTERNALERROR; } CDirectoryListing listing = pData->pParser->Parse(m_CurrentPath); 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_ERROR; } int CSftpControlSocket::ListParseEntry(const wxString& entry) { if (!m_pCurOpData) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (m_pCurOpData->opId != cmd_list) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Listentry received, but current operation is not cmd_list")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpListOpData *pData = static_cast(m_pCurOpData); if (!pData) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData of wrong type")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (pData->opState != list_list) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("ListParseResponse called at inproper time: %s"), pData->opState); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!pData->pParser) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("pData->pParser is 0")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_INTERNALERROR; } if (entry.Find('\r') != -1 || entry.Find('\n') != -1) { LogMessageRaw(RawList, entry); LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Listing entry contains \\r at pos %d and \\n at pos %d. Please contect FileZilla team."), entry.Find('\r'), entry.Find('\n')); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_INTERNALERROR; } pData->pParser->AddLine(entry); return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::ListSend(int prevResult /*=FZ_REPLY_OK*/) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::ListSend(%d)"), prevResult); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpListOpData *pData = static_cast(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; } pData->opState = list_list; } if (pData->opState == list_list) { pData->pParser = new CDirectoryListingParser(this, *m_pCurrentServer); Send(_T("ls")); return FZ_REPLY_WOULDBLOCK; } return FZ_REPLY_ERROR; } class CSftpChangeDirOpData : public CChangeDirOpData { }; enum cwdStates { cwd_init = 0, cwd_pwd, cwd_cwd, cwd_cwd_subdir }; int CSftpControlSocket::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; } } CSftpChangeDirOpData *pData = new CSftpChangeDirOpData; pData->pNextOpData = m_pCurOpData; pData->opState = state; pData->path = path; pData->subDir = subDir; pData->target = target; m_pCurOpData = pData; return ChangeDirSend(); } int CSftpControlSocket::ChangeDirParseResponse(bool successful, const wxString& reply) { if (!m_pCurOpData) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CSftpChangeDirOpData *pData = static_cast(m_pCurOpData); bool error = false; switch (pData->opState) { case cwd_pwd: if (!successful || reply == _T("")) error = true; if (ParsePwdReply(reply)) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else error = true; break; case cwd_cwd: if (!successful) { // Create remote directory if part of a file upload if (pData->pNextOpData && pData->pNextOpData->opId == cmd_transfer && !static_cast(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 (reply == _T("")) error = true; else if (ParsePwdReply(reply)) { CPathCache cache; cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path); if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } pData->target.Clear(); pData->opState = cwd_cwd_subdir; } else error = true; break; case cwd_cwd_subdir: if (!successful || reply == _T("")) error = true; else if (ParsePwdReply(reply)) { CPathCache cache; cache.Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else error = true; break; default: error = true; break; } if (error) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return ChangeDirSend(); } int CSftpControlSocket::ChangeDirSend() { if (!m_pCurOpData) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CSftpChangeDirOpData *pData = static_cast(m_pCurOpData); wxString cmd; switch (pData->opState) { case cwd_pwd: cmd = _T("pwd"); break; case cwd_cwd: cmd = _T("cd ") + QuoteFilename(pData->path.GetPath()); m_CurrentPath.Clear(); break; case cwd_cwd_subdir: if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } else cmd = _T("cd ") + QuoteFilename(pData->subDir); m_CurrentPath.Clear(); break; } if (cmd != _T("")) if (!Send(cmd)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::ProcessReply(bool successful, const wxString& reply /*=_T("")*/) { enum Command commandId = GetCurrentCommandId(); switch (commandId) { case cmd_connect: return ConnectParseResponse(successful, reply); case cmd_list: return ListParseResponse(successful, reply); case cmd_transfer: return FileTransferParseResponse(successful, reply); case cmd_cwd: return ChangeDirParseResponse(successful, reply); case cmd_mkdir: return MkdirParseResponse(successful, reply); case cmd_delete: return DeleteParseResponse(successful, reply); case cmd_removedir: return RemoveDirParseResponse(successful, reply); case cmd_chmod: return ChmodParseResponse(successful, reply); case cmd_rename: return RenameParseResponse(successful, reply); default: LogMessage(Debug_Warning, _T("No action for parsing replies to command %d"), (int)commandId); return ResetOperation(FZ_REPLY_INTERNALERROR); } } int CSftpControlSocket::ResetOperation(int nErrorCode) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::ResetOperation(%d)"), nErrorCode); if (m_pCurOpData && m_pCurOpData->opId == cmd_connect) { CSftpConnectOpData *pData = static_cast(m_pCurOpData); if (!pData->gotInitialReply) LogMessage(::Error, _("fzsftp could not be started")); if (pData->criticalFailure) nErrorCode |= FZ_REPLY_CRITICALERROR; } return CControlSocket::ResetOperation(nErrorCode); } int CSftpControlSocket::SendNextCommand(int prevResult /*=FZ_REPLY_OK*/) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::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_transfer: return FileTransferSend(prevResult); case cmd_cwd: return ChangeDirSend(); case cmd_mkdir: return MkdirSend(); default: LogMessage(::Debug_Warning, __TFILE__, __LINE__, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } return FZ_REPLY_ERROR; } int CSftpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath, const wxString &remoteFile, bool download, const CFileTransferCommand::t_transferSettings& transferSettings) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::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; } CSftpFileTransferOpData *pData = new CSftpFileTransferOpData; m_pCurOpData = pData; pData->localFile = localFile; pData->remotePath = remotePath; pData->remoteFile = remoteFile; pData->download = download; pData->transferSettings = transferSettings; 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); } } if (shouldList) { res = List(); if (res != FZ_REPLY_OK) return res; } pData->opState = filetransfer_transfer; res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; return FileTransferSend(); } int CSftpControlSocket::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; } CSftpFileTransferOpData *pData = static_cast(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); } } if (shouldList) { int res = List(CServerPath(), _T(""), true); if (res != FZ_REPLY_OK) return res; } pData->opState = filetransfer_transfer; int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } else { pData->tryAbsolutePath = true; pData->opState = filetransfer_transfer; int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } } else if (pData->opState == filetransfer_waitlist) { if (prevResult == FZ_REPLY_OK) { pData->opState = filetransfer_transfer; 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 && matchedCase) { pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32); int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } } pData->opState = filetransfer_transfer; int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } wxString cmd; if (pData->resume) cmd = _T("re"); if (pData->download) { // Create local directory if (!pData->resume) { wxFileName fn(pData->localFile); wxFileName::Mkdir(fn.GetPath(), 0777, wxPATH_MKDIR_FULL); } InitTransferStatus(pData->remoteFileSize, pData->resume ? pData->localFileSize : 0, false); cmd += _T("get "); cmd += QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath)) + _T(" "); cmd += QuoteFilename(pData->localFile); } else { InitTransferStatus(pData->localFileSize, pData->resume ? pData->remoteFileSize : 0, false); cmd += _T("put "); cmd += QuoteFilename(pData->localFile) + _T(" "); cmd += QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath)); } SetTransferStatusStartTime(); pData->transferInitiated = true; if (!Send(cmd)) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::FileTransferParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("FileTransferParseResponse()")); if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CSftpFileTransferOpData *pData = static_cast(m_pCurOpData); if (pData->opState != filetransfer_transfer) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T(" Called at improper time: opState == %d"), pData->opState); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } int CSftpControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/) { CRateLimiter::Get()->RemoveObject(this); if (m_pInputThread) { wxThreadEx* pThread = m_pInputThread; m_pInputThread = 0; wxProcess::Kill(m_pid, wxSIGKILL); m_inDestructor = true; if (pThread) { pThread->Wait(); delete pThread; } if (!m_termindatedInDestructor) m_pProcess->Detach(); else { delete m_pProcess; m_pProcess = 0; } } return CControlSocket::DoClose(nErrorCode); } void CSftpControlSocket::Cancel() { if (GetCurrentCommandId() != cmd_none) { DoClose(FZ_REPLY_CANCELED); } } void CSftpControlSocket::SetActive(bool recv) { m_pEngine->SetActive(recv); } enum mkdStates { mkd_init = 0, mkd_findparent, mkd_mkdsub, mkd_cwdsub, mkd_tryfull }; int CSftpControlSocket::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 CSftpControlSocket::MkdirParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::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(m_pCurOpData); LogMessage(Debug_Debug, _T(" state = %d"), pData->opState); bool error = false; switch (pData->opState) { case mkd_findparent: if (successful) { m_CurrentPath = pData->currentPath; pData->opState = mkd_mkdsub; } else if (pData->currentPath == pData->commonParent) pData->opState = mkd_tryfull; else if (pData->currentPath.HasParent()) { pData->segments.push_front(pData->currentPath.GetLastSegment()); pData->currentPath = pData->currentPath.GetParent(); } else pData->opState = mkd_tryfull; break; case mkd_mkdsub: if (successful) { 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; } else pData->opState = mkd_tryfull; break; case mkd_cwdsub: if (successful) { m_CurrentPath = pData->currentPath; pData->opState = mkd_mkdsub; } else pData->opState = mkd_tryfull; break; case mkd_tryfull: if (!successful) error = true; else { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } 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 CSftpControlSocket::MkdirSend() { LogMessage(Debug_Verbose, _T("CSftpControlSocket::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(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("cd ") + QuoteFilename(pData->currentPath.GetPath())); break; case mkd_mkdsub: res = Send(_T("mkdir ") + QuoteFilename(pData->segments.front())); break; case mkd_tryfull: res = Send(_T("mkdir ") + QuoteFilename(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; } wxString CSftpControlSocket::QuoteFilename(wxString filename) { filename.Replace(_T("\""), _T("\"\"")); return _T("\"") + filename + _T("\""); } class CSftpDeleteOpData : public COpData { public: CSftpDeleteOpData() : COpData(cmd_delete) { } virtual ~CSftpDeleteOpData() { } CServerPath path; wxString file; }; int CSftpControlSocket::Delete(const CServerPath& path /*=CServerPath()*/, const wxString& file /*=_T("")*/) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::Delete")); wxASSERT(!m_pCurOpData); CSftpDeleteOpData *pData = new CSftpDeleteOpData(); m_pCurOpData = pData; pData->path = path; pData->file = file; wxString filename = path.FormatFilename(file); if (filename == _T("")) { LogMessage(::Error, _T("Filename cannot be constructed for folder %s and filename %s"), path.GetPath().c_str(), file.c_str()); return FZ_REPLY_ERROR; } CDirectoryCache cache; cache.InvalidateFile(*m_pCurrentServer, path, file); if (!Send(_T("rm ") + WildcardEscape(QuoteFilename(filename)), _T("rm ") + QuoteFilename(filename))) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::DeleteParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::DeleteParseResponse")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!successful) return ResetOperation(FZ_REPLY_ERROR); CSftpDeleteOpData *pData = static_cast(m_pCurOpData); CDirectoryCache cache; cache.RemoveFile(*m_pCurrentServer, pData->path, pData->file); m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false); return ResetOperation(FZ_REPLY_OK); } class CSftpRemoveDirOpData : public COpData { public: CSftpRemoveDirOpData() : COpData(cmd_removedir) { } virtual ~CSftpRemoveDirOpData() { } CServerPath path; wxString subDir; }; int CSftpControlSocket::RemoveDir(const CServerPath& path /*=CServerPath()*/, const wxString& subDir /*=_T("")*/) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::RemoveDir")); wxASSERT(!m_pCurOpData); CSftpRemoveDirOpData *pData = new CSftpRemoveDirOpData(); m_pCurOpData = pData; pData->path = path; pData->subDir = subDir; CServerPath fullPath = pData->path; if (!fullPath.AddSegment(subDir)) { LogMessage(::Error, _T("Path cannot be constructed for folder %s and subdir %s"), path.GetPath().c_str(), subDir.c_str()); return FZ_REPLY_ERROR; } CDirectoryCache cache; cache.InvalidateFile(*m_pCurrentServer, path, subDir); CPathCache pathCache; pathCache.InvalidatePath(*m_pCurrentServer, pData->path, pData->subDir); if (!Send(_T("rmdir ") + WildcardEscape(QuoteFilename(fullPath.GetPath())), _T("rmdir ") + QuoteFilename(fullPath.GetPath()))) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CSftpControlSocket::RemoveDirParseResponse(bool successful, const wxString& reply) { LogMessage(Debug_Verbose, _T("CSftpControlSocket::RemoveDirParseResponse")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CSftpRemoveDirOpData *pData = static_cast(m_pCurOpData); if (pData->path.IsEmpty()) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty pData->path")); ResetOperation(FZ_REPLY_INTERNALERROR); 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); } class CSftpChmodOpData : public COpData { public: CSftpChmodOpData(const CChmodCommand& command) : COpData(cmd_chmod), m_cmd(command) { m_useAbsolute = false; } virtual ~CSftpChmodOpData() { } CChmodCommand m_cmd; bool m_useAbsolute; }; enum chmodStates { chmod_init = 0, chmod_chmod }; int CSftpControlSocket::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()); CSftpChmodOpData *pData = new CSftpChmodOpData(command); pData->opState = chmod_chmod; m_pCurOpData = pData; int res = ChangeDir(command.GetPath()); if (res != FZ_REPLY_OK) return res; return ChmodSend(); } int CSftpControlSocket::ChmodParseResponse(bool successful, const wxString& reply) { CSftpChmodOpData *pData = static_cast(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } int CSftpControlSocket::ChmodSend(int prevResult /*=FZ_REPLY_OK*/) { CSftpChmodOpData *pData = static_cast(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: { CDirectoryCache cache; cache.UpdateFile(*m_pCurrentServer, pData->m_cmd.GetPath(), pData->m_cmd.GetFile(), false, CDirectoryCache::unknown); wxString quotedFilename = QuoteFilename(pData->m_cmd.GetPath().FormatFilename(pData->m_cmd.GetFile(), !pData->m_useAbsolute)); res = Send(_T("chmod ") + pData->m_cmd.GetPermission() + _T(" ") + WildcardEscape(quotedFilename), _T("chmod ") + pData->m_cmd.GetPermission() + _T(" ") + quotedFilename); } 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 CSftpRenameOpData : public COpData { public: CSftpRenameOpData(const CRenameCommand& command) : COpData(cmd_rename), m_cmd(command) { m_useAbsolute = false; } virtual ~CSftpRenameOpData() { } CRenameCommand m_cmd; bool m_useAbsolute; }; enum renameStates { rename_init = 0, rename_rename }; int CSftpControlSocket::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()); CSftpRenameOpData *pData = new CSftpRenameOpData(command); pData->opState = rename_rename; m_pCurOpData = pData; int res = ChangeDir(command.GetFromPath()); if (res != FZ_REPLY_OK) return res; return RenameSend(); } int CSftpControlSocket::RenameParseResponse(bool successful, const wxString& reply) { CSftpRenameOpData *pData = static_cast(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!successful) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } const CServerPath& fromPath = pData->m_cmd.GetFromPath(); const CServerPath& toPath = pData->m_cmd.GetToPath(); CDirectoryCache cache; 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; } int CSftpControlSocket::RenameSend(int prevResult /*=FZ_REPLY_OK*/) { CSftpRenameOpData *pData = static_cast(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_rename: { CDirectoryCache cache; cache.InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile()); cache.InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile()); wxString localQuoted = QuoteFilename(pData->m_cmd.GetFromPath().FormatFilename(pData->m_cmd.GetFromFile(), !pData->m_useAbsolute)); wxString remoteQuoted = QuoteFilename(pData->m_cmd.GetToPath().FormatFilename(pData->m_cmd.GetToFile(), !pData->m_useAbsolute && pData->m_cmd.GetFromPath() == pData->m_cmd.GetToPath())); CPathCache pathCache; 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("mv ") + WildcardEscape(localQuoted) + _T(" ") + remoteQuoted, _T("mv ") + localQuoted + _T(" ") + remoteQuoted); } 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; } wxString CSftpControlSocket::WildcardEscape(const wxString& file) { // see src/putty/wildcard.c wxString escapedFile; escapedFile.Alloc(file.Len()); for (unsigned int i = 0; i < file.Len(); i++) { const char& c = file[i]; switch (c) { case '[': case ']': case '*': case '?': case '\\': escapedFile.Append('\\'); break; default: break; } escapedFile.Append(c); } return escapedFile; } void CSftpControlSocket::OnRateAvailable(enum CRateLimiter::rate_direction direction) { OnQuotaRequest(direction); } void CSftpControlSocket::OnQuotaRequest(enum CRateLimiter::rate_direction direction) { int bytes = GetAvailableBytes(direction); if (bytes > 0) { AddToStream(wxString::Format(_T("-%d%d\n"), (int)direction, bytes)); UpdateUsage(direction, bytes); } else if (bytes < 0) AddToStream(wxString::Format(_T("-%d-\n"), (int)direction, bytes)); else Wait(direction); }