#include "FileZilla.h"
#include "sftpcontrolsocket.h"
#include <wx/process.h>
#include <wx/txtstrm.h>
#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<CSftpConnectOpData *>(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<CSftpConnectOpData*>(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<CSftpFileTransferOpData *>(m_pCurOpData);
if (!pData->waitForAsyncRequest)
{
LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID());
return false;
}
pData->waitForAsyncRequest = false;
CFileExistsNotification *pFileExistsNotification = reinterpret_cast<CFileExistsNotification *>(pNotification);
switch (pFileExistsNotification->overwriteAction)
{
case CFileExistsNotification::overwrite:
SendNextCommand();
break;
case CFileExistsNotification::overwriteNewer:
if (!pFileExistsNotification->localTime.IsValid() || !pFileExistsNotification->remoteTime.IsValid())
SendNextCommand();
else if (pFileExistsNotification->download && pFileExistsNotification->localTime.IsEarlierThan(pFileExistsNotification->remoteTime))
SendNextCommand();
else if (!pFileExistsNotification->download && pFileExistsNotification->localTime.IsLaterThan(pFileExistsNotification->remoteTime))
SendNextCommand();
else
{
if (pData->download)
{
wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
LogMessage(Status, _("Skipping download of %s"), filename.c_str());
}
else
{
LogMessage(Status, _("Skipping upload of %s"), pData->localFile.c_str());
}
ResetOperation(FZ_REPLY_OK);
}
break;
case CFileExistsNotification::resume:
if (pData->download && 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<CHostKeyNotification *>(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<CSftpConnectOpData *>(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<CInteractiveLoginNotification *>(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<CSftpListOpData *>(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<CSftpListOpData *>(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<CSftpListOpData *>(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<CSftpChangeDirOpData *>(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<CSftpFileTransferOpData *>(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<CSftpChangeDirOpData *>(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<CSftpConnectOpData *>(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<CSftpFileTransferOpData *>(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<CSftpFileTransferOpData *>(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<CMkdirOpData *>(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<CMkdirOpData *>(m_pCurOpData);
LogMessage(Debug_Debug, _T(" state = %d"), pData->opState);
bool res;
switch (pData->opState)
{
case mkd_findparent:
case mkd_cwdsub:
m_CurrentPath.Clear();
res = Send(_T("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<CSftpDeleteOpData *>(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<CSftpRemoveDirOpData *>(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<CSftpChmodOpData*>(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<CSftpChmodOpData*>(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<CSftpRenameOpData*>(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<CSftpRenameOpData*>(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);
}
syntax highlighted by Code2HTML, v. 0.9.1