#include "FileZilla.h" #include "ftpcontrolsocket.h" #include "transfersocket.h" #include "directorylistingparser.h" #include "directorycache.h" #include "iothread.h" #include <wx/regex.h> #include "externalipresolver.h" #include "servercapabilities.h" #include "tlssocket.h" #include "pathcache.h" #include <algorithm> #include <wx/tokenzr.h> #include "local_filesys.h" #include <errno.h> #include "proxy.h" #define LOGON_WELCOME 0 #define LOGON_AUTH_TLS 1 #define LOGON_AUTH_SSL 2 #define LOGON_AUTH_WAIT 3 #define LOGON_LOGON 4 #define LOGON_SYST 5 #define LOGON_FEAT 6 #define LOGON_CLNT 7 #define LOGON_OPTSUTF8 8 #define LOGON_PBSZ 9 #define LOGON_PROT 10 #define LOGON_OPTSMLST 11 #define LOGON_CUSTOMCOMMANDS 12 #define LOGON_DONE 13 BEGIN_EVENT_TABLE(CFtpControlSocket, CRealControlSocket) EVT_FZ_EXTERNALIPRESOLVE(wxID_ANY, CFtpControlSocket::OnExternalIPAddress) EVT_TIMER(wxID_ANY, CFtpControlSocket::OnIdleTimer) END_EVENT_TABLE(); CRawTransferOpData::CRawTransferOpData() : COpData(cmd_rawtransfer) { bTriedPasv = bTriedActive = false; bPasv = true; } CFtpTransferOpData::CFtpTransferOpData() { transferEndReason = successful; tranferCommandSent = false; resumeOffset = 0; binary = true; } CFtpFileTransferOpData::CFtpFileTransferOpData(const wxString& local_file, const wxString& remote_file, const CServerPath& remote_path) : CFileTransferOpData(local_file, remote_file, remote_path) { pIOThread = 0; fileDidExist = true; } CFtpFileTransferOpData::~CFtpFileTransferOpData() { if (pIOThread) { CIOThread *pThread = pIOThread; pIOThread = 0; pThread->Destroy(); delete pThread; } } enum filetransferStates { filetransfer_init = 0, filetransfer_waitcwd, filetransfer_waitlist, filetransfer_size, filetransfer_mdtm, filetransfer_resumetest, filetransfer_transfer, filetransfer_waittransfer, filetransfer_waitresumetest, filetransfer_mfmt }; enum rawtransferStates { rawtransfer_init = 0, rawtransfer_type, rawtransfer_port_pasv, rawtransfer_rest, rawtransfer_transfer, rawtransfer_waitfinish, rawtransfer_waittransferpre, rawtransfer_waittransfer, rawtransfer_waitsocket }; enum loginCommandType { user, pass, account, other }; struct t_loginCommand { bool optional; bool hide_arguments; enum loginCommandType type; wxString command; }; class CFtpLogonOpData : public CConnectOpData { public: CFtpLogonOpData() { waitChallenge = false; gotPassword = false; waitForAsyncRequest = false; gotFirstWelcomeLine = false; ftp_proxy_type = 0; customCommandIndex = 0; for (int i = 0; i < LOGON_DONE; i++) neededCommands[i] = 1; } virtual ~CFtpLogonOpData() { } wxString challenge; // Used for interactive logons bool waitChallenge; bool waitForAsyncRequest; bool gotPassword; bool gotFirstWelcomeLine; unsigned int customCommandIndex; int neededCommands[LOGON_DONE]; std::list<t_loginCommand> loginSequence; int ftp_proxy_type; }; class CFtpDeleteOpData : public COpData { public: CFtpDeleteOpData() : COpData(cmd_delete) { m_needSendListing = false; m_deleteFailed = false; } virtual ~CFtpDeleteOpData() { } CServerPath path; std::list<wxString> files; bool omitPath; // Set to wxDateTime::UNow initially and after // sending an updated listing to the UI. wxDateTime m_time; bool m_needSendListing; // Set to true if deletion of at least one file failed bool m_deleteFailed; }; CFtpControlSocket::CFtpControlSocket(CFileZillaEnginePrivate *pEngine) : CRealControlSocket(pEngine) { m_pIPResolver = 0; m_pTransferSocket = 0; m_sentRestartOffset = false; m_bufferLen = 0; m_repliesToSkip = 0; m_pendingReplies = 1; m_pTlsSocket = 0; m_protectDataChannel = false; m_lastTypeBinary = -1; m_idleTimer.SetOwner(this); // Enable TCP_NODELAY, speeds things up a bit. // Enable SO_KEEPALIVE, lots of clueless users have broken routers and // firewalls which terminate the control connection on long transfers. m_pSocket->SetFlags(CSocket::flag_nodelay | CSocket::flag_keepalive); } CFtpControlSocket::~CFtpControlSocket() { DoClose(); m_idleTimer.Stop(); } void CFtpControlSocket::OnReceive() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::OnReceive()")); while (true) { int error; int read = m_pBackend->Read(m_receiveBuffer + m_bufferLen, RECVBUFFERSIZE - m_bufferLen, error); if (read < 0) { if (error != EAGAIN) { LogMessage(::Error, _("Could not read from socket: %s"), CSocket::GetErrorDescription(error).c_str()); if (GetCurrentCommandId() != cmd_connect) LogMessage(::Error, _("Disconnected from server")); DoClose(); } return; } if (!read) { LogMessage(::Error, _("Connection closed by server")); DoClose(); return; } m_pEngine->SetActive(CFileZillaEngine::recv); char* start = m_receiveBuffer; m_bufferLen += read; for (int i = start - m_receiveBuffer; i < m_bufferLen; i++) { char& p = m_receiveBuffer[i]; if (p == '\r' || p == '\n' || p == 0) { int len = i - (start - m_receiveBuffer); if (!len) { start++; continue; } if (len > MAXLINELEN) len = MAXLINELEN; p = 0; wxString line = ConvToLocal(start); start = m_receiveBuffer + i + 1; ParseLine(line); // Abort if connection got closed if (!m_pCurrentServer) return; } } memmove(m_receiveBuffer, start, m_bufferLen - (start - m_receiveBuffer)); m_bufferLen -= (start -m_receiveBuffer); if (m_bufferLen > MAXLINELEN) m_bufferLen = MAXLINELEN; } } void CFtpControlSocket::ParseLine(wxString line) { LogMessageRaw(Response, line); SetAlive(); if (m_pCurOpData && m_pCurOpData->opId == cmd_connect) { CFtpLogonOpData* pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData); if (pData->waitChallenge) { wxString& challenge = pData->challenge; if (challenge != _T("")) #ifdef __WXMSW__ challenge += _T("\r\n"); #else challenge += _T("\n"); #endif challenge += line; } else if (pData->opState == LOGON_FEAT) { wxString up = line.Upper(); if (up == _T(" UTF8")) CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, yes); else if (up == _T(" CLNT") || up.Left(6) == _T(" CLNT ")) CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, yes); else if (up == _T(" MLSD") || up.Left(6) == _T(" MLSD ")) CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes); else if (up == _T(" MLST") || up.Left(6) == _T(" MLST ")) { CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes, line.Mid(6)); // MSLT/MLSD specs require use of UTC CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no); } else if (up == _T(" MODE Z") || up.Left(6) == _T(" MODE Z ")) CServerCapabilities::SetCapability(*m_pCurrentServer, mode_z_support, yes); else if (up == _T(" MFMT") || up.Left(6) == _T(" MFMT ")) CServerCapabilities::SetCapability(*m_pCurrentServer, mfmt_command, yes); else if (up == _T(" PRET") || up.Left(6) == _T(" PRET ")) CServerCapabilities::SetCapability(*m_pCurrentServer, pret_command, yes); else if (up == _T(" MDTM") || up.Left(6) == _T(" MDTM ")) CServerCapabilities::SetCapability(*m_pCurrentServer, mdtm_command, yes); else if (up == _T(" SIZE") || up.Left(6) == _T(" SIZE ")) CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes); else if (up == _T(" TVFS")) CServerCapabilities::SetCapability(*m_pCurrentServer, tvfs_support, yes); else if (up == _T(" REST STREAM")) CServerCapabilities::SetCapability(*m_pCurrentServer, rest_stream, yes); } else if (pData->opState == LOGON_WELCOME) { if (!pData->gotFirstWelcomeLine) { if (line.Upper().Left(3) == _T("SSH")) { LogMessage(::Error, _("Cannot establish FTP connection to an SFTP server. Please select proper protocol.")); DoClose(FZ_REPLY_CRITICALERROR); return; } pData->gotFirstWelcomeLine = true; } } } //Check for multi-line responses if (line.Len() > 3) { if (m_MultilineResponseCode != _T("")) { if (line.Left(4) == m_MultilineResponseCode) { // end of multi-line found m_MultilineResponseCode.Clear(); m_Response = line; ParseResponse(); m_Response = _T(""); m_MultilineResponseLines.clear(); } else m_MultilineResponseLines.push_back(line); } // start of new multi-line else if (line.GetChar(3) == '-') { // DDD<SP> is the end of a multi-line response m_MultilineResponseCode = line.Left(3) + _T(" "); m_MultilineResponseLines.push_back(line); } else { m_Response = line; ParseResponse(); m_Response = _T(""); } } } void CFtpControlSocket::OnConnect() { m_lastTypeBinary = -1; SetAlive(); if (m_pCurrentServer->GetProtocol() == FTPS) { if (!m_pTlsSocket) { LogMessage(Status, _("Connection established, initializing TLS...")); wxASSERT(!m_pTlsSocket); delete m_pBackend; m_pTlsSocket = new CTlsSocket(this, m_pSocket, this); m_pBackend = m_pTlsSocket; if (!m_pTlsSocket->Init()) { LogMessage(::Error, _("Failed to initialize TLS.")); DoClose(); return; } int res = m_pTlsSocket->Handshake(); if (res == FZ_REPLY_ERROR) DoClose(); return; } else LogMessage(Status, _("TLS/SSL connection established, waiting for welcome message...")); } else if (m_pCurrentServer->GetProtocol() == FTPES && m_pTlsSocket) { LogMessage(Status, _("TLS/SSL connection established.")); return; } else LogMessage(Status, _("Connection established, waiting for welcome message...")); m_pendingReplies = 1; m_repliesToSkip = 0; Logon(); } void CFtpControlSocket::ParseResponse() { if (m_Response[0] != '1') { if (m_pendingReplies > 0) m_pendingReplies--; else { LogMessage(Debug_Warning, _T("Unexpected reply, no reply was pending.")); return; } } if (m_repliesToSkip) { LogMessage(Debug_Info, _T("Skipping reply after cancelled operation or keepalive command.")); if (m_Response[0] != '1') m_repliesToSkip--; if (!m_repliesToSkip) { SetWait(false); if (!m_pCurOpData) StartKeepaliveTimer(); else if (!m_pendingReplies) SendNextCommand(); } return; } enum Command commandId = GetCurrentCommandId(); switch (commandId) { case cmd_connect: LogonParseResponse(); break; case cmd_list: ListParseResponse(); break; case cmd_cwd: ChangeDirParseResponse(); break; case cmd_transfer: FileTransferParseResponse(); break; case cmd_raw: RawCommandParseResponse(); break; case cmd_delete: DeleteParseResponse(); break; case cmd_removedir: RemoveDirParseResponse(); break; case cmd_mkdir: MkdirParseResponse(); break; case cmd_rename: RenameParseResponse(); break; case cmd_chmod: ChmodParseResponse(); break; case cmd_rawtransfer: TransferParseResponse(); break; case cmd_none: LogMessage(Debug_Verbose, _T("Out-of-order reply, ignoring.")); break; default: LogMessage(Debug_Warning, _T("No action for parsing replies to command %d"), (int)commandId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } } bool CFtpControlSocket::GetLoginSequence(const CServer& server) { CFtpLogonOpData *pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData); pData->loginSequence.clear(); if (!pData->ftp_proxy_type) { // User t_loginCommand cmd = {false, false, user, _T("")}; pData->loginSequence.push_back(cmd); // Password cmd.optional = true; cmd.hide_arguments = true; cmd.type = pass; pData->loginSequence.push_back(cmd); // Optional account if (server.GetAccount() != _T("")) { cmd.hide_arguments = false; cmd.type = account; pData->loginSequence.push_back(cmd); } } else if (pData->ftp_proxy_type == 1) { const wxString& proxyUser = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_USER); if (proxyUser != _T("")) { // Proxy logon (if credendials are set) t_loginCommand cmd = {false, false, other, _T("USER ") + proxyUser}; pData->loginSequence.push_back(cmd); cmd.optional = true; cmd.hide_arguments = true; cmd.command = _T("PASS ") + m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_PASS); pData->loginSequence.push_back(cmd); } // User@host t_loginCommand cmd = {false, false, user, wxString::Format(_T("USER %s@%s"), server.GetUser().c_str(), server.FormatHost().c_str())}; pData->loginSequence.push_back(cmd); // Password cmd.optional = true; cmd.hide_arguments = true; cmd.type = pass; cmd.command = _T(""); pData->loginSequence.push_back(cmd); // Optional account if (server.GetAccount() != _T("")) { cmd.hide_arguments = false; cmd.type = account; pData->loginSequence.push_back(cmd); } } else if (pData->ftp_proxy_type == 2 || pData->ftp_proxy_type == 3) { const wxString& proxyUser = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_USER); if (proxyUser != _T("")) { // Proxy logon (if credendials are set) t_loginCommand cmd = {false, false, other, _T("USER ") + proxyUser}; pData->loginSequence.push_back(cmd); cmd.optional = true; cmd.hide_arguments = true; cmd.command = _T("PASS ") + m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_PASS); pData->loginSequence.push_back(cmd); } // Site or Open t_loginCommand cmd = {false, false, user, _T("")}; if (pData->ftp_proxy_type == 2) cmd.command = _T("SITE ") + server.FormatHost(); else cmd.command = _T("OPEN ") + server.FormatHost(); pData->loginSequence.push_back(cmd); // User cmd.type = user; cmd.command = _T(""); pData->loginSequence.push_back(cmd); // Password cmd.optional = true; cmd.hide_arguments = true; cmd.type = pass; pData->loginSequence.push_back(cmd); // Optional account if (server.GetAccount() != _T("")) { cmd.hide_arguments = false; cmd.type = account; pData->loginSequence.push_back(cmd); } } else if (pData->ftp_proxy_type == 4) { wxString proxyUser = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_USER); wxString proxyPass = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_PASS); wxString host = server.FormatHost(); wxString user = server.GetUser(); wxString account = server.GetAccount(); proxyUser.Replace(_T("%"), _T("%%")); proxyPass.Replace(_T("%"), _T("%%")); host.Replace(_T("%"), _T("%%")); user.Replace(_T("%"), _T("%%")); account.Replace(_T("%"), _T("%%")); wxString loginSequence = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_CUSTOMLOGINSEQUENCE); wxStringTokenizer tokens(loginSequence, _T("\n"), wxTOKEN_STRTOK); while (tokens.HasMoreTokens()) { wxString token = tokens.GetNextToken(); token.Trim(true); token.Trim(false); if (token == _T("")) continue; bool isHost = false; bool isUser = false; bool password = false; bool isProxyUser = false; bool isProxyPass = false; if (token.Find(_T("%h")) != -1) isHost = true; if (token.Find(_T("%u")) != -1) isUser = true; if (token.Find(_T("%p")) != -1) password = true; if (token.Find(_T("%s")) != -1) isProxyUser = true; if (token.Find(_T("%w")) != -1) isProxyPass = true; // Skip account if empty bool isAccount = false; if (token.Find(_T("%a")) != -1) { if (account == _T("")) continue; else isAccount = true; } if (isProxyUser && !isHost && !isUser && proxyUser == _T("")) continue; if (isProxyPass && !isHost && !isUser && proxyUser == _T("")) continue; token.Replace(_T("%s"), proxyUser); token.Replace(_T("%w"), proxyPass); token.Replace(_T("%h"), host); token.Replace(_T("%u"), user); token.Replace(_T("%a"), account); // Pass will be replaced before sending to cope with interactve login if (!password) token.Replace(_T("%%"), _T("%")); t_loginCommand cmd; if (password || isProxyPass) cmd.hide_arguments = true; else cmd.hide_arguments = false; if (isUser && !pass && !isAccount) { cmd.optional = false; cmd.type = ::user; } else if (pass && !isUser && !isAccount) { cmd.optional = true; cmd.type = ::pass; } else if (isAccount && !isUser && !pass) { cmd.optional = true; cmd.type = ::account; } else { cmd.optional = false; cmd.type = other; } cmd.command = token; pData->loginSequence.push_back(cmd); } if (pData->loginSequence.empty()) { LogMessage(::Error, _("Could not generate custom login sequence.")); return false; } } else { LogMessage(::Error, _("Unknown FTP proxy type, cannot generate login sequence.")); return false; } return true; } int CFtpControlSocket::Logon() { const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType(); if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != no) m_useUTF8 = true; else if (encoding == ENCODING_UTF8) m_useUTF8 = true; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::LogonParseResponse() { if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("LogonParseResponse without m_pCurOpData called")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_INTERNALERROR; } CFtpLogonOpData *pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData); int code = GetReplyCode(); if (pData->opState == LOGON_WELCOME) { if (code != 2 && code != 3) { DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0); return FZ_REPLY_DISCONNECTED; } } else if (pData->opState == LOGON_AUTH_TLS || pData->opState == LOGON_AUTH_SSL) { if (code != 2 && code != 3) { if (pData->opState == LOGON_AUTH_SSL) { DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0); return FZ_REPLY_DISCONNECTED; } } else { LogMessage(Status, _("Initializing TLS...")); wxASSERT(!m_pTlsSocket); delete m_pBackend; m_pTlsSocket = new CTlsSocket(this, m_pSocket, this); m_pBackend = m_pTlsSocket; if (!m_pTlsSocket->Init()) { LogMessage(::Error, _("Failed to initialize TLS.")); DoClose(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } int res = m_pTlsSocket->Handshake(); if (res == FZ_REPLY_ERROR) { DoClose(); return FZ_REPLY_ERROR; } pData->neededCommands[LOGON_AUTH_SSL] = 0; pData->opState = LOGON_AUTH_WAIT; if (res == FZ_REPLY_WOULDBLOCK) return FZ_REPLY_WOULDBLOCK; } } else if (pData->opState == LOGON_LOGON) { t_loginCommand cmd = pData->loginSequence.front(); if (code != 2 && code != 3) { if (m_pCurrentServer->GetEncodingType() == ENCODING_AUTO && m_useUTF8) { // Fall back to local charset for the case that the server might not // support UTF8 and the login data contains non-ascii characters. bool asciiOnly = true; for (unsigned int i = 0; i < m_pCurrentServer->GetUser().Length(); i++) if ((unsigned int)m_pCurrentServer->GetUser()[i] > 127) asciiOnly = false; for (unsigned int i = 0; i < m_pCurrentServer->GetPass().Length(); i++) if ((unsigned int)m_pCurrentServer->GetPass()[i] > 127) asciiOnly = false; for (unsigned int i = 0; i < m_pCurrentServer->GetAccount().Length(); i++) if ((unsigned int)m_pCurrentServer->GetAccount()[i] > 127) asciiOnly = false; if (!asciiOnly) { if (pData->ftp_proxy_type) { LogMessage(Status, _("Login data contains non-ASCII characters and server might not be UTF-8 aware. Cannot fall back to local charset since using proxy."), 0); int error = FZ_REPLY_DISCONNECTED; if (cmd.type == pass && code == 5) error |= FZ_REPLY_PASSWORDFAILED; DoClose(error); return FZ_REPLY_ERROR; } LogMessage(Status, _("Login data contains non-ASCII characters and server might not be UTF-8 aware. Trying local charset."), 0); m_useUTF8 = false; if (!GetLoginSequence(*m_pCurrentServer)) { int error = FZ_REPLY_DISCONNECTED; if (cmd.type == pass && code == 5) error |= FZ_REPLY_PASSWORDFAILED; DoClose(error); return FZ_REPLY_ERROR; } return SendNextCommand(); } } int error = FZ_REPLY_DISCONNECTED; if (cmd.type == pass && code == 5) error |= FZ_REPLY_CRITICALERROR | FZ_REPLY_PASSWORDFAILED; DoClose(error); return FZ_REPLY_ERROR; } pData->loginSequence.pop_front(); if (code == 2) { while (!pData->loginSequence.empty() && pData->loginSequence.front().optional) pData->loginSequence.pop_front(); } else if (code == 3 && pData->loginSequence.empty()) { LogMessage(::Error, _("Login sequence fully executed yet not logged in. Aborting.")); if (cmd.type == pass && m_pCurrentServer->GetAccount() == _T("")) LogMessage(::Error, _("Server might require an account. Try specifying an account using the Site Manager")); DoClose(FZ_REPLY_CRITICALERROR); return FZ_REPLY_ERROR; } if (!pData->loginSequence.empty()) { pData->waitChallenge = false; return SendNextCommand(); } } else if (pData->opState == LOGON_SYST) { if (code == 2) CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, yes, m_Response.Mid(4)); else CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, no); if (m_pCurrentServer->GetType() == DEFAULT && code == 2) { if (m_Response.Length() > 7 && m_Response.Mid(3, 4) == _T(" MVS")) m_pCurrentServer->SetType(MVS); else if (m_Response.Len() > 12 && m_Response.Mid(3, 9).Upper() == _T(" NONSTOP ")) m_pCurrentServer->SetType(HPNONSTOP); if (!m_MultilineResponseLines.empty() && m_MultilineResponseLines.front().Mid(4, 4).Upper() == _T("Z/VM")) { CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, yes, m_MultilineResponseLines.front().Mid(4) + _T(" ") + m_Response.Mid(4)); m_pCurrentServer->SetType(ZVM); } } if (m_Response.Find(_T("FileZilla")) != -1) { pData->neededCommands[LOGON_CLNT] = 0; pData->neededCommands[LOGON_OPTSUTF8] = 0; } } else if (pData->opState == LOGON_FEAT) { if (code == 2) { CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, yes); if (CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes) CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, no); if (CServerCapabilities::GetCapability(*m_pCurrentServer, clnt_command) != yes) CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, no); } else CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, no); if (CServerCapabilities::GetCapability(*m_pCurrentServer, tvfs_support) != yes) CServerCapabilities::SetCapability(*m_pCurrentServer, tvfs_support, no); const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType(); if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes) m_useUTF8 = false; } else if (pData->opState == LOGON_PROT) { if (code == 2 || code == 3) m_protectDataChannel = true; } else if (pData->opState == LOGON_CUSTOMCOMMANDS) { pData->customCommandIndex++; if (pData->customCommandIndex < m_pCurrentServer->GetPostLoginCommands().size()) return SendNextCommand(); } while (true) { pData->opState++; if (pData->opState == LOGON_DONE) { LogMessage(Status, _("Connected")); ResetOperation(FZ_REPLY_OK); return true; } if (!pData->neededCommands[pData->opState]) continue; else if (pData->opState == LOGON_SYST) { wxString system; enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), syst_command, &system); if (cap == unknown) break; else if (cap == yes) { if (m_pCurrentServer->GetType() == DEFAULT) { if (system.Left(3) == _T("MVS")) m_pCurrentServer->SetType(MVS); else if (system.Left(4).Upper() == _T("Z/VM")) m_pCurrentServer->SetType(ZVM); else if (system.Left(8).Upper() == _T("NONSTOP ")) m_pCurrentServer->SetType(HPNONSTOP); } if (system.Find(_T("FileZilla")) != -1) { pData->neededCommands[LOGON_CLNT] = 0; pData->neededCommands[LOGON_OPTSUTF8] = 0; } } } else if (pData->opState == LOGON_FEAT) { enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), feat_command); if (cap == unknown) break; } else if (pData->opState == LOGON_CLNT) { if (!m_useUTF8) continue; if (CServerCapabilities::GetCapability(*GetCurrentServer(), clnt_command) == yes) break; } else if (pData->opState == LOGON_OPTSUTF8) { if (!m_useUTF8) continue; if (CServerCapabilities::GetCapability(*GetCurrentServer(), utf8_command) == yes) break; } else if (pData->opState == LOGON_OPTSMLST) { wxString facts; if (CServerCapabilities::GetCapability(*GetCurrentServer(), mlsd_command, &facts) != yes) continue; capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), opst_mlst_command); if (cap == unknown) { MakeLowerAscii(facts); bool had_unset = false; wxString opts_facts; // Create a list of all facts understood by both FZ and the server. // Check if there's any supported fact not enabled by default, should that // be the case we need to send OPTS MLST while (!facts.IsEmpty()) { int delim = facts.Find(';'); if (delim == -1) break; if (!delim) { facts = facts.Mid(1); continue; } bool enabled; wxString fact; if (facts[delim - 1] == '*') { if (delim == 1) { facts = facts.Mid(delim + 1); continue; } enabled = true; fact = facts.Left(delim - 1); } else { enabled = false; fact = facts.Left(delim); } facts = facts.Mid(delim + 1); if (fact == _T("type") || fact == _T("size") || fact == _T("modify") || fact == _T("perm") || fact == _T("unix.mode") || fact == _T("unix.owner") || fact == _T("unix.user") || fact == _T("unix.group") || fact == _T("unix.uid") || fact == _T("unix.gid") || fact == _T("x.hidden")) { had_unset |= !enabled; opts_facts += fact + _T(";"); } } if (had_unset) { CServerCapabilities::SetCapability(*GetCurrentServer(), opst_mlst_command, yes, opts_facts); break; } else CServerCapabilities::SetCapability(*GetCurrentServer(), opst_mlst_command, no); } else if (cap == yes) break; } else break; } return SendNextCommand(); } int CFtpControlSocket::LogonSend() { if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("LogonParseResponse without m_pCurOpData called")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_INTERNALERROR; } CFtpLogonOpData *pData = reinterpret_cast<CFtpLogonOpData *>(m_pCurOpData); bool res; switch (pData->opState) { case LOGON_AUTH_WAIT: res = FZ_REPLY_WOULDBLOCK; LogMessage(Debug_Info, _T("LogonSend() called during LOGON_AUTH_WAIT, ignoring")); break; case LOGON_AUTH_TLS: res = Send(_T("AUTH TLS")); break; case LOGON_AUTH_SSL: res = Send(_T("AUTH SSL")); break; case LOGON_SYST: res = Send(_T("SYST")); break; case LOGON_LOGON: { t_loginCommand cmd = pData->loginSequence.front(); switch (cmd.type) { case user: if (m_pCurrentServer->GetLogonType() == INTERACTIVE) { pData->waitChallenge = true; pData->challenge = _T(""); } if (cmd.command == _T("")) res = Send(_T("USER ") + m_pCurrentServer->GetUser()); else res = Send(cmd.command); break; case pass: if (pData->challenge != _T("")) { CInteractiveLoginNotification *pNotification = new CInteractiveLoginNotification(pData->challenge); pNotification->server = *m_pCurrentServer; pData->challenge = _T(""); SendAsyncRequest(pNotification); return FZ_REPLY_WOULDBLOCK; } if (cmd.command == _T("")) res = Send(_T("PASS ") + m_pCurrentServer->GetPass(), true); else { wxString c = cmd.command; wxString pass = m_pCurrentServer->GetPass(); pass.Replace(_T("%"), _T("%%")); c.Replace(_T("%p"), pass); c.Replace(_T("%%"), _T("%")); res = Send(c, true); } break; case account: if (cmd.command == _T("")) res = Send(_T("ACCT ") + m_pCurrentServer->GetAccount()); else res = Send(cmd.command); break; case other: wxASSERT(cmd.command != _T("")); res = Send(cmd.command, cmd.hide_arguments); break; default: res = false; break; } } break; case LOGON_FEAT: res = Send(_T("FEAT")); break; case LOGON_CLNT: // Some servers refuse to enable UTF8 if client does not send CLNT command // to fix compatibility with Internet Explorer, but in the process breaking // compatibility with other clients. // Rather than forcing MS to fix Internet Explorer, letting other clients // suffer is a questionable decision in my opinion. res = Send(_T("CLNT FileZilla")); break; case LOGON_OPTSUTF8: // Handle servers that disobey RFC 2640 by having UTF8 in their FEAT // response but do not use UTF8 unless OPTS UTF8 ON gets send. // However these servers obey a conflicting ietf draft: // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt // Example servers are, amongst others, G6 FTP Server and RaidenFTPd. res = Send(_T("OPTS UTF8 ON")); break; case LOGON_PBSZ: res = Send(_T("PBSZ 0")); break; case LOGON_PROT: res = Send(_T("PROT P")); break; case LOGON_CUSTOMCOMMANDS: if (pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size()) { LogMessage(Debug_Warning, _T("pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size()")); DoClose(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } res = Send(m_pCurrentServer->GetPostLoginCommands()[pData->customCommandIndex]); break; case LOGON_OPTSMLST: { wxString args; CServerCapabilities::GetCapability(*GetCurrentServer(), opst_mlst_command, &args); res = Send(_T("OPTS MLST " + args)); } break; default: return FZ_REPLY_ERROR; } if (!res) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::GetReplyCode() const { if (m_Response == _T("")) return 0; if (m_Response[0] < '0' || m_Response[0] > '9') return 0; return m_Response[0] - '0'; } bool CFtpControlSocket::Send(wxString str, bool maskArgs /*=false*/) { int pos; if (maskArgs && (pos = str.Find(_T(" "))) != -1) { wxString stars('*', str.Length() - pos - 1); LogMessageRaw(Command, str.Left(pos + 1) + stars); } else LogMessageRaw(Command, str); str += _T("\r\n"); wxCharBuffer buffer = ConvToServer(str); if (!buffer) { LogMessage(::Error, _T("Failed to convert command to 8 bit charset")); return false; } unsigned int len = (unsigned int)strlen(buffer); bool res = CRealControlSocket::Send(buffer, len); if (res) m_pendingReplies++; return res; } class CFtpListOpData : public COpData, public CFtpTransferOpData { public: CFtpListOpData() : COpData(cmd_list) { viewHiddenCheck = false; viewHidden = false; m_pDirectoryListingParser = 0; mdtm_index = 0; fallback_to_current = false; } virtual ~CFtpListOpData() { delete m_pDirectoryListingParser; } CServerPath path; wxString subDir; bool fallback_to_current; CDirectoryListingParser* m_pDirectoryListingParser; CDirectoryListing directoryListing; // Set to true to get a directory listing even if a cache // lookup can be made after finding out true remote directory bool refresh; bool viewHiddenCheck; bool viewHidden; // Uses LIST -a command // Listing index for list_mdtm int mdtm_index; CTimeEx m_time_before_locking; }; enum listStates { list_init = 0, list_waitcwd, list_waitlock, list_waittransfer, list_mdtm }; int CFtpControlSocket::List(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, int flags /*=0*/) { LogMessage(Status, _("Retrieving directory listing...")); if (m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("List called from other command")); } CFtpListOpData *pData = new CFtpListOpData; 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 = (flags & LIST_FLAG_REFRESH) != 0; pData->fallback_to_current = !path.IsEmpty() && (flags & LIST_FLAG_FALLBACK_CURRENT) != 0; int res = ChangeDir(path, subDir, (flags & LIST_FLAG_LINK) != 0); if (res != FZ_REPLY_OK) return res; return ParseSubcommandResult(FZ_REPLY_OK); } int CFtpControlSocket::ListSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ListSubcommandResult()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData); LogMessage(Debug_Debug, _T(" state = %d"), pData->opState); if (pData->opState == list_waitcwd) { if (prevResult != FZ_REPLY_OK) { if (prevResult & FZ_REPLY_LINKNOTDIR) { ResetOperation(prevResult); return FZ_REPLY_ERROR; } if (pData->fallback_to_current) { // List current directory instead pData->fallback_to_current = false; pData->path.Clear(); pData->subDir = _T(""); int res = ChangeDir(); if (res != FZ_REPLY_OK) return res; } else { ResetOperation(prevResult); return FZ_REPLY_ERROR; } } if (pData->path.IsEmpty()) { pData->path = m_CurrentPath; wxASSERT(pData->subDir == _T("")); wxASSERT(!pData->path.IsEmpty()); } if (!pData->refresh) { wxASSERT(!pData->pNextOpData); // Do a cache lookup now that we know the correct directory CDirectoryCache cache; int hasUnsureEntries; bool is_outdated = false; bool found = cache.DoesExist(*m_pCurrentServer, m_CurrentPath, hasUnsureEntries, is_outdated); if (found) { // We're done if listing is recent and has no outdated entries if (!is_outdated && !hasUnsureEntries) { m_pEngine->SendDirectoryListingNotification(m_CurrentPath, true, false, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } } } if (!pData->holdsLock) { if (!TryLockCache(lock_list, m_CurrentPath)) { pData->opState = list_waitlock; pData->m_time_before_locking = CTimeEx::Now(); return FZ_REPLY_WOULDBLOCK; } } delete m_pTransferSocket; m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list); pData->m_pDirectoryListingParser = new CDirectoryListingParser(this, *m_pCurrentServer); pData->m_pDirectoryListingParser->SetTimezoneOffset(GetTimezoneOffset()); m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser; InitTransferStatus(-1, 0, true); pData->opState = list_waittransfer; if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command) == yes) return Transfer(_T("MLSD"), pData); else { if (m_pEngine->GetOptions()->GetOptionVal(OPTION_VIEW_HIDDEN_FILES)) { enum capabilities cap = CServerCapabilities::GetCapability(*m_pCurrentServer, list_hidden_support); if (cap == unknown) pData->viewHiddenCheck = true; else if (cap == yes) pData->viewHidden = true; else LogMessage(Debug_Info, _("View hidden option set, but unsupported by server")); } if (pData->viewHidden) return Transfer(_T("LIST -a"), pData); else return Transfer(_T("LIST"), pData); } } else if (pData->opState == list_waittransfer) { if (prevResult == FZ_REPLY_OK) { CDirectoryListing listing = pData->m_pDirectoryListingParser->Parse(m_CurrentPath); if (pData->viewHiddenCheck) { if (!pData->viewHidden) { // Repeat with LIST -a pData->viewHidden = true; pData->directoryListing = listing; // Reset status pData->transferEndReason = successful; pData->tranferCommandSent = false; delete m_pTransferSocket; m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list); pData->m_pDirectoryListingParser->Reset(); m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser; return Transfer(_T("LIST -a"), pData); } else { if (CheckInclusion(listing, pData->directoryListing)) { LogMessage(Debug_Info, _T("Server seems to support LIST -a")); CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes); } else { LogMessage(Debug_Info, _T("Server does not seem to support LIST -a")); CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no); listing = pData->directoryListing; } } } SetAlive(); int res = ListCheckTimezoneDetection(listing); if (res != FZ_REPLY_OK) return res; CDirectoryCache cache; cache.Store(listing, *m_pCurrentServer); m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else { if (pData->tranferCommandSent && IsMisleadingListResponse()) { CDirectoryListing listing; listing.path = m_CurrentPath; listing.m_firstListTime = CTimeEx::Now(); if (pData->viewHiddenCheck) { if (pData->viewHidden) { if (pData->directoryListing.GetCount()) { // Less files with LIST -a // Not supported LogMessage(Debug_Info, _T("Server does not seem to support LIST -a")); CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no); listing = pData->directoryListing; } else { LogMessage(Debug_Info, _T("Server seems to support LIST -a")); CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes); } } else { // Reset status pData->transferEndReason = successful; pData->tranferCommandSent = false; delete m_pTransferSocket; m_pTransferSocket = new CTransferSocket(m_pEngine, this, ::list); pData->m_pDirectoryListingParser->Reset(); m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser; // Repeat with LIST -a pData->viewHidden = true; pData->directoryListing = listing; return Transfer(_T("LIST -a"), pData); } } int res = ListCheckTimezoneDetection(listing); if (res != FZ_REPLY_OK) return res; CDirectoryCache cache; cache.Store(listing, *m_pCurrentServer); m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else { if (pData->viewHiddenCheck) { // If server does not support LIST -a, the server might reject this command // straight away. In this case, back to the previously retrieved listing. // On other failures like timeouts and such, return an error if (pData->viewHidden && pData->transferEndReason == transfer_command_failure_immediate) { CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no); int res = ListCheckTimezoneDetection(pData->directoryListing); if (res != FZ_REPLY_OK) return res; CDirectoryCache cache; cache.Store(pData->directoryListing, *m_pCurrentServer); m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } } if (prevResult & FZ_REPLY_ERROR) m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, true); } ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } else { ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } return SendNextCommand(); } int CFtpControlSocket::ListSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ListSend()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData); LogMessage(Debug_Debug, _T(" state = %d"), pData->opState); if (pData->opState == list_waitlock) { if (!pData->holdsLock) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Not holding the lock as expected")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } // Check if we can use already existing listing CDirectoryListing listing; CDirectoryCache cache; bool is_outdated = false; wxASSERT(pData->subDir == _T("")); // Did do ChangeDir before trying to lock bool found = cache.Lookup(listing, *m_pCurrentServer, pData->path, true, is_outdated); if (found && !is_outdated && !listing.m_hasUnsureEntries && listing.m_firstListTime > pData->m_time_before_locking) { m_pEngine->SendDirectoryListingNotification(listing.path, !pData->pNextOpData, false, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } return ListSubcommandResult(FZ_REPLY_OK); } if (pData->opState == list_mdtm) { LogMessage(Status, _("Calculating timezone offset of server...")); wxString cmd = _T("MDTM ") + m_CurrentPath.FormatFilename(pData->directoryListing[pData->mdtm_index].name, true); if (!Send(cmd)) return FZ_REPLY_ERROR; else return FZ_REPLY_WOULDBLOCK; } LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("invalid opstate")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } int CFtpControlSocket::ListParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ListParseResponse()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData); if (pData->opState != list_mdtm) { LogMessage(Debug_Warning, _T("ListParseResponse should never be called if opState != list_mdtm")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } // First condition prevents problems with concurrent MDTM if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown && m_Response.Left(4) == _T("213 ") && m_Response.Length() > 16) { wxDateTime date; const wxChar *res = date.ParseFormat(m_Response.Mid(4), _T("%Y%m%d%H%M%S")); if (res && date.IsValid()) { wxASSERT(pData->directoryListing[pData->mdtm_index].hasTimestamp != CDirentry::timestamp_none); wxDateTime listTime = pData->directoryListing[pData->mdtm_index].time; listTime -= wxTimeSpan(0, m_pCurrentServer->GetTimezoneOffset(), 0); int serveroffset = (date - listTime).GetSeconds().GetLo(); if (pData->directoryListing[pData->mdtm_index].hasTimestamp != CDirentry::timestamp_seconds) { // Round offset to full minutes if (serveroffset < 0) serveroffset -= 59; serveroffset -= serveroffset % 60; } wxDateTime now = wxDateTime::Now(); wxDateTime now_utc = now.ToTimezone(wxDateTime::GMT0); int localoffset = (now - now_utc).GetSeconds().GetLo(); int offset = serveroffset + localoffset; LogMessage(Status, _("Timezone offsets: Server: %d seconds. Local: %d seconds. Difference: %d seconds."), -serveroffset, localoffset, offset); wxTimeSpan span(0, 0, offset); const int count = pData->directoryListing.GetCount(); for (int i = 0; i < count; i++) { CDirentry& entry = pData->directoryListing[i]; if (entry.hasTimestamp < CDirentry::timestamp_time) continue; entry.time += span; } // TODO: Correct cached listings CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, yes, offset); } else { CServerCapabilities::SetCapability(*m_pCurrentServer, mdtm_command, no); CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no); } } else CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no); CDirectoryCache cache; cache.Store(pData->directoryListing, *m_pCurrentServer); m_pEngine->SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } int CFtpControlSocket::ListCheckTimezoneDetection(CDirectoryListing& listing) { wxASSERT(m_pCurOpData); CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData); if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown) { if (CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) != yes) CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no); else { const int count = listing.GetCount(); for (int i = 0; i < count; i++) { if (!listing[i].dir && listing[i].hasTimestamp >= CDirentry::timestamp_time) { pData->opState = list_mdtm; pData->directoryListing = listing; pData->mdtm_index = i; return SendNextCommand(); } } } } return FZ_REPLY_OK; } int CFtpControlSocket::ResetOperation(int nErrorCode) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ResetOperation(%d)"), nErrorCode); CTransferSocket* pTransferSocket = m_pTransferSocket; m_pTransferSocket = 0; delete pTransferSocket; delete m_pIPResolver; m_pIPResolver = 0; m_repliesToSkip = m_pendingReplies; if (m_pCurOpData && m_pCurOpData->opId == cmd_transfer) { CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); if (pData->tranferCommandSent) { if (pData->transferEndReason == transfer_failure_critical) nErrorCode |= FZ_REPLY_CRITICALERROR | FZ_REPLY_WRITEFAILED; if (pData->transferEndReason != transfer_command_failure_immediate || GetReplyCode() != 5) pData->transferInitiated = true; else { if (nErrorCode == FZ_REPLY_ERROR) nErrorCode |= FZ_REPLY_CRITICALERROR; } } if (nErrorCode != FZ_REPLY_OK && pData->download && !pData->fileDidExist) { delete pData->pIOThread; pData->pIOThread = 0; wxLongLong size; bool isLink; if (CLocalFileSystem::GetFileInfo(pData->localFile, isLink, &size, 0, 0) == CLocalFileSystem::file && size == 0) { // Download failed and a new local file was created before, but // nothing has been written to it. Remove it again, so we don't // leave a bunch of empty files all over the place. LogMessage(Debug_Verbose, _T("Deleting empty file")); wxRemoveFile(pData->localFile); } } } if (m_pCurOpData && m_pCurOpData->opId == cmd_delete && !(nErrorCode & FZ_REPLY_DISCONNECTED)) { CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData); if (pData->m_needSendListing) m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false); } if (m_pCurOpData && m_pCurOpData->opId == cmd_rawtransfer && nErrorCode != FZ_REPLY_OK) { CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData); if (pData->pOldData->transferEndReason == successful) { if ((nErrorCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT) pData->pOldData->transferEndReason = timeout; else if (!pData->pOldData->tranferCommandSent) pData->pOldData->transferEndReason = pre_transfer_command_failure; else pData->pOldData->transferEndReason = failure; } } m_lastCommandCompletionTime = wxDateTime::Now(); if (m_pCurOpData && !(nErrorCode & FZ_REPLY_DISCONNECTED)) StartKeepaliveTimer(); else m_idleTimer.Stop(); return CControlSocket::ResetOperation(nErrorCode); } int CFtpControlSocket::SendNextCommand() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::SendNextCommand()")); 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; } if (m_repliesToSkip) { LogMessage(__TFILE__, __LINE__, this, Status, _T("Waiting for replies to skip before sending next command...")); SetWait(true); return FZ_REPLY_WOULDBLOCK; } switch (m_pCurOpData->opId) { case cmd_list: return ListSend(); case cmd_connect: return LogonSend(); case cmd_cwd: return ChangeDirSend(); case cmd_transfer: return FileTransferSend(); case cmd_mkdir: return MkdirSend(); case cmd_rename: return RenameSend(); case cmd_chmod: return ChmodSend(); case cmd_rawtransfer: return TransferSend(); case cmd_raw: return RawCommandSend(); case cmd_delete: return DeleteSend(); case cmd_removedir: return RemoveDirSend(); default: LogMessage(__TFILE__, __LINE__, this, ::Debug_Warning, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } return FZ_REPLY_ERROR; } class CFtpChangeDirOpData : public CChangeDirOpData { public: CFtpChangeDirOpData() { tried_cdup = false; } virtual ~CFtpChangeDirOpData() { } bool tried_cdup; }; enum cwdStates { cwd_init = 0, cwd_pwd, cwd_cwd, cwd_pwd_cwd, cwd_cwd_subdir, cwd_pwd_subdir }; int CFtpControlSocket::ChangeDir(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, bool link_discovery /*=false*/) { 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 target = CPathCache::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 = CPathCache::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 { target = CPathCache::Lookup(*m_pCurrentServer, path, _T("")); if (m_CurrentPath == path || (!target.IsEmpty() && target == m_CurrentPath)) return FZ_REPLY_OK; state = cwd_cwd; } } CFtpChangeDirOpData *pData = new CFtpChangeDirOpData; pData->pNextOpData = m_pCurOpData; pData->opState = state; pData->path = path; pData->subDir = subDir; pData->target = target; pData->link_discovery = link_discovery; if (pData->pNextOpData && pData->pNextOpData->opId == cmd_transfer && !static_cast<CFtpFileTransferOpData *>(pData->pNextOpData)->download) { pData->tryMkdOnFail = true; wxASSERT(subDir == _T("")); } m_pCurOpData = pData; return SendNextCommand(); } int CFtpControlSocket::ChangeDirParseResponse() { if (!m_pCurOpData) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData); int code = GetReplyCode(); bool error = false; switch (pData->opState) { case cwd_pwd: if (code != 2 && code != 3) error = true; else if (ParsePwdReply(m_Response)) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else error = true; break; case cwd_cwd: if (code != 2 && code != 3) { // Create remote directory if part of a file upload if (pData->tryMkdOnFail) { pData->tryMkdOnFail = false; int res = Mkdir(pData->path); if (res != FZ_REPLY_OK) return res; } else error = true; } else { if (pData->target.IsEmpty()) pData->opState = cwd_pwd_cwd; else { m_CurrentPath = pData->target; if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } pData->target.Clear(); pData->opState = cwd_cwd_subdir; } } break; case cwd_pwd_cwd: if (code != 2 && code != 3) { LogMessage(Debug_Warning, _T("PWD failed, assuming path is '%s'."), pData->path.GetPath().c_str()); m_CurrentPath = pData->path; if (pData->target.IsEmpty()) CPathCache::Store(*m_pCurrentServer, m_CurrentPath, pData->path); if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else pData->opState = cwd_cwd_subdir; } else if (ParsePwdReply(m_Response, false, pData->path)) { if (pData->target.IsEmpty()) { CPathCache::Store(*m_pCurrentServer, m_CurrentPath, pData->path); } if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else pData->opState = cwd_cwd_subdir; } else error = true; break; case cwd_cwd_subdir: if (code != 2 && code != 3) { if (pData->subDir == _T("..") && !pData->tried_cdup && m_Response.Left(2) == _T("50")) { // CDUP command not implemented, try again using CWD .. pData->tried_cdup = true; } else if (pData->link_discovery) { LogMessage(Debug_Info, _T("Symlink does not link to a directory, probably a file")); ResetOperation(FZ_REPLY_LINKNOTDIR); return FZ_REPLY_ERROR; } else error = true; } else pData->opState = cwd_pwd_subdir; break; case cwd_pwd_subdir: { CServerPath assumedPath(pData->path); if (pData->subDir == _T("..")) { if (!assumedPath.HasParent()) assumedPath.Clear(); else assumedPath = assumedPath.GetParent(); } else assumedPath.AddSegment(pData->subDir); if (code != 2 && code != 3) { if (!assumedPath.IsEmpty()) { LogMessage(Debug_Warning, _T("PWD failed, assuming path is '%s'."), assumedPath.GetPath().c_str()); m_CurrentPath = assumedPath; if (pData->target.IsEmpty()) { CPathCache::Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir); } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else { LogMessage(Debug_Warning, _T("PWD failed, unable to guess current path.")); error = true; } } else if (ParsePwdReply(m_Response, false, assumedPath)) { if (pData->target.IsEmpty()) { CPathCache::Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir); } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else error = true; } break; } if (error) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return SendNextCommand(); } int CFtpControlSocket::ChangeDirSubcommandResult(int WXUNUSED(prevResult)) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ChangeDirSubcommandResult()")); return SendNextCommand(); } int CFtpControlSocket::ChangeDirSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ChangeDirSend()")); if (!m_pCurOpData) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData); wxString cmd; switch (pData->opState) { case cwd_pwd: case cwd_pwd_cwd: case cwd_pwd_subdir: cmd = _T("PWD"); break; case cwd_cwd: if (pData->tryMkdOnFail && !pData->holdsLock) { if (IsLocked(lock_mkdir, pData->path)) { // Some other engine is already creating this directory or // performing an action that will lead to its creation pData->tryMkdOnFail = false; } if (!TryLockCache(lock_mkdir, pData->path)) return FZ_REPLY_WOULDBLOCK; } cmd = _T("CWD ") + pData->path.GetPath(); m_CurrentPath.Clear(); break; case cwd_cwd_subdir: if (pData->subDir == _T("")) { ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } else if (pData->subDir == _T("..") && !pData->tried_cdup) cmd = _T("CDUP"); else cmd = _T("CWD ") + pData->path.FormatSubdir(pData->subDir); m_CurrentPath.Clear(); break; } if (cmd != _T("")) if (!Send(cmd)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath, const wxString &remoteFile, bool download, const CFileTransferCommand::t_transferSettings& transferSettings) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::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; } CFtpFileTransferOpData *pData = new CFtpFileTransferOpData(localFile, remoteFile, remotePath); m_pCurOpData = pData; pData->download = download; pData->transferSettings = transferSettings; pData->binary = transferSettings.binary; wxLongLong size; bool isLink; if (CLocalFileSystem::GetFileInfo(pData->localFile, isLink, &size, 0, 0) == CLocalFileSystem::file) pData->localFileSize = size.GetValue(); pData->opState = filetransfer_waitcwd; if (pData->remotePath.GetType() == DEFAULT) pData->remotePath.SetType(m_pCurrentServer->GetType()); int res = ChangeDir(pData->remotePath); if (res != FZ_REPLY_OK) return res; return ParseSubcommandResult(FZ_REPLY_OK); } int CFtpControlSocket::FileTransferParseResponse() { LogMessage(Debug_Verbose, _T("FileTransferParseResponse()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); if (pData->opState == list_init) return FZ_REPLY_ERROR; int code = GetReplyCode(); bool error = false; switch (pData->opState) { case filetransfer_size: if (code != 2 && code != 3) { if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == yes || !m_Response.Mid(4).CmpNoCase(_T("file not found")) || (pData->remotePath.FormatFilename(pData->remoteFile).Lower().Find(_T("file not found")) == -1 && m_Response.Lower().Find(_T("file not found")) != -1)) { // Server supports SIZE command but command failed. Most likely MDTM will fail as well, so // skip it. pData->opState = filetransfer_resumetest; int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } else pData->opState = filetransfer_mdtm; } else { pData->opState = filetransfer_mdtm; if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 4) { if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == unknown) CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes); wxString str = m_Response.Mid(4); wxFileOffset size = 0; const wxChar *buf = str.c_str(); while (*buf) { if (*buf < '0' || *buf > '9') break; size *= 10; size += *buf - '0'; buf++; } pData->remoteFileSize = size; } else LogMessage(Debug_Info, _T("Invalid SIZE reply")); } break; case filetransfer_mdtm: pData->opState = filetransfer_resumetest; if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 16) { wxDateTime date; const wxChar *res = date.ParseFormat(m_Response.Mid(4), _T("%Y%m%d%H%M")); if (res && date.IsValid()) { pData->fileTime = date.FromTimezone(wxDateTime::GMT0); pData->fileTime += wxTimeSpan(0, m_pCurrentServer->GetTimezoneOffset(), 0); } } { int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } break; case filetransfer_mfmt: ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; default: LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown op state")); error = true; break; } if (error) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return SendNextCommand(); } int CFtpControlSocket::FileTransferSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("FileTransferSubcommandResult()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); if (pData->opState == filetransfer_waitcwd) { if (prevResult == FZ_REPLY_OK) { CDirentry entry; bool dirDidExist; bool matchedCase; CDirectoryCache cache; bool found = cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase); if (!found) { if (!dirDidExist) pData->opState = filetransfer_waitlist; else if (pData->download && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } else pData->opState = filetransfer_resumetest; } else { if (entry.unsure) pData->opState = filetransfer_waitlist; else { if (matchedCase) { pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32); if (entry.hasTimestamp != CDirentry::timestamp_none) pData->fileTime = entry.time; if (pData->download && entry.hasTimestamp < CDirentry::timestamp_time && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } else pData->opState = filetransfer_resumetest; } else pData->opState = filetransfer_size; } } if (pData->opState == filetransfer_waitlist) { int res = List(CServerPath(), _T(""), LIST_FLAG_REFRESH); if (res != FZ_REPLY_OK) return res; ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } else if (pData->opState == filetransfer_resumetest) { int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } } else { pData->tryAbsolutePath = true; pData->opState = filetransfer_size; } } else if (pData->opState == filetransfer_waitlist) { if (prevResult == FZ_REPLY_OK) { CDirentry entry; bool dirDidExist; bool matchedCase; CDirectoryCache cache; bool found = cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase); if (!found) { if (!dirDidExist) pData->opState = filetransfer_size; else if (pData->download && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } else pData->opState = filetransfer_resumetest; } else { if (matchedCase && !entry.unsure) { pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32); if (entry.hasTimestamp != CDirentry::timestamp_none) pData->fileTime = entry.time; if (pData->download && entry.hasTimestamp < CDirentry::timestamp_time && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } else pData->opState = filetransfer_resumetest; } else pData->opState = filetransfer_size; } if (pData->opState == filetransfer_resumetest) { int res = CheckOverwriteFile(); if (res != FZ_REPLY_OK) return res; } } else pData->opState = filetransfer_size; } else if (pData->opState == filetransfer_waittransfer) { if (prevResult == FZ_REPLY_OK && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS)) { if (!pData->download && CServerCapabilities::GetCapability(*m_pCurrentServer, mfmt_command) == yes) { wxFileName fn(pData->localFile); if (fn.FileExists()) { pData->fileTime = fn.GetModificationTime(); if (pData->fileTime.IsValid()) { pData->opState = filetransfer_mfmt; return SendNextCommand(); } } } else if (pData->download && pData->fileTime.IsValid()) { wxFileName fn(pData->localFile); if (fn.FileExists()) { // Need to close file first delete pData->pIOThread; pData->pIOThread = 0; fn.SetTimes(&pData->fileTime, &pData->fileTime, 0); } } } ResetOperation(prevResult); return prevResult; } else if (pData->opState == filetransfer_waitresumetest) { if (prevResult != FZ_REPLY_OK) { if (pData->transferEndReason == failed_resumetest) { if (pData->localFileSize > ((wxFileOffset)1 << 32)) { CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, yes); LogMessage(::Error, _("Server does not support resume of files > 4GB.")); } else { CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, yes); LogMessage(::Error, _("Server does not support resume of files > 2GB.")); } ResetOperation(prevResult | FZ_REPLY_CRITICALERROR); return FZ_REPLY_ERROR; } else ResetOperation(prevResult); return prevResult; } if (pData->localFileSize > ((wxFileOffset)1 << 32)) CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, no); else CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, no); pData->opState = filetransfer_transfer; } return SendNextCommand(); } int CFtpControlSocket::FileTransferSend() { 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; } CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); wxString cmd; switch (pData->opState) { case filetransfer_size: cmd = _T("SIZE "); cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath); break; case filetransfer_mdtm: cmd = _T("MDTM "); cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath); break; case filetransfer_resumetest: case filetransfer_transfer: if (m_pTransferSocket) { LogMessage(__TFILE__, __LINE__, this, Debug_Verbose, _T("m_pTransferSocket != 0")); delete m_pTransferSocket; m_pTransferSocket = 0; } { wxFile* pFile = new wxFile; if (pData->download) { // Be quiet wxLogNull nullLog; wxFileOffset startOffset = 0; // Potentially racy bool didExist = wxFile::Exists(pData->localFile); if (pData->resume) { if (!pFile->Open(pData->localFile, wxFile::write_append)) { delete pFile; LogMessage(::Error, _("Failed to open \"%s\" for appending/writing"), pData->localFile.c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } pData->fileDidExist = didExist; startOffset = pFile->SeekEnd(0); if (startOffset == wxInvalidOffset) { delete pFile; LogMessage(::Error, _("Could not seek to the end of the file")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } pData->localFileSize = pFile->Length(); // Check resume capabilities if (pData->opState == filetransfer_resumetest) { int res = FileTransferTestResumeCapability(); if ((res & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) { delete pFile; // Server does not support resume but remote and local filesizes are equal return FZ_REPLY_OK; } if (res != FZ_REPLY_OK) { delete pFile; return res; } } } else { CreateLocalDir(pData->localFile); if (!pFile->Open(pData->localFile, wxFile::write)) { delete pFile; LogMessage(::Error, _("Failed to open \"%s\" for writing"), pData->localFile.c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } pData->fileDidExist = didExist; pData->localFileSize = pFile->Length(); } if (pData->resume) pData->resumeOffset = pData->localFileSize; else pData->resumeOffset = 0; InitTransferStatus(pData->remoteFileSize, startOffset, false); } else { if (!pFile->Open(pData->localFile, wxFile::read)) { delete pFile; LogMessage(::Error, _("Failed to open \"%s\" for reading"), pData->localFile.c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } wxFileOffset startOffset; if (pData->resume) { if (pData->remoteFileSize > 0) { startOffset = pData->remoteFileSize; // Assume native 64 bit type exists if (pFile->Seek(startOffset, wxFromStart) == wxInvalidOffset) { delete pFile; LogMessage(::Error, _("Could not seek to offset %s within file"), wxLongLong(startOffset).ToString().c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } else startOffset = 0; } else startOffset = 0; if (CServerCapabilities::GetCapability(*m_pCurrentServer, rest_stream) == yes) { // Use REST + STOR if resuming pData->resumeOffset = startOffset; } else { // Play it safe, use APPE if resuming pData->resumeOffset = 0; } wxFileOffset len = pFile->Length(); InitTransferStatus(len, startOffset, false); } pData->pIOThread = new CIOThread; if (!pData->pIOThread->Create(pFile, !pData->download, pData->binary)) { // CIOThread will delete pFile delete pData->pIOThread; pData->pIOThread = 0; LogMessage(::Error, _("Could not spawn IO thread")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } m_pTransferSocket = new CTransferSocket(m_pEngine, this, pData->download ? download : upload); m_pTransferSocket->m_binaryMode = pData->transferSettings.binary; if (pData->download) cmd = _T("RETR "); else if (pData->resume) { if (CServerCapabilities::GetCapability(*m_pCurrentServer, rest_stream) == yes) cmd = _T("STOR "); // In this case REST gets sent since resume offset was set earlier else { wxASSERT(pData->resumeOffset == 0); cmd = _T("APPE "); } } else cmd = _T("STOR "); cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath); pData->opState = filetransfer_waittransfer; return Transfer(cmd, pData); case filetransfer_mfmt: { cmd = _T("MFMT "); cmd += pData->fileTime.ToTimezone(wxDateTime::GMT0).Format(_T("%Y%m%d%H%M%S ")); cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath); break; } default: LogMessage(::Debug_Warning, _T("Unhandled opState: %d"), pData->opState); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (cmd != _T("")) if (!Send(cmd)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } void CFtpControlSocket::TransferEnd() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferEnd()")); // If m_pTransferSocket is zero, the message was sent by the previous command. // We can safely ignore it. // It does not cause problems, since before creating the next transfer socket, other // messages which were added to the queue later than this one will be processed first. if (!m_pCurOpData || !m_pTransferSocket || GetCurrentCommandId() != cmd_rawtransfer) { LogMessage(Debug_Verbose, _T("Call to TransferEnd at unusual time, ignoring")); return; } enum TransferEndReason reason = m_pTransferSocket->GetTransferEndreason(); if (reason == ::none) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Call to TransferEnd at unusual time")); return; } if (reason == successful) SetAlive(); CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData); if (pData->pOldData->transferEndReason == successful) pData->pOldData->transferEndReason = reason; switch (m_pCurOpData->opState) { case rawtransfer_transfer: pData->opState = rawtransfer_waittransferpre; break; case rawtransfer_waitfinish: pData->opState = rawtransfer_waittransfer; break; case rawtransfer_waitsocket: ResetOperation((reason == successful) ? FZ_REPLY_OK : FZ_REPLY_ERROR); break; default: LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("TransferEnd at unusual op state, ignoring")); break; } } bool CFtpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification) { if (m_pCurOpData) { if (!m_pCurOpData->waitForAsyncRequest) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID()); return false; } m_pCurOpData->waitForAsyncRequest = false; } 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; } CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); CFileExistsNotification *pFileExistsNotification = reinterpret_cast<CFileExistsNotification *>(pNotification); switch (pFileExistsNotification->overwriteAction) { case CFileExistsNotification::rename: if (pData->download) { wxFileName fn = pData->localFile; fn.SetFullName(pFileExistsNotification->newName); pData->localFile = fn.GetFullPath(); wxLongLong size; bool isLink; if (CLocalFileSystem::GetFileInfo(pData->localFile, isLink, &size, 0, 0) == CLocalFileSystem::file) pData->localFileSize = size.GetValue(); else pData->localFileSize = -1; if (CheckOverwriteFile() == FZ_REPLY_OK) SendNextCommand(); } else { pData->remoteFile = pFileExistsNotification->newName; pData->remoteFileSize = -1; pData->fileTime = wxDateTime(); CDirectoryCache cache; CDirentry entry; bool dir_did_exist; bool matched_case; if (!cache.LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dir_did_exist, matched_case) || !matched_case) { if (m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } } else // found and matched case { if (matched_case) { pData->remoteFileSize = entry.size.GetLo() + ((wxFileOffset)entry.size.GetHi() << 32); if (entry.hasTimestamp != CDirentry::timestamp_none) pData->fileTime = entry.time; if (pData->download && entry.hasTimestamp < CDirentry::timestamp_time && m_pEngine->GetOptions()->GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) && CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes) { pData->opState = filetransfer_mdtm; } else { if (CheckOverwriteFile() != FZ_REPLY_OK) break; } } else pData->opState = filetransfer_size; } SendNextCommand(); } break; default: return SetFileExistsAction(pFileExistsNotification); } } break; case reqId_interactiveLogin: { if (!m_pCurOpData || m_pCurOpData->opId != cmd_connect) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID()); return false; } CFtpLogonOpData* pData = static_cast<CFtpLogonOpData*>(m_pCurOpData); CInteractiveLoginNotification *pInteractiveLoginNotification = reinterpret_cast<CInteractiveLoginNotification *>(pNotification); if (!pInteractiveLoginNotification->passwordSet) { ResetOperation(FZ_REPLY_CANCELED); return false; } m_pCurrentServer->SetUser(m_pCurrentServer->GetUser(), pInteractiveLoginNotification->server.GetPass()); pData->gotPassword = true; SendNextCommand(); } break; case reqId_certificate: { if (!m_pTlsSocket || m_pTlsSocket->GetState() != CTlsSocket::verifycert) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID()); return false; } CCertificateNotification* pCertificateNotification = reinterpret_cast<CCertificateNotification *>(pNotification); m_pTlsSocket->TrustCurrentCert(pCertificateNotification->m_trusted); if (!pCertificateNotification->m_trusted) { DoClose(FZ_REPLY_CRITICALERROR); return false; } if (m_pCurOpData && m_pCurOpData->opId == cmd_connect && m_pCurOpData->opState == LOGON_AUTH_WAIT) { m_pCurOpData->opState = LOGON_LOGON; } SendNextCommand(); } break; default: LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown request %d"), pNotification->GetRequestID()); ResetOperation(FZ_REPLY_INTERNALERROR); return false; } return true; } class CRawCommandOpData : public COpData { public: CRawCommandOpData(const wxString& command) : COpData(cmd_raw) { m_command = command; } wxString m_command; }; int CFtpControlSocket::RawCommand(const wxString& command) { wxASSERT(command != _T("")); m_pCurOpData = new CRawCommandOpData(command); return SendNextCommand(); } int CFtpControlSocket::RawCommandSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RawCommandSend")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CDirectoryCache cache; cache.InvalidateServer(*m_pCurrentServer); m_CurrentPath.Clear(); CRawCommandOpData *pData = static_cast<CRawCommandOpData *>(m_pCurOpData); if (!Send(pData->m_command)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::RawCommandParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RawCommandParseResponse")); int code = GetReplyCode(); if (code == 2 || code == 3) { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } else { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } int CFtpControlSocket::Delete(const CServerPath& path, const std::list<wxString>& files) { wxASSERT(!m_pCurOpData); CFtpDeleteOpData *pData = new CFtpDeleteOpData(); m_pCurOpData = pData; pData->path = path; pData->files = files; pData->omitPath = true; int res = ChangeDir(pData->path); if (res != FZ_REPLY_OK) return res; // CFileZillaEnginePrivate should have checked this already wxASSERT(!files.empty()); return SendNextCommand(); } int CFtpControlSocket::DeleteSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::DeleteSubcommandResult()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData); if (prevResult != FZ_REPLY_OK) pData->omitPath = false; return SendNextCommand(); } int CFtpControlSocket::DeleteSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::DeleteSend()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData); const wxString& file = pData->files.front(); if (file == _T("")) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty filename")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } wxString filename = pData->path.FormatFilename(file, pData->omitPath); if (filename == _T("")) { LogMessage(::Error, _("Filename cannot be constructed for directory %s and filename %s"), pData->path.GetPath().c_str(), file.c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (!pData->m_time.IsValid()) pData->m_time = wxDateTime::UNow(); CDirectoryCache cache; cache.InvalidateFile(*m_pCurrentServer, pData->path, file); if (!Send(_T("DELE ") + filename)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::DeleteParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::DeleteParseResponse()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData); int code = GetReplyCode(); if (code != 2 && code != 3) pData->m_deleteFailed = true; else { const wxString& file = pData->files.front(); CDirectoryCache cache; cache.RemoveFile(*m_pCurrentServer, pData->path, file); wxDateTime now = wxDateTime::UNow(); if (now.IsValid() && pData->m_time.IsValid() && (now - pData->m_time).GetSeconds() >= 1) { m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false); pData->m_time = now; pData->m_needSendListing = false; } else pData->m_needSendListing = true; } pData->files.pop_front(); if (!pData->files.empty()) return SendNextCommand(); return ResetOperation(pData->m_deleteFailed ? FZ_REPLY_ERROR : FZ_REPLY_OK); } class CFtpRemoveDirOpData : public COpData { public: CFtpRemoveDirOpData() : COpData(cmd_removedir) { } virtual ~CFtpRemoveDirOpData() { } CServerPath path; CServerPath fullPath; wxString subDir; bool omitPath; }; int CFtpControlSocket::RemoveDir(const CServerPath& path, const wxString& subDir) { wxASSERT(!m_pCurOpData); CFtpRemoveDirOpData *pData = new CFtpRemoveDirOpData(); m_pCurOpData = pData; pData->path = path; pData->subDir = subDir; pData->omitPath = true; pData->fullPath = path; if (!pData->fullPath.AddSegment(subDir)) { LogMessage(::Error, _("Path cannot be constructed for directory %s and subdir %s"), path.GetPath().c_str(), subDir.c_str()); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } int res = ChangeDir(pData->path); if (res != FZ_REPLY_OK) return res; return SendNextCommand(); } int CFtpControlSocket::RemoveDirSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RemoveDirSubcommandResult()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData); if (prevResult != FZ_REPLY_OK) pData->omitPath = false; else pData->path = m_CurrentPath; return SendNextCommand(); } int CFtpControlSocket::RemoveDirSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RemoveDirSend()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData); CDirectoryCache cache; cache.InvalidateFile(*m_pCurrentServer, pData->path, pData->subDir); CServerPath path(CPathCache::Lookup(*m_pCurrentServer, pData->path, pData->subDir)); if (path.IsEmpty()) { path = pData->path; path.AddSegment(pData->subDir); } m_pEngine->InvalidateCurrentWorkingDirs(path); CPathCache::InvalidatePath(*m_pCurrentServer, pData->path, pData->subDir); if (pData->omitPath) { if (!Send(_T("RMD ") + pData->subDir)) return FZ_REPLY_ERROR; } else if (!Send(_T("RMD ") + pData->fullPath.GetPath())) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::RemoveDirParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RemoveDirParseResponse()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData); int code = GetReplyCode(); if (code != 2 && code != 3) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CDirectoryCache cache; cache.RemoveDir(*m_pCurrentServer, pData->path, pData->subDir, CPathCache::Lookup(*m_pCurrentServer, pData->path, pData->subDir)); m_pEngine->SendDirectoryListingNotification(pData->path, false, true, false); return ResetOperation(FZ_REPLY_OK); } enum mkdStates { mkd_init = 0, mkd_findparent, mkd_mkdsub, mkd_cwdsub, mkd_tryfull }; int CFtpControlSocket::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->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)) { delete pData; return FZ_REPLY_OK; } if (m_CurrentPath.IsParentOf(path, false)) pData->commonParent = m_CurrentPath; else pData->commonParent = path.GetCommonParent(m_CurrentPath); } if (!path.HasParent()) pData->opState = mkd_tryfull; else { pData->currentPath = path.GetParent(); pData->segments.push_back(path.GetLastSegment()); if (pData->currentPath == m_CurrentPath) pData->opState = mkd_mkdsub; else pData->opState = mkd_findparent; } pData->pNextOpData = m_pCurOpData; m_pCurOpData = pData; return SendNextCommand(); } int CFtpControlSocket::MkdirParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::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); int code = GetReplyCode(); bool error = false; switch (pData->opState) { case mkd_findparent: if (code == 2 || code == 3) { m_CurrentPath = pData->currentPath; pData->opState = mkd_mkdsub; } else if (pData->currentPath == pData->commonParent) pData->opState = mkd_tryfull; else if (pData->currentPath.HasParent()) { const CServerPath& parent = pData->currentPath.GetParent(); pData->segments.push_front(pData->currentPath.GetLastSegment()); pData->currentPath = parent; } else pData->opState = mkd_tryfull; break; case mkd_mkdsub: if (code != 2 && code != 3) { // Don't fall back to using the full path if the error message // is "already exists". // Case 1: Full response a known "already exists" message. // Case 2: Substrng of response contains "already exists". pData->path may not // contain this substring as the path might be returned in the reply. // Case 3: Substrng of response contains "file exists". pData->path may not // contain this substring as the path might be returned in the reply. const wxString response = m_Response.Mid(4).Lower(); const wxString path = pData->path.GetPath().Lower(); if (response != _T("directory already exists") && (path.Find(_T("already exists")) != -1 || response.Find(_T("already exists")) == -1) && (path.Find(_T("file exists")) != -1 || response.Find(_T("file exists")) == -1) ) { pData->opState = mkd_tryfull; break; } } { 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; } break; case mkd_cwdsub: if (code == 2 || code == 3) { m_CurrentPath = pData->currentPath; pData->opState = mkd_mkdsub; } else pData->opState = mkd_tryfull; break; case mkd_tryfull: if (code != 2 && code != 3) error = true; else { ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } break; 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 SendNextCommand(); } int CFtpControlSocket::MkdirSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::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); if (!pData->holdsLock) { if (!TryLockCache(lock_mkdir, pData->path)) return FZ_REPLY_WOULDBLOCK; } bool res; switch (pData->opState) { case mkd_findparent: case mkd_cwdsub: m_CurrentPath.Clear(); res = Send(_T("CWD ") + pData->currentPath.GetPath()); break; case mkd_mkdsub: res = Send(_T("MKD ") + pData->segments.front()); break; case mkd_tryfull: res = Send(_T("MKD ") + 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; } class CFtpRenameOpData : public COpData { public: CFtpRenameOpData(const CRenameCommand& command) : COpData(cmd_rename), m_cmd(command) { m_useAbsolute = false; } virtual ~CFtpRenameOpData() { } CRenameCommand m_cmd; bool m_useAbsolute; }; enum renameStates { rename_init = 0, rename_rnfrom, rename_rnto }; int CFtpControlSocket::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()); CFtpRenameOpData *pData = new CFtpRenameOpData(command); pData->opState = rename_rnfrom; m_pCurOpData = pData; int res = ChangeDir(command.GetFromPath()); if (res != FZ_REPLY_OK) return res; return SendNextCommand(); } int CFtpControlSocket::RenameParseResponse() { CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } int code = GetReplyCode(); if (code != 2 && code != 3) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } if (pData->opState == rename_rnfrom) pData->opState = rename_rnto; else { CDirectoryCache cache; const CServerPath& fromPath = pData->m_cmd.GetFromPath(); const CServerPath& toPath = pData->m_cmd.GetToPath(); 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; } return SendNextCommand(); } int CFtpControlSocket::RenameSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RenameSubcommandResult()")); CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(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; return SendNextCommand(); } int CFtpControlSocket::RenameSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::RenameSend()")); CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } bool res; switch (pData->opState) { case rename_rnfrom: res = Send(_T("RNFR ") + pData->m_cmd.GetFromPath().FormatFilename(pData->m_cmd.GetFromFile(), !pData->m_useAbsolute)); break; case rename_rnto: { 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()); CServerPath path(CPathCache::Lookup(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile())); if (path.IsEmpty()) { path = pData->m_cmd.GetFromPath(); path.AddSegment(pData->m_cmd.GetFromFile()); } m_pEngine->InvalidateCurrentWorkingDirs(path); CPathCache::InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile()); CPathCache::InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile()); res = Send(_T("RNTO ") + pData->m_cmd.GetToPath().FormatFilename(pData->m_cmd.GetToFile(), !pData->m_useAbsolute && pData->m_cmd.GetFromPath() == pData->m_cmd.GetToPath())); 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 CFtpChmodOpData : public COpData { public: CFtpChmodOpData(const CChmodCommand& command) : COpData(cmd_chmod), m_cmd(command) { m_useAbsolute = false; } virtual ~CFtpChmodOpData() { } CChmodCommand m_cmd; bool m_useAbsolute; }; enum chmodStates { chmod_init = 0, chmod_chmod }; int CFtpControlSocket::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()); CFtpChmodOpData *pData = new CFtpChmodOpData(command); pData->opState = chmod_chmod; m_pCurOpData = pData; int res = ChangeDir(command.GetPath()); if (res != FZ_REPLY_OK) return res; return SendNextCommand(); } int CFtpControlSocket::ChmodParseResponse() { CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } int code = GetReplyCode(); if (code != 2 && code != 3) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } CDirectoryCache cache; cache.UpdateFile(*m_pCurrentServer, pData->m_cmd.GetPath(), pData->m_cmd.GetFile(), false, CDirectoryCache::unknown); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } int CFtpControlSocket::ChmodSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ChmodSubcommandResult()")); CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(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; return SendNextCommand(); } int CFtpControlSocket::ChmodSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ChmodSend()")); CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData); if (!pData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("m_pCurOpData empty")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } bool res; switch (pData->opState) { case chmod_chmod: res = Send(_T("SITE CHMOD ") + pData->m_cmd.GetPermission() + _T(" ") + pData->m_cmd.GetPath().FormatFilename(pData->m_cmd.GetFile(), !pData->m_useAbsolute)); 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; } bool CFtpControlSocket::IsMisleadingListResponse() const { // Some servers are broken. Instead of an empty listing, some MVS servers // for example they return "550 no members found" // Other servers return "550 No files found." if (!m_Response.CmpNoCase(_T("550 No members found."))) return true; if (!m_Response.CmpNoCase(_T("550 No data sets found."))) return true; if (!m_Response.CmpNoCase(_T("550 No files found."))) return true; return false; } bool CFtpControlSocket::ParseEpsvResponse(CRawTransferOpData* pData) { int pos = m_Response.Find(_T("(|||")); if (pos == -1) return false; int pos2 = m_Response.Mid(pos + 4).Find(_T("|)")); if (pos2 <= 0) return false; wxString number = m_Response.Mid(pos + 4, pos2); unsigned long port; if (!number.ToULong(&port)) return false; if (port <= 0 || port > 65535) return false; pData->port = port; pData->host = m_pSocket->GetPeerIP(); return true; } bool CFtpControlSocket::ParsePasvResponse(CRawTransferOpData* pData) { // Validate ip address wxString digit = _T("0*[0-9]{1,3}"); const wxChar* dot = _T(","); wxString exp = _T("( |\\()(") + digit + dot + digit + dot + digit + dot + digit + dot + digit + dot + digit + _T(")( |\\)|$)"); wxRegEx regex; regex.Compile(exp); if (!regex.Matches(m_Response)) return false; pData->host = regex.GetMatch(m_Response, 2); int i = pData->host.Find(',', true); long number; if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number)) return false; pData->port = number; //get ls byte of server socket pData->host = pData->host.Left(i); i = pData->host.Find(',', true); if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number)) return false; pData->port += 256 * number; //add ms byte of server socket pData->host = pData-> host.Left(i); pData->host.Replace(_T(","), _T(".")); if (m_pProxyBackend) { // We do not have any information about the proxy's inner workings return true; } const wxString peerIP = m_pSocket->GetPeerIP(); if (!IsRoutableAddress(pData->host, m_pSocket->GetAddressFamily()) && IsRoutableAddress(peerIP, m_pSocket->GetAddressFamily())) { if (m_pEngine->GetOptions()->GetOptionVal(OPTION_PASVREPLYFALLBACKMODE) != 1 || pData->bTriedActive) { LogMessage(Status, _("Server sent passive reply with unroutable address. Using server address instead.")); LogMessage(Debug_Info, _T(" Reply: %s, peer: %s"), pData->host.c_str(), peerIP.c_str()); pData->host = peerIP; } else { LogMessage(Status, _("Server sent passive reply with unroutable address. Passive mode failed.")); LogMessage(Debug_Info, _T(" Reply: %s, peer: %s"), pData->host.c_str(), peerIP.c_str()); return false; } } else if (m_pEngine->GetOptions()->GetOptionVal(OPTION_PASVREPLYFALLBACKMODE) == 2) { // Always use server address pData->host = peerIP; } return true; } int CFtpControlSocket::GetExternalIPAddress(wxString& address) { // Local IP should work. Only a complete moron would use IPv6 // and NAT at the same time. if (m_pSocket->GetAddressFamily() != CSocket::ipv6) { int mode = m_pEngine->GetOptions()->GetOptionVal(OPTION_EXTERNALIPMODE); if (mode) { if (m_pEngine->GetOptions()->GetOptionVal(OPTION_NOEXTERNALONLOCAL) && !IsRoutableAddress(m_pSocket->GetPeerIP(), m_pSocket->GetAddressFamily())) // Skip next block, use local address goto getLocalIP; } if (mode == 1) { wxString ip = m_pEngine->GetOptions()->GetOption(OPTION_EXTERNALIP); if (ip != _T("")) { address = ip; return FZ_REPLY_OK; } LogMessage(::Debug_Warning, _("No external IP address set, trying default.")); } else if (mode == 2) { if (!m_pIPResolver) { const wxString& localAddress = m_pSocket->GetLocalIP(); if (localAddress != _T("") && localAddress == m_pEngine->GetOptions()->GetOption(OPTION_LASTRESOLVEDIP)) { LogMessage(::Debug_Verbose, _T("Using cached external IP address")); address = localAddress; return FZ_REPLY_OK; } wxString resolverAddress = m_pEngine->GetOptions()->GetOption(OPTION_EXTERNALIPRESOLVER); LogMessage(::Debug_Info, _("Retrieving external IP address from %s"), resolverAddress.c_str()); m_pIPResolver = new CExternalIPResolver(this); m_pIPResolver->GetExternalIP(resolverAddress, CSocket::ipv4); if (!m_pIPResolver->Done()) { LogMessage(::Debug_Verbose, _T("Waiting for resolver thread")); return FZ_REPLY_WOULDBLOCK; } } if (!m_pIPResolver->Successful()) { delete m_pIPResolver; m_pIPResolver = 0; LogMessage(::Debug_Warning, _("Failed to retrieve external ip address, using local address")); } else { LogMessage(::Debug_Info, _T("Got external IP address")); address = m_pIPResolver->GetIP(); m_pEngine->GetOptions()->SetOption(OPTION_LASTRESOLVEDIP, address); delete m_pIPResolver; m_pIPResolver = 0; return FZ_REPLY_OK; } } } getLocalIP: address = m_pSocket->GetLocalIP(); if (address == _T("")) { LogMessage(::Error, _("Failed to retrieve local ip address."), 1); return FZ_REPLY_ERROR; } return FZ_REPLY_OK; } void CFtpControlSocket::OnExternalIPAddress(fzExternalIPResolveEvent& event) { LogMessage(::Debug_Verbose, _T("CFtpControlSocket::OnExternalIPAddress()")); if (!m_pIPResolver) { LogMessage(::Debug_Info, _T("Ignoring event")); return; } SendNextCommand(); } int CFtpControlSocket::Transfer(const wxString& cmd, CFtpTransferOpData* oldData) { wxASSERT(oldData); oldData->tranferCommandSent = false; CRawTransferOpData *pData = new CRawTransferOpData; pData->pNextOpData = m_pCurOpData; m_pCurOpData = pData; pData->cmd = cmd; pData->pOldData = oldData; pData->pOldData->transferEndReason = successful; if (m_pProxyBackend) { // Only passive suported // Theoretically could use reverse proxy ability in SOCKS5, but // it is too fragile to set up with all those broken routers and // firewalls sabotaging connections. Regular active mode is hard // enough already pData->bPasv = true; pData->bTriedActive = true; } else { switch (m_pCurrentServer->GetPasvMode()) { case MODE_PASSIVE: pData->bPasv = true; break; case MODE_ACTIVE: pData->bPasv = false; break; default: pData->bPasv = m_pEngine->GetOptions()->GetOptionVal(OPTION_USEPASV) != 0; break; } } if ((pData->pOldData->binary && m_lastTypeBinary == 1) || (!pData->pOldData->binary && m_lastTypeBinary == 0)) pData->opState = rawtransfer_port_pasv; else pData->opState = rawtransfer_type; return SendNextCommand(); } int CFtpControlSocket::TransferParseResponse() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferParseResponse()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData); if (pData->opState == rawtransfer_init) return FZ_REPLY_ERROR; int code = GetReplyCode(); LogMessage(Debug_Debug, _T(" code = %d"), code); LogMessage(Debug_Debug, _T(" state = %d"), pData->opState); bool error = false; switch (pData->opState) { case rawtransfer_type: if (code != 2 && code != 2) error = true; else { pData->opState = rawtransfer_port_pasv; m_lastTypeBinary = pData->pOldData->binary ? 1 : 0; } break; case rawtransfer_port_pasv: if (code != 2 && code != 3) { if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK)) { error = true; break; } if (pData->bTriedPasv) if (pData->bTriedActive) error = true; else pData->bPasv = false; else pData->bPasv = true; break; } if (pData->bPasv) { bool parsed; if (m_pSocket->GetAddressFamily() == CSocket::ipv6) parsed = ParseEpsvResponse(pData); else parsed = ParsePasvResponse(pData); if (!parsed) { if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK)) { error = true; break; } if (!pData->bTriedActive) pData->bPasv = false; else error = true; break; } } if (pData->pOldData->resumeOffset > 0 || m_sentRestartOffset) pData->opState = rawtransfer_rest; else pData->opState = rawtransfer_transfer; break; case rawtransfer_rest: if (pData->pOldData->resumeOffset == 0) m_sentRestartOffset = false; if (pData->pOldData->resumeOffset != 0 && code != 2 && code != 3) error = true; else pData->opState = rawtransfer_transfer; break; case rawtransfer_transfer: if (code != 1) { if (pData->pOldData->transferEndReason == successful) pData->pOldData->transferEndReason = transfer_command_failure_immediate; error = true; } else pData->opState = rawtransfer_waitfinish; break; case rawtransfer_waittransferpre: if (code != 1) { if (pData->pOldData->transferEndReason == successful) pData->pOldData->transferEndReason = transfer_command_failure_immediate; error = true; } else pData->opState = rawtransfer_waittransfer; break; case rawtransfer_waitfinish: if (code != 2 && code != 3) { if (pData->pOldData->transferEndReason == successful) pData->pOldData->transferEndReason = transfer_command_failure; error = true; } else pData->opState = rawtransfer_waitsocket; break; case rawtransfer_waittransfer: if (code != 2 && code != 3) { if (pData->pOldData->transferEndReason == successful) pData->pOldData->transferEndReason = transfer_command_failure; error = true; } else { if (pData->pOldData->transferEndReason != successful) { error = true; break; } ResetOperation(FZ_REPLY_OK); return FZ_REPLY_OK; } break; case rawtransfer_waitsocket: LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Extra reply received during rawtransfer_waitsocket.")); error = true; break; default: LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("Unknown op state")); error = true; } if (error) { ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } return SendNextCommand(); } int CFtpControlSocket::TransferSend() { LogMessage(Debug_Verbose, _T("CFtpControlSocket::TransferSend()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (!m_pTransferSocket) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pTransferSocket")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData); LogMessage(Debug_Debug, _T(" state = %d"), pData->opState); wxString cmd; switch (pData->opState) { case rawtransfer_type: m_lastTypeBinary = -1; if (pData->pOldData->binary) cmd = _T("TYPE I"); else cmd = _T("TYPE A"); break; case rawtransfer_port_pasv: if (pData->bPasv) { pData->bTriedPasv = true; if (m_pSocket->GetAddressFamily() == CSocket::ipv6) cmd = _T("EPSV"); else cmd = _T("PASV"); } else { wxString address; int res = GetExternalIPAddress(address); if (res == FZ_REPLY_WOULDBLOCK) return res; else if (res == FZ_REPLY_OK) { wxString portArgument = m_pTransferSocket->SetupActiveTransfer(address); if (portArgument != _T("")) { pData->bTriedActive = true; if (m_pSocket->GetAddressFamily() == CSocket::ipv6) cmd = _T("EPRT " + portArgument); else cmd = _T("PORT " + portArgument); break; } } if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK) || pData->bTriedPasv) { LogMessage(::Error, _("Failed to create listening socket for active mode transfer")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } LogMessage(::Debug_Warning, _("Failed to create listening socket for active mode transfer")); pData->bTriedActive = true; pData->bTriedPasv = true; pData->bPasv = true; if (m_pSocket->GetAddressFamily() == CSocket::ipv6) cmd = _T("EPSV"); else cmd = _T("PASV"); } break; case rawtransfer_rest: cmd = _T("REST ") + pData->pOldData->resumeOffset.ToString(); if (pData->pOldData->resumeOffset > 0) m_sentRestartOffset = true; break; case rawtransfer_transfer: if (pData->bPasv) { if (!m_pTransferSocket->SetupPassiveTransfer(pData->host, pData->port)) { LogMessage(::Error, _("Could not establish connection to server")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } } cmd = pData->cmd; pData->pOldData->tranferCommandSent = true; SetTransferStatusStartTime(); m_pTransferSocket->SetActive(); break; case rawtransfer_waitfinish: case rawtransfer_waittransferpre: case rawtransfer_waittransfer: case rawtransfer_waitsocket: break; default: LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("invalid opstate")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } if (cmd != _T("")) if (!Send(cmd)) return FZ_REPLY_ERROR; return FZ_REPLY_WOULDBLOCK; } int CFtpControlSocket::FileTransferTestResumeCapability() { LogMessage(Debug_Verbose, _T("FileTransferTestResumeCapability()")); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("Empty m_pCurOpData")); ResetOperation(FZ_REPLY_INTERNALERROR); return FZ_REPLY_ERROR; } CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData); if (!pData->download) return FZ_REPLY_OK; for (int i = 0; i < 2; i++) { if (pData->localFileSize >= ((wxFileOffset)1 << (i ? 31 : 32))) { switch (CServerCapabilities::GetCapability(*GetCurrentServer(), i ? resume2GBbug : resume4GBbug)) { case yes: if (pData->remoteFileSize == pData->localFileSize) { LogMessage(::Debug_Info, _("Server does not support resume of files > %d GB. End transfer since file sizes match."), i ? 2 : 4); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_CANCELED; } LogMessage(::Error, _("Server does not support resume of files > %d GB."), i ? 2 : 4); ResetOperation(FZ_REPLY_CRITICALERROR); return FZ_REPLY_ERROR; case unknown: if (pData->remoteFileSize < pData->localFileSize) { // Don't perform test break; } if (pData->remoteFileSize == pData->localFileSize) { LogMessage(::Debug_Info, _("Server may not support resume of files > %d GB. End transfer since file sizes match."), i ? 2 : 4); ResetOperation(FZ_REPLY_OK); return FZ_REPLY_CANCELED; } else if (pData->remoteFileSize > pData->localFileSize) { LogMessage(Status, _("Testing resume capabilities of server")); pData->opState = filetransfer_waitresumetest; pData->resumeOffset = pData->remoteFileSize - 1; m_pTransferSocket = new CTransferSocket(m_pEngine, this, resumetest); return Transfer(_T("RETR ") + pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath), pData); } break; case no: break; } } } return FZ_REPLY_OK; } int CFtpControlSocket::Connect(const CServer &server) { if (m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Info, _T("deleting nonzero pData")); delete m_pCurOpData; } CFtpLogonOpData* pData = new CFtpLogonOpData; m_pCurOpData = pData; // Do not use FTP proxy if generic proxy is set int generic_proxy_type = m_pEngine->GetOptions()->GetOptionVal(OPTION_PROXY_TYPE); if ((generic_proxy_type <= CProxySocket::unknown || generic_proxy_type >= CProxySocket::proxytype_count) && (pData->ftp_proxy_type = m_pEngine->GetOptions()->GetOptionVal(OPTION_FTP_PROXY_TYPE)) && !server.GetBypassProxy()) { pData->host = m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_HOST); int pos = pData->host.Find(':'); if (pos != -1) { unsigned long port = 0; if (!pData->host.Mid(pos + 1).ToULong(&port)) port = 0; pData->host = pData->host.Left(pos); pData->port = port; } else pData->port = 21; if (pData->host == _T("") || pData->port < 1 || pData->port > 65535) { LogMessage(::Error, _("Proxy set but proxy host or port invalid")); DoClose(FZ_REPLY_CRITICALERROR); return FZ_REPLY_ERROR; } LogMessage(Status, _("Using proxy %s"), m_pEngine->GetOptions()->GetOption(OPTION_FTP_PROXY_HOST).c_str()); } else { pData->ftp_proxy_type = 0; pData->host = server.GetHost(); pData->port = server.GetPort(); } if (server.GetProtocol() != FTPES) { pData->neededCommands[LOGON_AUTH_TLS] = 0; pData->neededCommands[LOGON_AUTH_SSL] = 0; pData->neededCommands[LOGON_AUTH_WAIT] = 0; if (server.GetProtocol() != FTPS) { pData->neededCommands[LOGON_PBSZ] = 0; pData->neededCommands[LOGON_PROT] = 0; } } if (server.GetPostLoginCommands().empty()) pData->neededCommands[LOGON_CUSTOMCOMMANDS] = 0; if (!GetLoginSequence(server)) return DoClose(FZ_REPLY_INTERNALERROR); return CRealControlSocket::Connect(server); } bool CFtpControlSocket::CheckInclusion(const CDirectoryListing& listing1, const CDirectoryListing& listing2) { // Check if listing2 is contained within listing1 if (listing2.GetCount() > listing1.GetCount()) return false; std::vector<wxString> names1, names2; listing1.GetFilenames(names1); listing2.GetFilenames(names2); std::sort(names1.begin(), names1.end()); std::sort(names2.begin(), names2.end()); std::vector<wxString>::const_iterator iter1, iter2; iter1 = names1.begin(); iter2 = names2.begin(); while (iter2 != names2.begin()) { if (iter1 == names1.end()) return false; if (*iter1 != *iter2) { iter1++; continue; } iter1++; iter2++; } return true; } void CFtpControlSocket::OnIdleTimer(wxTimerEvent& event) { if (event.GetId() != m_idleTimer.GetId()) { event.Skip(); return; } if (m_pCurOpData) return; if (m_pendingReplies || m_repliesToSkip) return; LogMessage(Status, _("Sending keep-alive command")); wxString cmd; int i = GetRandomNumber(0, 2); if (!i) cmd = _T("NOOP"); else if (i == 1) { if (m_lastTypeBinary) cmd = _T("TYPE I"); else cmd = _T("TYPE A"); } else cmd = _T("PWD"); if (!Send(cmd)) return; m_repliesToSkip++; } void CFtpControlSocket::StartKeepaliveTimer() { if (!m_pEngine->GetOptions()->GetOptionVal(OPTION_FTP_SENDKEEPALIVE)) return; if (m_repliesToSkip || m_pendingReplies) return; if (!m_lastCommandCompletionTime.IsValid()) return; wxTimeSpan span = wxDateTime::Now() - m_lastCommandCompletionTime; if (span.GetSeconds() >= (60 * 30)) return; m_idleTimer.Start(30000, true); } int CFtpControlSocket::ParseSubcommandResult(int prevResult) { LogMessage(Debug_Verbose, _T("CFtpControlSocket::ParseSubcommandResult(%d)"), prevResult); if (!m_pCurOpData) { LogMessage(__TFILE__, __LINE__, this, Debug_Warning, _T("ParseSubcommandResult called without active operation")); ResetOperation(FZ_REPLY_ERROR); return FZ_REPLY_ERROR; } switch (m_pCurOpData->opId) { case cmd_cwd: return ChangeDirSubcommandResult(prevResult); case cmd_list: return ListSubcommandResult(prevResult); case cmd_transfer: return FileTransferSubcommandResult(prevResult); case cmd_delete: return DeleteSubcommandResult(prevResult); case cmd_removedir: return RemoveDirSubcommandResult(prevResult); case cmd_rename: return RenameSubcommandResult(prevResult); case cmd_chmod: return ChmodSubcommandResult(prevResult); default: LogMessage(__TFILE__, __LINE__, this, ::Debug_Warning, _T("Unknown opID (%d) in ParseSubcommandResult"), m_pCurOpData->opId); ResetOperation(FZ_REPLY_INTERNALERROR); break; } return FZ_REPLY_ERROR; }