source: filezilla/trunk/fuentes/src/engine/ftpcontrolsocket.cpp @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 2 years ago

Update new version: 3.15.02

File size: 124.6 KB
Line 
1#include <filezilla.h>
2
3#include "directorycache.h"
4#include "directorylistingparser.h"
5#include "engineprivate.h"
6#include "externalipresolver.h"
7#include "ftpcontrolsocket.h"
8#include "iothread.h"
9#include "pathcache.h"
10#include "servercapabilities.h"
11#include "tlssocket.h"
12#include "transfersocket.h"
13#include "proxy.h"
14
15#include <libfilezilla/file.hpp>
16#include <libfilezilla/iputils.hpp>
17#include <libfilezilla/local_filesys.hpp>
18#include <libfilezilla/util.hpp>
19
20#include <wx/filename.h>
21#include <wx/log.h>
22#include <wx/tokenzr.h>
23
24#include <algorithm>
25
26#define LOGON_WELCOME   0
27#define LOGON_AUTH_TLS  1
28#define LOGON_AUTH_SSL  2
29#define LOGON_AUTH_WAIT 3
30#define LOGON_LOGON             4
31#define LOGON_SYST              5
32#define LOGON_FEAT              6
33#define LOGON_CLNT              7
34#define LOGON_OPTSUTF8  8
35#define LOGON_PBSZ              9
36#define LOGON_PROT              10
37#define LOGON_OPTSMLST  11
38#define LOGON_CUSTOMCOMMANDS 12
39#define LOGON_DONE              13
40
41CRawTransferOpData::CRawTransferOpData()
42        : COpData(Command::rawtransfer)
43        , pOldData()
44        , bPasv(true)
45        , bTriedPasv()
46        , bTriedActive()
47        , port()
48{
49}
50
51CFtpFileTransferOpData::CFtpFileTransferOpData(bool is_download, const wxString& local_file, const wxString& remote_file, const CServerPath& remote_path)
52        : CFileTransferOpData(is_download, local_file, remote_file, remote_path)
53{
54}
55
56CFtpFileTransferOpData::~CFtpFileTransferOpData()
57{
58        if (pIOThread) {
59                CIOThread *pThread = pIOThread;
60                pIOThread = 0;
61                pThread->Destroy();
62                delete pThread;
63        }
64}
65
66enum filetransferStates
67{
68        filetransfer_init = 0,
69        filetransfer_waitcwd,
70        filetransfer_waitlist,
71        filetransfer_size,
72        filetransfer_mdtm,
73        filetransfer_resumetest,
74        filetransfer_transfer,
75        filetransfer_waittransfer,
76        filetransfer_waitresumetest,
77        filetransfer_mfmt
78};
79
80enum rawtransferStates
81{
82        rawtransfer_init = 0,
83        rawtransfer_type,
84        rawtransfer_port_pasv,
85        rawtransfer_rest,
86        rawtransfer_transfer,
87        rawtransfer_waitfinish,
88        rawtransfer_waittransferpre,
89        rawtransfer_waittransfer,
90        rawtransfer_waitsocket
91};
92
93enum class loginCommandType
94{
95        user,
96        pass,
97        account,
98        other
99};
100
101struct t_loginCommand
102{
103        bool optional;
104        bool hide_arguments;
105        loginCommandType type;
106
107        wxString command;
108};
109
110class CFtpLogonOpData : public CConnectOpData
111{
112public:
113        CFtpLogonOpData()
114        {
115                waitChallenge = false;
116                gotPassword = false;
117                waitForAsyncRequest = false;
118                gotFirstWelcomeLine = false;
119                ftp_proxy_type = 0;
120
121                customCommandIndex = 0;
122
123                for (int i = 0; i < LOGON_DONE; ++i)
124                        neededCommands[i] = 1;
125        }
126
127        virtual ~CFtpLogonOpData()
128        {
129        }
130
131        wxString challenge; // Used for interactive logons
132        bool waitChallenge;
133        bool waitForAsyncRequest;
134        bool gotPassword;
135        bool gotFirstWelcomeLine;
136
137        unsigned int customCommandIndex;
138
139        int neededCommands[LOGON_DONE];
140
141        std::deque<t_loginCommand> loginSequence;
142
143        int ftp_proxy_type;
144};
145
146class CFtpDeleteOpData final : public COpData
147{
148public:
149        CFtpDeleteOpData()
150                : COpData(Command::del)
151        {
152        }
153
154        virtual ~CFtpDeleteOpData() {}
155
156        CServerPath path;
157        std::deque<wxString> files;
158        bool omitPath{};
159
160        // Set to fz::datetime::Now initially and after
161        // sending an updated listing to the UI.
162        fz::datetime m_time;
163
164        bool m_needSendListing{};
165
166        // Set to true if deletion of at least one file failed
167        bool m_deleteFailed{};
168};
169
170CFtpControlSocket::CFtpControlSocket(CFileZillaEnginePrivate & engine)
171        : CRealControlSocket(engine)
172{
173        m_pIPResolver = 0;
174        m_pTransferSocket = 0;
175        m_sentRestartOffset = false;
176        m_bufferLen = 0;
177        m_repliesToSkip = 0;
178        m_pendingReplies = 1;
179        m_pTlsSocket = 0;
180        m_protectDataChannel = false;
181        m_lastTypeBinary = -1;
182
183        // Enable TCP_NODELAY, speeds things up a bit.
184        // Enable SO_KEEPALIVE, lots of clueless users have broken routers and
185        // firewalls which terminate the control connection on long transfers.
186        m_pSocket->SetFlags(CSocket::flag_nodelay | CSocket::flag_keepalive);
187
188        // The GUI and file operations can easily block our thread. But the socket has an
189        // internal thread. Register read callback to get timely update to rtt.
190        m_pSocket->SetSynchronousReadCallback(&m_rtt);
191}
192
193CFtpControlSocket::~CFtpControlSocket()
194{
195        remove_handler();
196        m_pSocket->SetSynchronousReadCallback(0);
197
198        DoClose();
199}
200
201void CFtpControlSocket::OnReceive()
202{
203        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::OnReceive()"));
204
205        for (;;) {
206                int error;
207                int read = m_pBackend->Read(m_receiveBuffer + m_bufferLen, RECVBUFFERSIZE - m_bufferLen, error);
208
209                if (read < 0) {
210                        if (error != EAGAIN) {
211                                LogMessage(MessageType::Error, _("Could not read from socket: %s"), CSocket::GetErrorDescription(error));
212                                if (GetCurrentCommandId() != Command::connect)
213                                        LogMessage(MessageType::Error, _("Disconnected from server"));
214                                DoClose();
215                        }
216                        return;
217                }
218
219                if (!read) {
220                        LogMessage(MessageType::Error, _("Connection closed by server"));
221                        DoClose();
222                        return;
223                }
224
225                SetActive(CFileZillaEngine::recv);
226
227                char* start = m_receiveBuffer;
228                m_bufferLen += read;
229
230                for (int i = start - m_receiveBuffer; i < m_bufferLen; ++i) {
231                        char& p = m_receiveBuffer[i];
232                        if (p == '\r' ||
233                                p == '\n' ||
234                                p == 0)
235                        {
236                                int len = i - (start - m_receiveBuffer);
237                                if (!len)
238                                {
239                                        ++start;
240                                        continue;
241                                }
242
243                                p = 0;
244                                wxString line = ConvToLocal(start, i + 1 - (start - m_receiveBuffer));
245                                start = m_receiveBuffer + i + 1;
246
247                                ParseLine(line);
248
249                                // Abort if connection got closed
250                                if (!m_pCurrentServer)
251                                        return;
252                        }
253                }
254                memmove(m_receiveBuffer, start, m_bufferLen - (start - m_receiveBuffer));
255                m_bufferLen -= (start -m_receiveBuffer);
256                if (m_bufferLen > MAXLINELEN)
257                        m_bufferLen = MAXLINELEN;
258        }
259}
260
261namespace {
262bool HasFeature(wxString const& line, wxString const& feature)
263{
264        return line == feature || line.StartsWith(feature + _T(" "));
265}
266}
267
268void CFtpControlSocket::ParseFeat(wxString line)
269{
270        line.Trim(false);
271        wxString up = line.Upper();
272
273        if (HasFeature(up, _T("UTF8")))
274                CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, yes);
275        else if (HasFeature(up, _T("CLNT")))
276                CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, yes);
277        else if (HasFeature(up, _T("MLSD"))) {
278                wxString facts;
279                // FEAT output for MLST overrides MLSD
280                if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command, &facts) != yes || facts.empty()) {
281                        facts = line.Mid(5);
282                }
283                CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes, facts);
284
285                // MLST/MLSD specs require use of UTC
286                CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no);
287        }
288        else if (HasFeature(up, _T("MLST"))) {
289                wxString facts = line.Mid(5);
290                // FEAT output for MLST overrides MLSD
291                if (facts.empty()) {
292                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command, &facts) != yes) {
293                                facts.clear();
294                        }
295                }
296                CServerCapabilities::SetCapability(*m_pCurrentServer, mlsd_command, yes, facts);
297
298                // MLST/MLSD specs require use of UTC
299                CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no);
300        }
301        else if (HasFeature(up, _T("MODE Z")))
302                CServerCapabilities::SetCapability(*m_pCurrentServer, mode_z_support, yes);
303        else if (HasFeature(up, _T("MFMT")))
304                CServerCapabilities::SetCapability(*m_pCurrentServer, mfmt_command, yes);
305        else if (HasFeature(up, _T("MDTM")))
306                CServerCapabilities::SetCapability(*m_pCurrentServer, mdtm_command, yes);
307        else if (HasFeature(up, _T("SIZE")))
308                CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes);
309        else if (HasFeature(up, _T("TVFS")))
310                CServerCapabilities::SetCapability(*m_pCurrentServer, tvfs_support, yes);
311        else if (HasFeature(up, _T("REST STREAM")))
312                CServerCapabilities::SetCapability(*m_pCurrentServer, rest_stream, yes);
313        else if (HasFeature(up, _T("EPSV")))
314                CServerCapabilities::SetCapability(*m_pCurrentServer, epsv_command, yes);
315}
316
317void CFtpControlSocket::ParseLine(wxString line)
318{
319        m_rtt.Stop();
320        LogMessageRaw(MessageType::Response, line);
321        SetAlive();
322
323        if (m_pCurOpData && m_pCurOpData->opId == Command::connect) {
324                CFtpLogonOpData* pData = static_cast<CFtpLogonOpData *>(m_pCurOpData);
325                if (pData->waitChallenge) {
326                        wxString& challenge = pData->challenge;
327                        if (!challenge.empty())
328#ifdef __WXMSW__
329                                challenge += _T("\r\n");
330#else
331                                challenge += _T("\n");
332#endif
333                        challenge += line;
334                }
335                else if (pData->opState == LOGON_FEAT) {
336                        ParseFeat(line);
337                }
338                else if (pData->opState == LOGON_WELCOME) {
339                        if (!pData->gotFirstWelcomeLine) {
340                                if (line.Upper().Left(3) == _T("SSH")) {
341                                        LogMessage(MessageType::Error, _("Cannot establish FTP connection to an SFTP server. Please select proper protocol."));
342                                        DoClose(FZ_REPLY_CRITICALERROR);
343                                        return;
344                                }
345                                pData->gotFirstWelcomeLine = true;
346                        }
347                }
348        }
349        //Check for multi-line responses
350        if (line.Len() > 3) {
351                if (!m_MultilineResponseCode.empty()) {
352                        if (line.Left(4) == m_MultilineResponseCode) {
353                                // end of multi-line found
354                                m_MultilineResponseCode.clear();
355                                m_Response = line;
356                                ParseResponse();
357                                m_Response = _T("");
358                                m_MultilineResponseLines.clear();
359                        }
360                        else
361                                m_MultilineResponseLines.push_back(line);
362                }
363                // start of new multi-line
364                else if (line.GetChar(3) == '-') {
365                        // DDD<SP> is the end of a multi-line response
366                        m_MultilineResponseCode = line.Left(3) + _T(" ");
367                        m_MultilineResponseLines.push_back(line);
368                }
369                else {
370                        m_Response = line;
371                        ParseResponse();
372                        m_Response = _T("");
373                }
374        }
375}
376
377void CFtpControlSocket::OnConnect()
378{
379        m_lastTypeBinary = -1;
380
381        SetAlive();
382
383        if (m_pCurrentServer->GetProtocol() == FTPS) {
384                if (!m_pTlsSocket) {
385                        LogMessage(MessageType::Status, _("Connection established, initializing TLS..."));
386
387                        wxASSERT(!m_pTlsSocket);
388                        delete m_pBackend;
389                        m_pTlsSocket = new CTlsSocket(this, *m_pSocket, this);
390                        m_pBackend = m_pTlsSocket;
391
392                        if (!m_pTlsSocket->Init()) {
393                                LogMessage(MessageType::Error, _("Failed to initialize TLS."));
394                                DoClose();
395                                return;
396                        }
397
398                        int res = m_pTlsSocket->Handshake();
399                        if (res == FZ_REPLY_ERROR)
400                                DoClose();
401
402                        return;
403                }
404                else
405                        LogMessage(MessageType::Status, _("TLS connection established, waiting for welcome message..."));
406        }
407        else if ((m_pCurrentServer->GetProtocol() == FTPES || m_pCurrentServer->GetProtocol() == FTP) && m_pTlsSocket) {
408                LogMessage(MessageType::Status, _("TLS connection established."));
409                SendNextCommand();
410                return;
411        }
412        else
413                LogMessage(MessageType::Status, _("Connection established, waiting for welcome message..."));
414        m_pendingReplies = 1;
415        m_repliesToSkip = 0;
416        Logon();
417}
418
419void CFtpControlSocket::ParseResponse()
420{
421        if( m_Response.empty() ) {
422                LogMessage(MessageType::Debug_Warning, _T("No reply in ParseResponse"));
423                return;
424        }
425
426        if (m_Response[0] != '1') {
427                if (m_pendingReplies > 0)
428                        m_pendingReplies--;
429                else {
430                        LogMessage(MessageType::Debug_Warning, _T("Unexpected reply, no reply was pending."));
431                        return;
432                }
433        }
434
435        if (m_repliesToSkip)
436        {
437                LogMessage(MessageType::Debug_Info, _T("Skipping reply after cancelled operation or keepalive command."));
438                if (m_Response[0] != '1')
439                        m_repliesToSkip--;
440
441                if (!m_repliesToSkip)
442                {
443                        SetWait(false);
444                        if (!m_pCurOpData)
445                                StartKeepaliveTimer();
446                        else if (!m_pendingReplies)
447                                SendNextCommand();
448                }
449
450                return;
451        }
452
453        Command commandId = GetCurrentCommandId();
454        switch (commandId)
455        {
456        case Command::connect:
457                LogonParseResponse();
458                break;
459        case Command::list:
460                ListParseResponse();
461                break;
462        case Command::cwd:
463                ChangeDirParseResponse();
464                break;
465        case Command::transfer:
466                FileTransferParseResponse();
467                break;
468        case Command::raw:
469                RawCommandParseResponse();
470                break;
471        case Command::del:
472                DeleteParseResponse();
473                break;
474        case Command::removedir:
475                RemoveDirParseResponse();
476                break;
477        case Command::mkdir:
478                MkdirParseResponse();
479                break;
480        case Command::rename:
481                RenameParseResponse();
482                break;
483        case Command::chmod:
484                ChmodParseResponse();
485                break;
486        case Command::rawtransfer:
487                TransferParseResponse();
488                break;
489        case Command::none:
490                LogMessage(MessageType::Debug_Verbose, _T("Out-of-order reply, ignoring."));
491                break;
492        default:
493                LogMessage(MessageType::Debug_Warning, _T("No action for parsing replies to command %d"), (int)commandId);
494                ResetOperation(FZ_REPLY_INTERNALERROR);
495                break;
496        }
497}
498
499bool CFtpControlSocket::GetLoginSequence(const CServer& server)
500{
501        CFtpLogonOpData *pData = static_cast<CFtpLogonOpData *>(m_pCurOpData);
502        pData->loginSequence.clear();
503
504        if (!pData->ftp_proxy_type) {
505                // User
506                t_loginCommand cmd = {false, false, loginCommandType::user, _T("")};
507                pData->loginSequence.push_back(cmd);
508
509                // Password
510                cmd.optional = true;
511                cmd.hide_arguments = true;
512                cmd.type = loginCommandType::pass;
513                pData->loginSequence.push_back(cmd);
514
515                // Optional account
516                if (!server.GetAccount().empty()) {
517                        cmd.hide_arguments = false;
518                        cmd.type = loginCommandType::account;
519                        pData->loginSequence.push_back(cmd);
520                }
521        }
522        else if (pData->ftp_proxy_type == 1) {
523                const wxString& proxyUser = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_USER);
524                if (!proxyUser.empty()) {
525                        // Proxy logon (if credendials are set)
526                        t_loginCommand cmd = {false, false, loginCommandType::other, _T("USER ") + proxyUser};
527                        pData->loginSequence.push_back(cmd);
528                        cmd.optional = true;
529                        cmd.hide_arguments = true;
530                        cmd.command = _T("PASS ") + engine_.GetOptions().GetOption(OPTION_FTP_PROXY_PASS);
531                        pData->loginSequence.push_back(cmd);
532                }
533                // User@host
534                t_loginCommand cmd = {false, false, loginCommandType::user, wxString::Format(_T("USER %s@%s"), server.GetUser(), server.FormatHost())};
535                pData->loginSequence.push_back(cmd);
536
537                // Password
538                cmd.optional = true;
539                cmd.hide_arguments = true;
540                cmd.type = loginCommandType::pass;
541                cmd.command = _T("");
542                pData->loginSequence.push_back(cmd);
543
544                // Optional account
545                if (!server.GetAccount().empty()) {
546                        cmd.hide_arguments = false;
547                        cmd.type = loginCommandType::account;
548                        pData->loginSequence.push_back(cmd);
549                }
550        }
551        else if (pData->ftp_proxy_type == 2 || pData->ftp_proxy_type == 3) {
552                const wxString& proxyUser = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_USER);
553                if (!proxyUser.empty()) {
554                        // Proxy logon (if credendials are set)
555                        t_loginCommand cmd = {false, false, loginCommandType::other, _T("USER ") + proxyUser};
556                        pData->loginSequence.push_back(cmd);
557                        cmd.optional = true;
558                        cmd.hide_arguments = true;
559                        cmd.command = _T("PASS ") + engine_.GetOptions().GetOption(OPTION_FTP_PROXY_PASS);
560                        pData->loginSequence.push_back(cmd);
561                }
562
563                // Site or Open
564                t_loginCommand cmd = {false, false, loginCommandType::user, _T("")};
565                if (pData->ftp_proxy_type == 2)
566                        cmd.command = _T("SITE ") + server.FormatHost();
567                else
568                        cmd.command = _T("OPEN ") + server.FormatHost();
569                pData->loginSequence.push_back(cmd);
570
571                // User
572                cmd.type = loginCommandType::user;
573                cmd.command = _T("");
574                pData->loginSequence.push_back(cmd);
575
576                // Password
577                cmd.optional = true;
578                cmd.hide_arguments = true;
579                cmd.type = loginCommandType::pass;
580                pData->loginSequence.push_back(cmd);
581
582                // Optional account
583                if (!server.GetAccount().empty()) {
584                        cmd.hide_arguments = false;
585                        cmd.type = loginCommandType::account;
586                        pData->loginSequence.push_back(cmd);
587                }
588        }
589        else if (pData->ftp_proxy_type == 4) {
590                wxString proxyUser = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_USER);
591                wxString proxyPass = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_PASS);
592                wxString host = server.FormatHost();
593                wxString user = server.GetUser();
594                wxString account = server.GetAccount();
595                proxyUser.Replace(_T("%"), _T("%%"));
596                proxyPass.Replace(_T("%"), _T("%%"));
597                host.Replace(_T("%"), _T("%%"));
598                user.Replace(_T("%"), _T("%%"));
599                account.Replace(_T("%"), _T("%%"));
600
601                wxString loginSequence = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_CUSTOMLOGINSEQUENCE);
602                wxStringTokenizer tokens(loginSequence, _T("\n"), wxTOKEN_STRTOK);
603
604                while (tokens.HasMoreTokens()) {
605                        wxString token = tokens.GetNextToken();
606                        token.Trim(true);
607                        token.Trim(false);
608
609                        if (token.empty())
610                                continue;
611
612                        bool isHost = false;
613                        bool isUser = false;
614                        bool password = false;
615                        bool isProxyUser = false;
616                        bool isProxyPass = false;
617                        if (token.Find(_T("%h")) != -1)
618                                isHost = true;
619                        if (token.Find(_T("%u")) != -1)
620                                isUser = true;
621                        if (token.Find(_T("%p")) != -1)
622                                password = true;
623                        if (token.Find(_T("%s")) != -1)
624                                isProxyUser = true;
625                        if (token.Find(_T("%w")) != -1)
626                                isProxyPass = true;
627
628                        // Skip account if empty
629                        bool isAccount = false;
630                        if (token.Find(_T("%a")) != -1) {
631                                if (account.empty())
632                                        continue;
633                                else
634                                        isAccount = true;
635                        }
636
637                        if (isProxyUser && !isHost && !isUser && proxyUser.empty())
638                                continue;
639                        if (isProxyPass && !isHost && !isUser && proxyUser.empty())
640                                continue;
641
642                        token.Replace(_T("%s"), proxyUser);
643                        token.Replace(_T("%w"), proxyPass);
644                        token.Replace(_T("%h"), host);
645                        token.Replace(_T("%u"), user);
646                        token.Replace(_T("%a"), account);
647                        // Pass will be replaced before sending to cope with interactve login
648
649                        if (!password)
650                                token.Replace(_T("%%"), _T("%"));
651
652                        t_loginCommand cmd;
653                        if (password || isProxyPass)
654                                cmd.hide_arguments = true;
655                        else
656                                cmd.hide_arguments = false;
657
658                        if (isUser && !password && !isAccount) {
659                                cmd.optional = false;
660                                cmd.type = loginCommandType::user;
661                        }
662                        else if (password && !isUser && !isAccount) {
663                                cmd.optional = true;
664                                cmd.type = loginCommandType::pass;
665                        }
666                        else if (isAccount && !isUser && !password) {
667                                cmd.optional = true;
668                                cmd.type = loginCommandType::account;
669                        }
670                        else {
671                                cmd.optional = false;
672                                cmd.type = loginCommandType::other;
673                        }
674
675                        cmd.command = token;
676
677                        pData->loginSequence.push_back(cmd);
678                }
679
680                if (pData->loginSequence.empty()) {
681                        LogMessage(MessageType::Error, _("Could not generate custom login sequence."));
682                        return false;
683                }
684        }
685        else {
686                LogMessage(MessageType::Error, _("Unknown FTP proxy type, cannot generate login sequence."));
687                return false;
688        }
689
690        return true;
691}
692
693int CFtpControlSocket::Logon()
694{
695        const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType();
696        if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != no)
697                m_useUTF8 = true;
698        else if (encoding == ENCODING_UTF8)
699                m_useUTF8 = true;
700
701        return FZ_REPLY_WOULDBLOCK;
702}
703
704int CFtpControlSocket::LogonParseResponse()
705{
706        if (!m_pCurOpData) {
707                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("LogonParseResponse without m_pCurOpData called"));
708                ResetOperation(FZ_REPLY_INTERNALERROR);
709                return FZ_REPLY_INTERNALERROR;
710        }
711
712        CFtpLogonOpData *pData = static_cast<CFtpLogonOpData *>(m_pCurOpData);
713
714        int code = GetReplyCode();
715
716        if (pData->opState == LOGON_WELCOME) {
717                if (code != 2 && code != 3) {
718                        DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0);
719                        return FZ_REPLY_DISCONNECTED;
720                }
721        }
722        else if (pData->opState == LOGON_AUTH_TLS ||
723                         pData->opState == LOGON_AUTH_SSL)
724        {
725                if (code != 2 && code != 3) {
726                        CServerCapabilities::SetCapability(*m_pCurrentServer, (pData->opState == LOGON_AUTH_TLS) ? auth_tls_command : auth_ssl_command, no);
727                        if (pData->opState == LOGON_AUTH_SSL) {
728                                if (m_pCurrentServer->GetProtocol() == FTP) {
729                                        // For now. In future make TLS mandatory unless explicitly requested INSECURE_FTP as protocol
730                                        LogMessage(MessageType::Status, _("Insecure server, it does not support FTP over TLS."));
731                                        pData->neededCommands[LOGON_PBSZ] = 0;
732                                        pData->neededCommands[LOGON_PROT] = 0;
733
734                                        pData->opState = LOGON_LOGON;
735                                        return SendNextCommand();
736                                }
737                                else {
738                                        DoClose(code == 5 ? FZ_REPLY_CRITICALERROR : 0);
739                                        return FZ_REPLY_DISCONNECTED;
740                                }
741                        }
742                }
743                else {
744                        CServerCapabilities::SetCapability(*m_pCurrentServer, (pData->opState == LOGON_AUTH_TLS) ? auth_tls_command : auth_ssl_command, yes);
745
746                        LogMessage(MessageType::Status, _("Initializing TLS..."));
747
748                        wxASSERT(!m_pTlsSocket);
749                        delete m_pBackend;
750
751                        m_pTlsSocket = new CTlsSocket(this, *m_pSocket, this);
752                        m_pBackend = m_pTlsSocket;
753
754                        if (!m_pTlsSocket->Init()) {
755                                LogMessage(MessageType::Error, _("Failed to initialize TLS."));
756                                DoClose(FZ_REPLY_INTERNALERROR);
757                                return FZ_REPLY_ERROR;
758                        }
759
760                        int res = m_pTlsSocket->Handshake();
761                        if (res == FZ_REPLY_ERROR) {
762                                DoClose();
763                                return FZ_REPLY_ERROR;
764                        }
765
766                        pData->neededCommands[LOGON_AUTH_SSL] = 0;
767                        pData->opState = LOGON_AUTH_WAIT;
768
769                        if (res == FZ_REPLY_WOULDBLOCK)
770                                return FZ_REPLY_WOULDBLOCK;
771                }
772        }
773        else if (pData->opState == LOGON_LOGON) {
774                t_loginCommand cmd = pData->loginSequence.front();
775
776                if (code != 2 && code != 3) {
777                        if( cmd.type == loginCommandType::user || cmd.type == loginCommandType::pass ) {
778                                wxString const user = m_pCurrentServer->GetUser();
779                                if( user.StartsWith(_T(" ")) || user.EndsWith(_T(" ")) ) {
780                                        LogMessage(MessageType::Status, _("Check your login credentials. The entered username starts or ends with a space character."));
781                                }
782                                wxString const pw = m_pCurrentServer->GetPass();
783                                if( pw.StartsWith(_T(" ")) || pw.EndsWith(_T(" ")) ) {
784                                        LogMessage(MessageType::Status, _("Check your login credentials. The entered password starts or ends with a space character."));
785                                }
786                        }
787
788                        if (m_pCurrentServer->GetEncodingType() == ENCODING_AUTO && m_useUTF8)
789                        {
790                                // Fall back to local charset for the case that the server might not
791                                // support UTF8 and the login data contains non-ascii characters.
792                                bool asciiOnly = true;
793                                for (unsigned int i = 0; i < m_pCurrentServer->GetUser().Length(); ++i)
794                                        if ((unsigned int)m_pCurrentServer->GetUser()[i] > 127)
795                                                asciiOnly = false;
796                                for (unsigned int i = 0; i < m_pCurrentServer->GetPass().Length(); ++i)
797                                        if ((unsigned int)m_pCurrentServer->GetPass()[i] > 127)
798                                                asciiOnly = false;
799                                for (unsigned int i = 0; i < m_pCurrentServer->GetAccount().Length(); ++i)
800                                        if ((unsigned int)m_pCurrentServer->GetAccount()[i] > 127)
801                                                asciiOnly = false;
802                                if (!asciiOnly) {
803                                        if (pData->ftp_proxy_type) {
804                                                LogMessage(MessageType::Status, _("Login data contains non-ASCII characters and server might not be UTF-8 aware. Cannot fall back to local charset since using proxy."));
805                                                int error = FZ_REPLY_DISCONNECTED;
806                                                if (cmd.type == loginCommandType::pass && code == 5)
807                                                        error |= FZ_REPLY_PASSWORDFAILED;
808                                                DoClose(error);
809                                                return FZ_REPLY_ERROR;
810                                        }
811                                        LogMessage(MessageType::Status, _("Login data contains non-ASCII characters and server might not be UTF-8 aware. Trying local charset."));
812                                        m_useUTF8 = false;
813                                        if (!GetLoginSequence(*m_pCurrentServer))
814                                        {
815                                                int error = FZ_REPLY_DISCONNECTED;
816                                                if (cmd.type == loginCommandType::pass && code == 5)
817                                                        error |= FZ_REPLY_PASSWORDFAILED;
818                                                DoClose(error);
819                                                return FZ_REPLY_ERROR;
820                                        }
821                                        return SendNextCommand();
822                                }
823                        }
824
825                        int error = FZ_REPLY_DISCONNECTED;
826                        if (cmd.type == loginCommandType::pass && code == 5)
827                                error |= FZ_REPLY_CRITICALERROR | FZ_REPLY_PASSWORDFAILED;
828                        DoClose(error);
829                        return FZ_REPLY_ERROR;
830                }
831
832                pData->loginSequence.pop_front();
833                if (code == 2) {
834                        while (!pData->loginSequence.empty() && pData->loginSequence.front().optional)
835                                pData->loginSequence.pop_front();
836                }
837                else if (code == 3 && pData->loginSequence.empty()) {
838                        LogMessage(MessageType::Error, _("Login sequence fully executed yet not logged in. Aborting."));
839                        if (cmd.type == loginCommandType::pass && m_pCurrentServer->GetAccount().empty())
840                                LogMessage(MessageType::Error, _("Server might require an account. Try specifying an account using the Site Manager"));
841                        DoClose(FZ_REPLY_CRITICALERROR);
842                        return FZ_REPLY_ERROR;
843                }
844
845                if (!pData->loginSequence.empty()) {
846                        pData->waitChallenge = false;
847
848                        return SendNextCommand();
849                }
850        }
851        else if (pData->opState == LOGON_SYST) {
852                if (code == 2)
853                        CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, yes, m_Response.Mid(4));
854                else
855                        CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, no);
856
857                if (m_pCurrentServer->GetType() == DEFAULT && code == 2) {
858                        if (m_Response.Length() > 7 && m_Response.Mid(3, 4) == _T(" MVS"))
859                                m_pCurrentServer->SetType(MVS);
860                        else if (m_Response.Len() > 12 && m_Response.Mid(3, 9).Upper() == _T(" NONSTOP "))
861                                m_pCurrentServer->SetType(HPNONSTOP);
862
863                        if (!m_MultilineResponseLines.empty() && m_MultilineResponseLines.front().Mid(4, 4).Upper() == _T("Z/VM")) {
864                                CServerCapabilities::SetCapability(*GetCurrentServer(), syst_command, yes, m_MultilineResponseLines.front().Mid(4) + _T(" ") + m_Response.Mid(4));
865                                m_pCurrentServer->SetType(ZVM);
866                        }
867                }
868
869                if (m_Response.Find(_T("FileZilla")) != -1) {
870                        pData->neededCommands[LOGON_CLNT] = 0;
871                        pData->neededCommands[LOGON_OPTSUTF8] = 0;
872                }
873        }
874        else if (pData->opState == LOGON_FEAT) {
875                if (code == 2) {
876                        CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, yes);
877                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes)
878                                CServerCapabilities::SetCapability(*m_pCurrentServer, utf8_command, no);
879                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, clnt_command) != yes)
880                                CServerCapabilities::SetCapability(*m_pCurrentServer, clnt_command, no);
881                }
882                else
883                        CServerCapabilities::SetCapability(*GetCurrentServer(), feat_command, no);
884
885                if (CServerCapabilities::GetCapability(*m_pCurrentServer, tvfs_support) != yes)
886                        CServerCapabilities::SetCapability(*m_pCurrentServer, tvfs_support, no);
887
888                const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType();
889                if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes)
890                {
891                        LogMessage(MessageType::Status, _("Server does not support non-ASCII characters."));
892                        m_useUTF8 = false;
893                }
894        }
895        else if (pData->opState == LOGON_PROT) {
896                if (code == 2 || code == 3)
897                        m_protectDataChannel = true;
898        }
899        else if (pData->opState == LOGON_CUSTOMCOMMANDS) {
900                ++pData->customCommandIndex;
901                if (pData->customCommandIndex < m_pCurrentServer->GetPostLoginCommands().size())
902                        return SendNextCommand();
903        }
904
905        for (;;) {
906                ++pData->opState;
907
908                if (pData->opState == LOGON_DONE) {
909                        LogMessage(MessageType::Status, _("Logged in"));
910                        ResetOperation(FZ_REPLY_OK);
911                        LogMessage(MessageType::Debug_Info, _T("Measured latency of %d ms"), m_rtt.GetLatency());
912                        return true;
913                }
914
915                if (!pData->neededCommands[pData->opState])
916                        continue;
917                else if (pData->opState == LOGON_SYST) {
918                        wxString system;
919                        enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), syst_command, &system);
920                        if (cap == unknown)
921                                break;
922                        else if (cap == yes) {
923                                if (m_pCurrentServer->GetType() == DEFAULT) {
924                                        if (system.Left(3) == _T("MVS"))
925                                                m_pCurrentServer->SetType(MVS);
926                                        else if (system.Left(4).Upper() == _T("Z/VM"))
927                                                m_pCurrentServer->SetType(ZVM);
928                                        else if (system.Left(8).Upper() == _T("NONSTOP "))
929                                                m_pCurrentServer->SetType(HPNONSTOP);
930                                }
931
932                                if (system.Find(_T("FileZilla")) != -1) {
933                                        pData->neededCommands[LOGON_CLNT] = 0;
934                                        pData->neededCommands[LOGON_OPTSUTF8] = 0;
935                                }
936                        }
937                }
938                else if (pData->opState == LOGON_FEAT) {
939                        enum capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), feat_command);
940                        if (cap == unknown)
941                                break;
942                        const enum CharsetEncoding encoding = m_pCurrentServer->GetEncodingType();
943                        if (encoding == ENCODING_AUTO && CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) != yes) {
944                                LogMessage(MessageType::Status, _("Server does not support non-ASCII characters."));
945                                m_useUTF8 = false;
946                        }
947                }
948                else if (pData->opState == LOGON_CLNT) {
949                        if (!m_useUTF8)
950                                continue;
951
952                        if (CServerCapabilities::GetCapability(*GetCurrentServer(), clnt_command) == yes)
953                                break;
954                }
955                else if (pData->opState == LOGON_OPTSUTF8) {
956                        if (!m_useUTF8)
957                                continue;
958
959                        if (CServerCapabilities::GetCapability(*GetCurrentServer(), utf8_command) == yes)
960                                break;
961                }
962                else if (pData->opState == LOGON_OPTSMLST) {
963                        wxString facts;
964                        if (CServerCapabilities::GetCapability(*GetCurrentServer(), mlsd_command, &facts) != yes)
965                                continue;
966                        capabilities cap = CServerCapabilities::GetCapability(*GetCurrentServer(), opst_mlst_command);
967                        if (cap == unknown) {
968                                facts = fz::str_tolower_ascii(facts);
969
970                                bool had_unset = false;
971                                wxString opts_facts;
972
973                                // Create a list of all facts understood by both FZ and the server.
974                                // Check if there's any supported fact not enabled by default, should that
975                                // be the case we need to send OPTS MLST
976                                while (!facts.empty()) {
977                                        int delim = facts.Find(';');
978                                        if (delim == -1)
979                                                break;
980
981                                        if (!delim) {
982                                                facts = facts.Mid(1);
983                                                continue;
984                                        }
985
986                                        bool enabled;
987                                        wxString fact;
988
989                                        if (facts[delim - 1] == '*') {
990                                                if (delim == 1) {
991                                                        facts = facts.Mid(delim + 1);
992                                                        continue;
993                                                }
994                                                enabled = true;
995                                                fact = facts.Left(delim - 1);
996                                        }
997                                        else {
998                                                enabled = false;
999                                                fact = facts.Left(delim);
1000                                        }
1001                                        facts = facts.Mid(delim + 1);
1002
1003                                        if (fact == _T("type") ||
1004                                                fact == _T("size") ||
1005                                                fact == _T("modify") ||
1006                                                fact == _T("perm") ||
1007                                                fact == _T("unix.mode") ||
1008                                                fact == _T("unix.owner") ||
1009                                                fact == _T("unix.user") ||
1010                                                fact == _T("unix.group") ||
1011                                                fact == _T("unix.uid") ||
1012                                                fact == _T("unix.gid") ||
1013                                                fact == _T("x.hidden"))
1014                                        {
1015                                                had_unset |= !enabled;
1016                                                opts_facts += fact + _T(";");
1017                                        }
1018                                }
1019
1020                                if (had_unset) {
1021                                        CServerCapabilities::SetCapability(*GetCurrentServer(), opst_mlst_command, yes, opts_facts);
1022                                        break;
1023                                }
1024                                else
1025                                        CServerCapabilities::SetCapability(*GetCurrentServer(), opst_mlst_command, no);
1026                        }
1027                        else if (cap == yes)
1028                                break;
1029                }
1030                else
1031                        break;
1032        }
1033
1034        return SendNextCommand();
1035}
1036
1037int CFtpControlSocket::LogonSend()
1038{
1039        if (!m_pCurOpData)
1040        {
1041                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("LogonParseResponse without m_pCurOpData called"));
1042                ResetOperation(FZ_REPLY_INTERNALERROR);
1043                return FZ_REPLY_INTERNALERROR;
1044        }
1045
1046        CFtpLogonOpData *pData = static_cast<CFtpLogonOpData *>(m_pCurOpData);
1047
1048        bool res;
1049        switch (pData->opState)
1050        {
1051        case LOGON_AUTH_WAIT:
1052                res = FZ_REPLY_WOULDBLOCK;
1053                LogMessage(MessageType::Debug_Info, _T("LogonSend() called during LOGON_AUTH_WAIT, ignoring"));
1054                break;
1055        case LOGON_AUTH_TLS:
1056                res = SendCommand(_T("AUTH TLS"), false, false);
1057                break;
1058        case LOGON_AUTH_SSL:
1059                res = SendCommand(_T("AUTH SSL"), false, false);
1060                break;
1061        case LOGON_SYST:
1062                res = SendCommand(_T("SYST"));
1063                break;
1064        case LOGON_LOGON:
1065                {
1066                        t_loginCommand cmd = pData->loginSequence.front();
1067                        switch (cmd.type)
1068                        {
1069                        case loginCommandType::user:
1070                                if (m_pCurrentServer->GetLogonType() == INTERACTIVE) {
1071                                        pData->waitChallenge = true;
1072                                        pData->challenge = _T("");
1073                                }
1074
1075                                if (cmd.command.empty())
1076                                        res = SendCommand(_T("USER ") + m_pCurrentServer->GetUser());
1077                                else
1078                                        res = SendCommand(cmd.command);
1079                                break;
1080                        case loginCommandType::pass:
1081                                if (!pData->challenge.empty()) {
1082                                        CInteractiveLoginNotification *pNotification = new CInteractiveLoginNotification(CInteractiveLoginNotification::interactive, pData->challenge, false);
1083                                        pNotification->server = *m_pCurrentServer;
1084                                        pData->challenge = _T("");
1085
1086                                        SendAsyncRequest(pNotification);
1087
1088                                        return FZ_REPLY_WOULDBLOCK;
1089                                }
1090
1091                                if (cmd.command.empty())
1092                                        res = SendCommand(_T("PASS ") + m_pCurrentServer->GetPass(), true);
1093                                else {
1094                                        wxString c = cmd.command;
1095                                        wxString pass = m_pCurrentServer->GetPass();
1096                                        pass.Replace(_T("%"), _T("%%"));
1097                                        c.Replace(_T("%p"), pass);
1098                                        c.Replace(_T("%%"), _T("%"));
1099                                        res = SendCommand(c, true);
1100                                }
1101                                break;
1102                        case loginCommandType::account:
1103                                if (cmd.command.empty())
1104                                        res = SendCommand(_T("ACCT ") + m_pCurrentServer->GetAccount());
1105                                else
1106                                        res = SendCommand(cmd.command);
1107                                break;
1108                        case loginCommandType::other:
1109                                wxASSERT(!cmd.command.empty());
1110                                res = SendCommand(cmd.command, cmd.hide_arguments);
1111                                break;
1112                        default:
1113                                res = false;
1114                                break;
1115                        }
1116                }
1117                break;
1118        case LOGON_FEAT:
1119                res = SendCommand(_T("FEAT"));
1120                break;
1121        case LOGON_CLNT:
1122                // Some servers refuse to enable UTF8 if client does not send CLNT command
1123                // to fix compatibility with Internet Explorer, but in the process breaking
1124                // compatibility with other clients.
1125                // Rather than forcing MS to fix Internet Explorer, letting other clients
1126                // suffer is a questionable decision in my opinion.
1127                res = SendCommand(_T("CLNT FileZilla"));
1128                break;
1129        case LOGON_OPTSUTF8:
1130                // Handle servers that disobey RFC 2640 by having UTF8 in their FEAT
1131                // response but do not use UTF8 unless OPTS UTF8 ON gets send.
1132                // However these servers obey a conflicting ietf draft:
1133                // http://www.ietf.org/proceedings/02nov/I-D/draft-ietf-ftpext-utf-8-option-00.txt
1134                // Example servers are, amongst others, G6 FTP Server and RaidenFTPd.
1135                res = SendCommand(_T("OPTS UTF8 ON"));
1136                break;
1137        case LOGON_PBSZ:
1138                res = SendCommand(_T("PBSZ 0"));
1139                break;
1140        case LOGON_PROT:
1141                res = SendCommand(_T("PROT P"));
1142                break;
1143        case LOGON_CUSTOMCOMMANDS:
1144                if (pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size())
1145                {
1146                        LogMessage(MessageType::Debug_Warning, _T("pData->customCommandIndex >= m_pCurrentServer->GetPostLoginCommands().size()"));
1147                        DoClose(FZ_REPLY_INTERNALERROR);
1148                        return FZ_REPLY_ERROR;
1149                }
1150                res = SendCommand(m_pCurrentServer->GetPostLoginCommands()[pData->customCommandIndex]);
1151                break;
1152        case LOGON_OPTSMLST:
1153                {
1154                        wxString args;
1155                        CServerCapabilities::GetCapability(*GetCurrentServer(), opst_mlst_command, &args);
1156                        res = SendCommand(_T("OPTS MLST " + args));
1157                }
1158                break;
1159        default:
1160                return FZ_REPLY_ERROR;
1161        }
1162
1163        if (!res)
1164                return FZ_REPLY_ERROR;
1165
1166        return FZ_REPLY_WOULDBLOCK;
1167}
1168
1169int CFtpControlSocket::GetReplyCode() const
1170{
1171        if (m_Response.empty()) {
1172                return 0;
1173        }
1174        else if (m_Response[0] < '0' || m_Response[0] > '9') {
1175                return 0;
1176        }
1177        else {
1178                return m_Response[0] - '0';
1179        }
1180}
1181
1182bool CFtpControlSocket::SendCommand(wxString const& str, bool maskArgs, bool measureRTT)
1183{
1184        int pos;
1185        if (maskArgs && (pos = str.Find(_T(" "))) != -1)
1186        {
1187                wxString stars('*', str.Length() - pos - 1);
1188                LogMessageRaw(MessageType::Command, str.Left(pos + 1) + stars);
1189        }
1190        else
1191                LogMessageRaw(MessageType::Command, str);
1192
1193        wxCharBuffer buffer = ConvToServer(str + _T("\r\n"));
1194        if (!buffer)
1195        {
1196                LogMessage(MessageType::Error, _T("Failed to convert command to 8 bit charset"));
1197                return false;
1198        }
1199        unsigned int len = (unsigned int)strlen(buffer);
1200        bool res = CRealControlSocket::Send(buffer, len);
1201        if (res)
1202                ++m_pendingReplies;
1203
1204        if (measureRTT)
1205                m_rtt.Start();
1206
1207        return res;
1208}
1209
1210class CFtpListOpData : public COpData, public CFtpTransferOpData
1211{
1212public:
1213        CFtpListOpData()
1214                : COpData(Command::list)
1215                , fallback_to_current()
1216                , m_pDirectoryListingParser()
1217                , refresh()
1218                , viewHiddenCheck()
1219                , viewHidden()
1220                , mdtm_index()
1221        {
1222        }
1223
1224        virtual ~CFtpListOpData()
1225        {
1226                delete m_pDirectoryListingParser;
1227        }
1228
1229        CServerPath path;
1230        wxString subDir;
1231        bool fallback_to_current;
1232
1233        CDirectoryListingParser* m_pDirectoryListingParser;
1234
1235        CDirectoryListing directoryListing;
1236
1237        // Set to true to get a directory listing even if a cache
1238        // lookup can be made after finding out true remote directory
1239        bool refresh;
1240
1241        bool viewHiddenCheck;
1242        bool viewHidden; // Uses LIST -a command
1243
1244        // Listing index for list_mdtm
1245        int mdtm_index;
1246
1247        fz::monotonic_clock m_time_before_locking;
1248};
1249
1250enum listStates
1251{
1252        list_init = 0,
1253        list_waitcwd,
1254        list_waitlock,
1255        list_waittransfer,
1256        list_mdtm
1257};
1258
1259int CFtpControlSocket::List(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, int flags /*=0*/)
1260{
1261        CServerPath newPath = m_CurrentPath;
1262        if (!path.empty()) {
1263                newPath = path;
1264        }
1265        if (!newPath.ChangePath(subDir)) {
1266                newPath.clear();
1267        }
1268
1269        if (newPath.empty()) {
1270                LogMessage(MessageType::Status, _("Retrieving directory listing..."));
1271        }
1272        else {
1273                LogMessage(MessageType::Status, _("Retrieving directory listing of \"%s\"..."), newPath.GetPath());
1274        }
1275
1276        if (m_pCurOpData) {
1277                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("List called from other command"));
1278        }
1279        CFtpListOpData *pData = new CFtpListOpData;
1280        pData->pNextOpData = m_pCurOpData;
1281        m_pCurOpData = pData;
1282
1283        pData->opState = list_waitcwd;
1284
1285        if (path.GetType() == DEFAULT)
1286                path.SetType(m_pCurrentServer->GetType());
1287        pData->path = path;
1288        pData->subDir = subDir;
1289        pData->refresh = (flags & LIST_FLAG_REFRESH) != 0;
1290        pData->fallback_to_current = !path.empty() && (flags & LIST_FLAG_FALLBACK_CURRENT) != 0;
1291
1292        int res = ChangeDir(path, subDir, (flags & LIST_FLAG_LINK) != 0);
1293        if (res != FZ_REPLY_OK)
1294                return res;
1295
1296        return ParseSubcommandResult(FZ_REPLY_OK);
1297}
1298
1299int CFtpControlSocket::ListSubcommandResult(int prevResult)
1300{
1301        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ListSubcommandResult()"));
1302
1303        if (!m_pCurOpData) {
1304                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1305                ResetOperation(FZ_REPLY_INTERNALERROR);
1306                return FZ_REPLY_ERROR;
1307        }
1308
1309        CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData);
1310        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
1311
1312        if (pData->opState == list_waitcwd) {
1313                if (prevResult != FZ_REPLY_OK) {
1314                        if (prevResult & FZ_REPLY_LINKNOTDIR) {
1315                                ResetOperation(prevResult);
1316                                return FZ_REPLY_ERROR;
1317                        }
1318
1319                        if (pData->fallback_to_current) {
1320                                // List current directory instead
1321                                pData->fallback_to_current = false;
1322                                pData->path.clear();
1323                                pData->subDir = _T("");
1324                                int res = ChangeDir();
1325                                if (res != FZ_REPLY_OK)
1326                                        return res;
1327                        }
1328                        else {
1329                                ResetOperation(prevResult);
1330                                return FZ_REPLY_ERROR;
1331                        }
1332                }
1333                if (pData->path.empty()) {
1334                        pData->path = m_CurrentPath;
1335                        wxASSERT(pData->subDir.empty());
1336                        wxASSERT(!pData->path.empty());
1337                }
1338
1339                if (!pData->refresh) {
1340                        wxASSERT(!pData->pNextOpData);
1341
1342                        // Do a cache lookup now that we know the correct directory
1343                        int hasUnsureEntries;
1344                        bool is_outdated = false;
1345                        bool found = engine_.GetDirectoryCache().DoesExist(*m_pCurrentServer, m_CurrentPath, hasUnsureEntries, is_outdated);
1346                        if (found) {
1347                                // We're done if listing is recent and has no outdated entries
1348                                if (!is_outdated && !hasUnsureEntries) {
1349                                        engine_.SendDirectoryListingNotification(m_CurrentPath, true, false, false);
1350
1351                                        ResetOperation(FZ_REPLY_OK);
1352
1353                                        return FZ_REPLY_OK;
1354                                }
1355                        }
1356                }
1357
1358                if (!pData->holdsLock) {
1359                        if (!TryLockCache(lock_list, m_CurrentPath)) {
1360                                pData->opState = list_waitlock;
1361                                pData->m_time_before_locking = fz::monotonic_clock::now();
1362                                return FZ_REPLY_WOULDBLOCK;
1363                        }
1364                }
1365
1366                delete m_pTransferSocket;
1367                m_pTransferSocket = new CTransferSocket(engine_, *this, TransferMode::list);
1368
1369                // Assume that a server supporting UTF-8 does not send EBCDIC listings.
1370                listingEncoding::type encoding = listingEncoding::unknown;
1371                if (CServerCapabilities::GetCapability(*m_pCurrentServer, utf8_command) == yes)
1372                        encoding = listingEncoding::normal;
1373
1374                pData->m_pDirectoryListingParser = new CDirectoryListingParser(this, *m_pCurrentServer, encoding);
1375
1376                pData->m_pDirectoryListingParser->SetTimezoneOffset(GetTimezoneOffset());
1377                m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;
1378
1379                engine_.transfer_status_.Init(-1, 0, true);
1380
1381                pData->opState = list_waittransfer;
1382                if (CServerCapabilities::GetCapability(*m_pCurrentServer, mlsd_command) == yes)
1383                        return Transfer(_T("MLSD"), pData);
1384                else {
1385                        if (engine_.GetOptions().GetOptionVal(OPTION_VIEW_HIDDEN_FILES)) {
1386                                enum capabilities cap = CServerCapabilities::GetCapability(*m_pCurrentServer, list_hidden_support);
1387                                if (cap == unknown)
1388                                        pData->viewHiddenCheck = true;
1389                                else if (cap == yes)
1390                                        pData->viewHidden = true;
1391                                else
1392                                        LogMessage(MessageType::Debug_Info, _("View hidden option set, but unsupported by server"));
1393                        }
1394
1395                        if (pData->viewHidden)
1396                                return Transfer(_T("LIST -a"), pData);
1397                        else
1398                                return Transfer(_T("LIST"), pData);
1399                }
1400        }
1401        else if (pData->opState == list_waittransfer) {
1402                if (prevResult == FZ_REPLY_OK) {
1403                        CDirectoryListing listing = pData->m_pDirectoryListingParser->Parse(m_CurrentPath);
1404
1405                        if (pData->viewHiddenCheck) {
1406                                if (!pData->viewHidden) {
1407                                        // Repeat with LIST -a
1408                                        pData->viewHidden = true;
1409                                        pData->directoryListing = listing;
1410
1411                                        // Reset status
1412                                        pData->transferEndReason = TransferEndReason::successful;
1413                                        pData->tranferCommandSent = false;
1414                                        delete m_pTransferSocket;
1415                                        m_pTransferSocket = new CTransferSocket(engine_, *this, TransferMode::list);
1416                                        pData->m_pDirectoryListingParser->Reset();
1417                                        m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;
1418
1419                                        return Transfer(_T("LIST -a"), pData);
1420                                }
1421                                else {
1422                                        if (CheckInclusion(listing, pData->directoryListing)) {
1423                                                LogMessage(MessageType::Debug_Info, _T("Server seems to support LIST -a"));
1424                                                CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes);
1425                                        }
1426                                        else {
1427                                                LogMessage(MessageType::Debug_Info, _T("Server does not seem to support LIST -a"));
1428                                                CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);
1429                                                listing = pData->directoryListing;
1430                                        }
1431                                }
1432                        }
1433
1434                        SetAlive();
1435
1436                        int res = ListCheckTimezoneDetection(listing);
1437                        if (res != FZ_REPLY_OK)
1438                                return res;
1439
1440                        engine_.GetDirectoryCache().Store(listing, *m_pCurrentServer);
1441
1442                        engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1443
1444                        ResetOperation(FZ_REPLY_OK);
1445                        return FZ_REPLY_OK;
1446                }
1447                else {
1448                        if (pData->tranferCommandSent && IsMisleadingListResponse()) {
1449                                CDirectoryListing listing;
1450                                listing.path = m_CurrentPath;
1451                                listing.m_firstListTime = fz::monotonic_clock::now();
1452
1453                                if (pData->viewHiddenCheck) {
1454                                        if (pData->viewHidden) {
1455                                                if (pData->directoryListing.GetCount()) {
1456                                                        // Less files with LIST -a
1457                                                        // Not supported
1458                                                        LogMessage(MessageType::Debug_Info, _T("Server does not seem to support LIST -a"));
1459                                                        CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);
1460                                                        listing = pData->directoryListing;
1461                                                }
1462                                                else {
1463                                                        LogMessage(MessageType::Debug_Info, _T("Server seems to support LIST -a"));
1464                                                        CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, yes);
1465                                                }
1466                                        }
1467                                        else {
1468                                                // Reset status
1469                                                pData->transferEndReason = TransferEndReason::successful;
1470                                                pData->tranferCommandSent = false;
1471                                                delete m_pTransferSocket;
1472                                                m_pTransferSocket = new CTransferSocket(engine_, *this, TransferMode::list);
1473                                                pData->m_pDirectoryListingParser->Reset();
1474                                                m_pTransferSocket->m_pDirectoryListingParser = pData->m_pDirectoryListingParser;
1475
1476                                                // Repeat with LIST -a
1477                                                pData->viewHidden = true;
1478                                                pData->directoryListing = listing;
1479                                                return Transfer(_T("LIST -a"), pData);
1480                                        }
1481                                }
1482
1483                                int res = ListCheckTimezoneDetection(listing);
1484                                if (res != FZ_REPLY_OK)
1485                                        return res;
1486
1487                                engine_.GetDirectoryCache().Store(listing, *m_pCurrentServer);
1488
1489                                engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1490
1491                                ResetOperation(FZ_REPLY_OK);
1492                                return FZ_REPLY_OK;
1493                        }
1494                        else {
1495                                if (pData->viewHiddenCheck) {
1496                                        // If server does not support LIST -a, the server might reject this command
1497                                        // straight away. In this case, back to the previously retrieved listing.
1498                                        // On other failures like timeouts and such, return an error
1499                                        if (pData->viewHidden &&
1500                                                pData->transferEndReason == TransferEndReason::transfer_command_failure_immediate)
1501                                        {
1502                                                CServerCapabilities::SetCapability(*m_pCurrentServer, list_hidden_support, no);
1503
1504                                                int res = ListCheckTimezoneDetection(pData->directoryListing);
1505                                                if (res != FZ_REPLY_OK)
1506                                                        return res;
1507
1508                                                engine_.GetDirectoryCache().Store(pData->directoryListing, *m_pCurrentServer);
1509
1510                                                engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1511
1512                                                ResetOperation(FZ_REPLY_OK);
1513                                                return FZ_REPLY_OK;
1514                                        }
1515                                }
1516
1517                                if (prevResult & FZ_REPLY_ERROR)
1518                                        engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, true);
1519                        }
1520
1521                        ResetOperation(FZ_REPLY_ERROR);
1522                        return FZ_REPLY_ERROR;
1523                }
1524        }
1525        else {
1526                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Wrong opState: %d"), pData->opState);
1527                ResetOperation(FZ_REPLY_INTERNALERROR);
1528                return FZ_REPLY_ERROR;
1529        }
1530}
1531
1532int CFtpControlSocket::ListSend()
1533{
1534        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ListSend()"));
1535
1536        if (!m_pCurOpData) {
1537                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1538                ResetOperation(FZ_REPLY_INTERNALERROR);
1539                return FZ_REPLY_ERROR;
1540        }
1541
1542        CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData);
1543        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
1544
1545        if (pData->opState == list_waitlock) {
1546                if (!pData->holdsLock) {
1547                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Not holding the lock as expected"));
1548                        ResetOperation(FZ_REPLY_INTERNALERROR);
1549                        return FZ_REPLY_ERROR;
1550                }
1551
1552                // Check if we can use already existing listing
1553                CDirectoryListing listing;
1554                bool is_outdated = false;
1555                wxASSERT(pData->subDir.empty()); // Did do ChangeDir before trying to lock
1556                bool found = engine_.GetDirectoryCache().Lookup(listing, *m_pCurrentServer, pData->path, true, is_outdated);
1557                if (found && !is_outdated && !listing.get_unsure_flags() &&
1558                        listing.m_firstListTime >= pData->m_time_before_locking)
1559                {
1560                        engine_.SendDirectoryListingNotification(listing.path, !pData->pNextOpData, false, false);
1561
1562                        ResetOperation(FZ_REPLY_OK);
1563                        return FZ_REPLY_OK;
1564                }
1565
1566                pData->opState = list_waitcwd;
1567
1568                return ListSubcommandResult(FZ_REPLY_OK);
1569        }
1570        if (pData->opState == list_mdtm) {
1571                LogMessage(MessageType::Status, _("Calculating timezone offset of server..."));
1572                wxString cmd = _T("MDTM ") + m_CurrentPath.FormatFilename(pData->directoryListing[pData->mdtm_index].name, true);
1573                if (!SendCommand(cmd))
1574                        return FZ_REPLY_ERROR;
1575                else
1576                        return FZ_REPLY_WOULDBLOCK;
1577        }
1578
1579        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("invalid opstate"));
1580        ResetOperation(FZ_REPLY_INTERNALERROR);
1581        return FZ_REPLY_ERROR;
1582}
1583
1584int CFtpControlSocket::ListParseResponse()
1585{
1586        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ListParseResponse()"));
1587
1588        if (!m_pCurOpData)
1589        {
1590                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Empty m_pCurOpData"));
1591                ResetOperation(FZ_REPLY_INTERNALERROR);
1592                return FZ_REPLY_ERROR;
1593        }
1594
1595        CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData);
1596
1597        if (pData->opState != list_mdtm) {
1598                LogMessage(MessageType::Debug_Warning, _T("ListParseResponse should never be called if opState != list_mdtm"));
1599                ResetOperation(FZ_REPLY_INTERNALERROR);
1600                return FZ_REPLY_ERROR;
1601        }
1602
1603
1604        // First condition prevents problems with concurrent MDTM
1605        if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown &&
1606                m_Response.Left(4) == _T("213 ") && m_Response.Length() > 16)
1607        {
1608                fz::datetime date(m_Response.Mid(4).ToStdWstring(), fz::datetime::utc);
1609                if (date.empty()) {
1610                        wxASSERT(pData->directoryListing[pData->mdtm_index].has_date());
1611                        fz::datetime listTime = pData->directoryListing[pData->mdtm_index].time;
1612                        listTime -= fz::duration::from_minutes(m_pCurrentServer->GetTimezoneOffset());
1613
1614                        int serveroffset = static_cast<int>((date - listTime).get_seconds());
1615                        if (!pData->directoryListing[pData->mdtm_index].has_seconds()) {
1616                                // Round offset to full minutes
1617                                if (serveroffset < 0)
1618                                        serveroffset -= 59;
1619                                serveroffset -= serveroffset % 60;
1620                        }
1621
1622                        LogMessage(MessageType::Status, _("Timezone offset of server is %d seconds."), -serveroffset);
1623
1624                        fz::duration span = fz::duration::from_seconds(serveroffset);
1625                        const int count = pData->directoryListing.GetCount();
1626                        for (int i = 0; i < count; ++i) {
1627                                CDirentry& entry = pData->directoryListing[i];
1628                                entry.time += span;
1629                        }
1630
1631                        // TODO: Correct cached listings
1632
1633                        CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, yes, serveroffset);
1634                }
1635                else {
1636                        CServerCapabilities::SetCapability(*m_pCurrentServer, mdtm_command, no);
1637                        CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no);
1638                }
1639        }
1640        else
1641                CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no);
1642
1643        engine_.GetDirectoryCache().Store(pData->directoryListing, *m_pCurrentServer);
1644
1645        engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1646
1647        ResetOperation(FZ_REPLY_OK);
1648        return FZ_REPLY_OK;
1649}
1650
1651int CFtpControlSocket::ListCheckTimezoneDetection(CDirectoryListing& listing)
1652{
1653        wxASSERT(m_pCurOpData);
1654
1655        CFtpListOpData *pData = static_cast<CFtpListOpData *>(m_pCurOpData);
1656
1657        if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown)
1658        {
1659                if (CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) != yes)
1660                        CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, no);
1661                else
1662                {
1663                        const int count = listing.GetCount();
1664                        for (int i = 0; i < count; ++i) {
1665                                if (!listing[i].is_dir() && listing[i].has_time()) {
1666                                        pData->opState = list_mdtm;
1667                                        pData->directoryListing = listing;
1668                                        pData->mdtm_index = i;
1669                                        return SendNextCommand();
1670                                }
1671                        }
1672                }
1673        }
1674
1675        return FZ_REPLY_OK;
1676}
1677
1678int CFtpControlSocket::ResetOperation(int nErrorCode)
1679{
1680        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ResetOperation(%d)"), nErrorCode);
1681
1682        CTransferSocket* pTransferSocket = m_pTransferSocket;
1683        m_pTransferSocket = 0;
1684        delete pTransferSocket;
1685        delete m_pIPResolver;
1686        m_pIPResolver = 0;
1687
1688        m_repliesToSkip = m_pendingReplies;
1689
1690        if (m_pCurOpData && m_pCurOpData->opId == Command::transfer) {
1691                CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
1692                if (pData->tranferCommandSent) {
1693                        if (pData->transferEndReason == TransferEndReason::transfer_failure_critical)
1694                                nErrorCode |= FZ_REPLY_CRITICALERROR | FZ_REPLY_WRITEFAILED;
1695                        if (pData->transferEndReason != TransferEndReason::transfer_command_failure_immediate || GetReplyCode() != 5)
1696                                pData->transferInitiated = true;
1697                        else {
1698                                if (nErrorCode == FZ_REPLY_ERROR)
1699                                        nErrorCode |= FZ_REPLY_CRITICALERROR;
1700                        }
1701                }
1702                if (nErrorCode != FZ_REPLY_OK && pData->download && !pData->fileDidExist) {
1703                        delete pData->pIOThread;
1704                        pData->pIOThread = 0;
1705                        int64_t size;
1706                        bool isLink;
1707                        if (fz::local_filesys::get_file_info(fz::to_native(pData->localFile), isLink, &size, 0, 0) == fz::local_filesys::file && size == 0) {
1708                                // Download failed and a new local file was created before, but
1709                                // nothing has been written to it. Remove it again, so we don't
1710                                // leave a bunch of empty files all over the place.
1711                                LogMessage(MessageType::Debug_Verbose, _T("Deleting empty file"));
1712                                wxRemoveFile(pData->localFile);
1713                        }
1714                }
1715        }
1716        if (m_pCurOpData && m_pCurOpData->opId == Command::del && !(nErrorCode & FZ_REPLY_DISCONNECTED)) {
1717                CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);
1718                if (pData->m_needSendListing)
1719                        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
1720        }
1721
1722        if (m_pCurOpData && m_pCurOpData->opId == Command::rawtransfer &&
1723                nErrorCode != FZ_REPLY_OK)
1724        {
1725                CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
1726                if (pData->pOldData->transferEndReason == TransferEndReason::successful)
1727                {
1728                        if ((nErrorCode & FZ_REPLY_TIMEOUT) == FZ_REPLY_TIMEOUT)
1729                                pData->pOldData->transferEndReason = TransferEndReason::timeout;
1730                        else if (!pData->pOldData->tranferCommandSent)
1731                                pData->pOldData->transferEndReason = TransferEndReason::pre_transfer_command_failure;
1732                        else
1733                                pData->pOldData->transferEndReason = TransferEndReason::failure;
1734                }
1735        }
1736
1737        m_lastCommandCompletionTime = fz::monotonic_clock::now();
1738        if (m_pCurOpData && !(nErrorCode & FZ_REPLY_DISCONNECTED))
1739                StartKeepaliveTimer();
1740        else {
1741                stop_timer(m_idleTimer);
1742                m_idleTimer = 0;
1743        }
1744
1745        return CControlSocket::ResetOperation(nErrorCode);
1746}
1747
1748int CFtpControlSocket::SendNextCommand()
1749{
1750        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::SendNextCommand()"));
1751        if (!m_pCurOpData)
1752        {
1753                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("SendNextCommand called without active operation"));
1754                ResetOperation(FZ_REPLY_ERROR);
1755                return FZ_REPLY_ERROR;
1756        }
1757
1758        if (m_pCurOpData->waitForAsyncRequest)
1759        {
1760                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Waiting for async request, ignoring SendNextCommand..."));
1761                return FZ_REPLY_WOULDBLOCK;
1762        }
1763
1764        if (m_repliesToSkip)
1765        {
1766                LogMessage(MessageType::Status, _T("Waiting for replies to skip before sending next command..."));
1767                SetWait(true);
1768                return FZ_REPLY_WOULDBLOCK;
1769        }
1770
1771        switch (m_pCurOpData->opId)
1772        {
1773        case Command::list:
1774                return ListSend();
1775        case Command::connect:
1776                return LogonSend();
1777        case Command::cwd:
1778                return ChangeDirSend();
1779        case Command::transfer:
1780                return FileTransferSend();
1781        case Command::mkdir:
1782                return MkdirSend();
1783        case Command::rename:
1784                return RenameSend();
1785        case Command::chmod:
1786                return ChmodSend();
1787        case Command::rawtransfer:
1788                return TransferSend();
1789        case Command::raw:
1790                return RawCommandSend();
1791        case Command::del:
1792                return DeleteSend();
1793        case Command::removedir:
1794                return RemoveDirSend();
1795        default:
1796                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId);
1797                ResetOperation(FZ_REPLY_INTERNALERROR);
1798                break;
1799        }
1800
1801        return FZ_REPLY_ERROR;
1802}
1803
1804class CFtpChangeDirOpData : public CChangeDirOpData
1805{
1806public:
1807        CFtpChangeDirOpData()
1808        {
1809                tried_cdup = false;
1810        }
1811
1812        virtual ~CFtpChangeDirOpData()
1813        {
1814        }
1815
1816        bool tried_cdup;
1817};
1818
1819enum cwdStates
1820{
1821        cwd_init = 0,
1822        cwd_pwd,
1823        cwd_cwd,
1824        cwd_pwd_cwd,
1825        cwd_cwd_subdir,
1826        cwd_pwd_subdir
1827};
1828
1829int CFtpControlSocket::ChangeDir(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, bool link_discovery /*=false*/)
1830{
1831        enum cwdStates state = cwd_init;
1832
1833        if (path.GetType() == DEFAULT)
1834                path.SetType(m_pCurrentServer->GetType());
1835
1836        CServerPath target;
1837        if (path.empty())
1838        {
1839                if (m_CurrentPath.empty())
1840                        state = cwd_pwd;
1841                else
1842                        return FZ_REPLY_OK;
1843        }
1844        else
1845        {
1846                if (!subDir.empty())
1847                {
1848                        // Check if the target is in cache already
1849                        target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, subDir);
1850                        if (!target.empty())
1851                        {
1852                                if (m_CurrentPath == target)
1853                                        return FZ_REPLY_OK;
1854
1855                                path = target;
1856                                subDir = _T("");
1857                                state = cwd_cwd;
1858                        }
1859                        else
1860                        {
1861                                // Target unknown, check for the parent's target
1862                                target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, _T(""));
1863                                if (m_CurrentPath == path || (!target.empty() && target == m_CurrentPath)) {
1864                                        target.clear();
1865                                        state = cwd_cwd_subdir;
1866                                }
1867                                else
1868                                        state = cwd_cwd;
1869                        }
1870                }
1871                else {
1872                        target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, _T(""));
1873                        if (m_CurrentPath == path || (!target.empty() && target == m_CurrentPath))
1874                                return FZ_REPLY_OK;
1875                        state = cwd_cwd;
1876                }
1877        }
1878
1879        CFtpChangeDirOpData *pData = new CFtpChangeDirOpData;
1880        pData->pNextOpData = m_pCurOpData;
1881        pData->opState = state;
1882        pData->path = path;
1883        pData->subDir = subDir;
1884        pData->target = target;
1885        pData->link_discovery = link_discovery;
1886
1887        if (pData->pNextOpData && pData->pNextOpData->opId == Command::transfer &&
1888                !static_cast<CFtpFileTransferOpData *>(pData->pNextOpData)->download)
1889        {
1890                pData->tryMkdOnFail = true;
1891                wxASSERT(subDir.empty());
1892        }
1893
1894
1895        m_pCurOpData = pData;
1896
1897        return SendNextCommand();
1898}
1899
1900int CFtpControlSocket::ChangeDirParseResponse()
1901{
1902        if (!m_pCurOpData)
1903        {
1904                ResetOperation(FZ_REPLY_ERROR);
1905                return FZ_REPLY_ERROR;
1906        }
1907        CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData);
1908
1909        int code = GetReplyCode();
1910        bool error = false;
1911        switch (pData->opState)
1912        {
1913        case cwd_pwd:
1914                if (code != 2 && code != 3)
1915                        error = true;
1916                else if (ParsePwdReply(m_Response))
1917                {
1918                        ResetOperation(FZ_REPLY_OK);
1919                        return FZ_REPLY_OK;
1920                }
1921                else
1922                        error = true;
1923                break;
1924        case cwd_cwd:
1925                if (code != 2 && code != 3)
1926                {
1927                        // Create remote directory if part of a file upload
1928                        if (pData->tryMkdOnFail)
1929                        {
1930                                pData->tryMkdOnFail = false;
1931                                int res = Mkdir(pData->path);
1932                                if (res != FZ_REPLY_OK)
1933                                        return res;
1934                        }
1935                        else
1936                                error = true;
1937                }
1938                else
1939                {
1940                        if (pData->target.empty())
1941                                pData->opState = cwd_pwd_cwd;
1942                        else
1943                        {
1944                                m_CurrentPath = pData->target;
1945                                if (pData->subDir.empty())
1946                                {
1947                                        ResetOperation(FZ_REPLY_OK);
1948                                        return FZ_REPLY_OK;
1949                                }
1950
1951                                pData->target.clear();
1952                                pData->opState = cwd_cwd_subdir;
1953                        }
1954                }
1955                break;
1956        case cwd_pwd_cwd:
1957                if (code != 2 && code != 3)
1958                {
1959                        LogMessage(MessageType::Debug_Warning, _T("PWD failed, assuming path is '%s'."), pData->path.GetPath());
1960                        m_CurrentPath = pData->path;
1961
1962                        if (pData->target.empty())
1963                                engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path);
1964
1965                        if (pData->subDir.empty())
1966                        {
1967                                ResetOperation(FZ_REPLY_OK);
1968                                return FZ_REPLY_OK;
1969                        }
1970                        else
1971                                pData->opState = cwd_cwd_subdir;
1972                }
1973                else if (ParsePwdReply(m_Response, false, pData->path))
1974                {
1975                        if (pData->target.empty())
1976                        {
1977                                engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path);
1978                        }
1979                        if (pData->subDir.empty())
1980                        {
1981                                ResetOperation(FZ_REPLY_OK);
1982                                return FZ_REPLY_OK;
1983                        }
1984                        else
1985                                pData->opState = cwd_cwd_subdir;
1986                }
1987                else
1988                        error = true;
1989                break;
1990        case cwd_cwd_subdir:
1991                if (code != 2 && code != 3)
1992                {
1993                        if (pData->subDir == _T("..") && !pData->tried_cdup && m_Response.Left(2) == _T("50"))
1994                        {
1995                                // CDUP command not implemented, try again using CWD ..
1996                                pData->tried_cdup = true;
1997                        }
1998                        else if (pData->link_discovery)
1999                        {
2000                                LogMessage(MessageType::Debug_Info, _T("Symlink does not link to a directory, probably a file"));
2001                                ResetOperation(FZ_REPLY_LINKNOTDIR);
2002                                return FZ_REPLY_ERROR;
2003                        }
2004                        else
2005                                error = true;
2006                }
2007                else
2008                        pData->opState = cwd_pwd_subdir;
2009                break;
2010        case cwd_pwd_subdir:
2011                {
2012                        CServerPath assumedPath(pData->path);
2013                        if (pData->subDir == _T(".."))
2014                        {
2015                                if (!assumedPath.HasParent())
2016                                        assumedPath.clear();
2017                                else
2018                                        assumedPath = assumedPath.GetParent();
2019                        }
2020                        else
2021                                assumedPath.AddSegment(pData->subDir);
2022
2023                        if (code != 2 && code != 3)
2024                        {
2025                                if (!assumedPath.empty())
2026                                {
2027                                        LogMessage(MessageType::Debug_Warning, _T("PWD failed, assuming path is '%s'."), assumedPath.GetPath());
2028                                        m_CurrentPath = assumedPath;
2029
2030                                        if (pData->target.empty())
2031                                        {
2032                                                engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);
2033                                        }
2034
2035                                        ResetOperation(FZ_REPLY_OK);
2036                                        return FZ_REPLY_OK;
2037                                }
2038                                else
2039                                {
2040                                        LogMessage(MessageType::Debug_Warning, _T("PWD failed, unable to guess current path."));
2041                                        error = true;
2042                                }
2043                        }
2044                        else if (ParsePwdReply(m_Response, false, assumedPath))
2045                        {
2046                                if (pData->target.empty())
2047                                {
2048                                        engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);
2049                                }
2050
2051                                ResetOperation(FZ_REPLY_OK);
2052                                return FZ_REPLY_OK;
2053                        }
2054                        else
2055                                error = true;
2056                }
2057                break;
2058        }
2059
2060        if (error)
2061        {
2062                ResetOperation(FZ_REPLY_ERROR);
2063                return FZ_REPLY_ERROR;
2064        }
2065
2066        return SendNextCommand();
2067}
2068
2069int CFtpControlSocket::ChangeDirSubcommandResult(int WXUNUSED(prevResult))
2070{
2071        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ChangeDirSubcommandResult()"));
2072
2073        return SendNextCommand();
2074}
2075
2076int CFtpControlSocket::ChangeDirSend()
2077{
2078        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ChangeDirSend()"));
2079
2080        if (!m_pCurOpData)
2081        {
2082                ResetOperation(FZ_REPLY_ERROR);
2083                return FZ_REPLY_ERROR;
2084        }
2085        CFtpChangeDirOpData *pData = static_cast<CFtpChangeDirOpData *>(m_pCurOpData);
2086
2087        wxString cmd;
2088        switch (pData->opState)
2089        {
2090        case cwd_pwd:
2091        case cwd_pwd_cwd:
2092        case cwd_pwd_subdir:
2093                cmd = _T("PWD");
2094                break;
2095        case cwd_cwd:
2096                if (pData->tryMkdOnFail && !pData->holdsLock)
2097                {
2098                        if (IsLocked(lock_mkdir, pData->path))
2099                        {
2100                                // Some other engine is already creating this directory or
2101                                // performing an action that will lead to its creation
2102                                pData->tryMkdOnFail = false;
2103                        }
2104                        if (!TryLockCache(lock_mkdir, pData->path))
2105                                return FZ_REPLY_WOULDBLOCK;
2106                }
2107                cmd = _T("CWD ") + pData->path.GetPath();
2108                m_CurrentPath.clear();
2109                break;
2110        case cwd_cwd_subdir:
2111                if (pData->subDir.empty())
2112                {
2113                        ResetOperation(FZ_REPLY_INTERNALERROR);
2114                        return FZ_REPLY_ERROR;
2115                }
2116                else if (pData->subDir == _T("..") && !pData->tried_cdup)
2117                        cmd = _T("CDUP");
2118                else
2119                        cmd = _T("CWD ") + pData->path.FormatSubdir(pData->subDir);
2120                m_CurrentPath.clear();
2121                break;
2122        }
2123
2124        if (!cmd.empty())
2125                if (!SendCommand(cmd))
2126                        return FZ_REPLY_ERROR;
2127
2128        return FZ_REPLY_WOULDBLOCK;
2129}
2130
2131int CFtpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath,
2132                                                                        const wxString &remoteFile, bool download,
2133                                                                        const CFileTransferCommand::t_transferSettings& transferSettings)
2134{
2135        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::FileTransfer()"));
2136
2137        if (localFile.empty()) {
2138                if (!download)
2139                        ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
2140                else
2141                        ResetOperation(FZ_REPLY_SYNTAXERROR);
2142                return FZ_REPLY_ERROR;
2143        }
2144
2145        if (download) {
2146                wxString filename = remotePath.FormatFilename(remoteFile);
2147                LogMessage(MessageType::Status, _("Starting download of %s"), filename);
2148        }
2149        else {
2150                LogMessage(MessageType::Status, _("Starting upload of %s"), localFile);
2151        }
2152        if (m_pCurOpData)
2153        {
2154                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("deleting nonzero pData"));
2155                delete m_pCurOpData;
2156        }
2157
2158        CFtpFileTransferOpData *pData = new CFtpFileTransferOpData(download, localFile, remoteFile, remotePath);
2159        m_pCurOpData = pData;
2160
2161        pData->transferSettings = transferSettings;
2162        pData->binary = transferSettings.binary;
2163
2164        int64_t size;
2165        bool isLink;
2166        if (fz::local_filesys::get_file_info(fz::to_native(pData->localFile), isLink, &size, 0, 0) == fz::local_filesys::file)
2167                pData->localFileSize = size;
2168
2169        pData->opState = filetransfer_waitcwd;
2170
2171        if (pData->remotePath.GetType() == DEFAULT)
2172                pData->remotePath.SetType(m_pCurrentServer->GetType());
2173
2174        int res = ChangeDir(pData->remotePath);
2175        if (res != FZ_REPLY_OK)
2176                return res;
2177
2178        return ParseSubcommandResult(FZ_REPLY_OK);
2179}
2180
2181int CFtpControlSocket::FileTransferParseResponse()
2182{
2183        LogMessage(MessageType::Debug_Verbose, _T("FileTransferParseResponse()"));
2184
2185        if (!m_pCurOpData)
2186        {
2187                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2188                ResetOperation(FZ_REPLY_INTERNALERROR);
2189                return FZ_REPLY_ERROR;
2190        }
2191
2192        CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
2193        if (pData->opState == filetransfer_init)
2194                return FZ_REPLY_ERROR;
2195
2196        int code = GetReplyCode();
2197        bool error = false;
2198        switch (pData->opState)
2199        {
2200        case filetransfer_size:
2201                if (code != 2 && code != 3)
2202                {
2203                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == yes ||
2204                                !m_Response.Mid(4).CmpNoCase(_T("file not found")) ||
2205                                (pData->remotePath.FormatFilename(pData->remoteFile).Lower().Find(_T("file not found")) == -1 &&
2206                                 m_Response.Lower().Find(_T("file not found")) != -1))
2207                        {
2208                                // Server supports SIZE command but command failed. Most likely MDTM will fail as well, so
2209                                // skip it.
2210                                pData->opState = filetransfer_resumetest;
2211
2212                                int res = CheckOverwriteFile();
2213                                if (res != FZ_REPLY_OK)
2214                                        return res;
2215                        }
2216                        else
2217                                pData->opState = filetransfer_mdtm;
2218                }
2219                else
2220                {
2221                        pData->opState = filetransfer_mdtm;
2222                        if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 4)
2223                        {
2224                                if (CServerCapabilities::GetCapability(*m_pCurrentServer, size_command) == unknown)
2225                                        CServerCapabilities::SetCapability(*m_pCurrentServer, size_command, yes);
2226                                wxString str = m_Response.Mid(4);
2227                                wxFileOffset size = 0;
2228                                const wxChar *buf = str.c_str();
2229                                while (*buf)
2230                                {
2231                                        if (*buf < '0' || *buf > '9')
2232                                                break;
2233
2234                                        size *= 10;
2235                                        size += *buf - '0';
2236                                        ++buf;
2237                                }
2238                                pData->remoteFileSize = size;
2239                        }
2240                        else
2241                                LogMessage(MessageType::Debug_Info, _T("Invalid SIZE reply"));
2242                }
2243                break;
2244        case filetransfer_mdtm:
2245                pData->opState = filetransfer_resumetest;
2246                if (m_Response.Left(4) == _T("213 ") && m_Response.Length() > 16) {
2247                        pData->fileTime = fz::datetime(m_Response.Mid(4).ToStdWstring(), fz::datetime::utc);
2248                        if (pData->fileTime.empty()) {
2249                                pData->fileTime += fz::duration::from_minutes(m_pCurrentServer->GetTimezoneOffset());
2250                        }
2251                }
2252
2253                {
2254                        int res = CheckOverwriteFile();
2255                        if (res != FZ_REPLY_OK)
2256                                return res;
2257                }
2258
2259                break;
2260        case filetransfer_mfmt:
2261                ResetOperation(FZ_REPLY_OK);
2262                return FZ_REPLY_OK;
2263        default:
2264                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown op state"));
2265                error = true;
2266                break;
2267        }
2268
2269        if (error)
2270        {
2271                ResetOperation(FZ_REPLY_ERROR);
2272                return FZ_REPLY_ERROR;
2273        }
2274
2275        return SendNextCommand();
2276}
2277
2278int CFtpControlSocket::FileTransferSubcommandResult(int prevResult)
2279{
2280        LogMessage(MessageType::Debug_Verbose, _T("FileTransferSubcommandResult()"));
2281
2282        if (!m_pCurOpData) {
2283                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2284                ResetOperation(FZ_REPLY_INTERNALERROR);
2285                return FZ_REPLY_ERROR;
2286        }
2287
2288        CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
2289
2290        if (pData->opState == filetransfer_waitcwd) {
2291                if (prevResult == FZ_REPLY_OK) {
2292                        CDirentry entry;
2293                        bool dirDidExist;
2294                        bool matchedCase;
2295                        bool found = engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
2296                        if (!found) {
2297                                if (!dirDidExist)
2298                                        pData->opState = filetransfer_waitlist;
2299                                else if (pData->download &&
2300                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2301                                        CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2302                                {
2303                                        pData->opState = filetransfer_mdtm;
2304                                }
2305                                else
2306                                        pData->opState = filetransfer_resumetest;
2307                        }
2308                        else {
2309                                if (entry.is_unsure())
2310                                        pData->opState = filetransfer_waitlist;
2311                                else {
2312                                        if (matchedCase) {
2313                                                pData->remoteFileSize = entry.size;
2314                                                if (entry.has_date())
2315                                                        pData->fileTime = entry.time;
2316
2317                                                if (pData->download &&
2318                                                        !entry.has_time() &&
2319                                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2320                                                        CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2321                                                {
2322                                                        pData->opState = filetransfer_mdtm;
2323                                                }
2324                                                else
2325                                                        pData->opState = filetransfer_resumetest;
2326                                        }
2327                                        else
2328                                                pData->opState = filetransfer_size;
2329                                }
2330                        }
2331                        if (pData->opState == filetransfer_waitlist) {
2332                                int res = List(CServerPath(), _T(""), LIST_FLAG_REFRESH);
2333                                if (res != FZ_REPLY_OK)
2334                                        return res;
2335                                ResetOperation(FZ_REPLY_INTERNALERROR);
2336                                return FZ_REPLY_ERROR;
2337                        }
2338                        else if (pData->opState == filetransfer_resumetest) {
2339                                int res = CheckOverwriteFile();
2340                                if (res != FZ_REPLY_OK)
2341                                        return res;
2342                        }
2343                }
2344                else {
2345                        pData->tryAbsolutePath = true;
2346                        pData->opState = filetransfer_size;
2347                }
2348        }
2349        else if (pData->opState == filetransfer_waitlist) {
2350                if (prevResult == FZ_REPLY_OK) {
2351                        CDirentry entry;
2352                        bool dirDidExist;
2353                        bool matchedCase;
2354                        bool found = engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
2355                        if (!found) {
2356                                if (!dirDidExist)
2357                                        pData->opState = filetransfer_size;
2358                                else if (pData->download &&
2359                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2360                                        CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2361                                {
2362                                        pData->opState = filetransfer_mdtm;
2363                                }
2364                                else
2365                                        pData->opState = filetransfer_resumetest;
2366                        }
2367                        else {
2368                                if (matchedCase && !entry.is_unsure()) {
2369                                        pData->remoteFileSize = entry.size;
2370                                        if (entry.has_date())
2371                                                pData->fileTime = entry.time;
2372
2373                                        if (pData->download &&
2374                                                !entry.has_time() &&
2375                                                engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2376                                                CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2377                                        {
2378                                                pData->opState = filetransfer_mdtm;
2379                                        }
2380                                        else
2381                                                pData->opState = filetransfer_resumetest;
2382                                }
2383                                else
2384                                        pData->opState = filetransfer_size;
2385                        }
2386
2387                        if (pData->opState == filetransfer_resumetest) {
2388                                int res = CheckOverwriteFile();
2389                                if (res != FZ_REPLY_OK)
2390                                        return res;
2391                        }
2392                }
2393                else
2394                        pData->opState = filetransfer_size;
2395        }
2396        else if (pData->opState == filetransfer_waittransfer) {
2397                if (prevResult == FZ_REPLY_OK && engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS)) {
2398                        if (!pData->download &&
2399                                CServerCapabilities::GetCapability(*m_pCurrentServer, mfmt_command) == yes)
2400                        {
2401                                fz::datetime mtime = fz::local_filesys::get_modification_time(fz::to_native(pData->localFile));
2402                                if (mtime.empty()) {
2403                                        pData->fileTime = mtime;
2404                                        pData->opState = filetransfer_mfmt;
2405                                        return SendNextCommand();
2406                                }
2407                        }
2408                        else if (pData->download && pData->fileTime.empty()) {
2409                                delete pData->pIOThread;
2410                                pData->pIOThread = 0;
2411                                if (!fz::local_filesys::set_modification_time(fz::to_native(pData->localFile), pData->fileTime))
2412                                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Could not set modification time"));
2413                        }
2414                }
2415                ResetOperation(prevResult);
2416                return prevResult;
2417        }
2418        else if (pData->opState == filetransfer_waitresumetest) {
2419                if (prevResult != FZ_REPLY_OK) {
2420                        if (pData->transferEndReason == TransferEndReason::failed_resumetest) {
2421                                if (pData->localFileSize > ((wxFileOffset)1 << 32)) {
2422                                        CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, yes);
2423                                        LogMessage(MessageType::Error, _("Server does not support resume of files > 4GB."));
2424                                }
2425                                else {
2426                                        CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, yes);
2427                                        LogMessage(MessageType::Error, _("Server does not support resume of files > 2GB."));
2428                                }
2429
2430                                ResetOperation(prevResult | FZ_REPLY_CRITICALERROR);
2431                                return FZ_REPLY_ERROR;
2432                        }
2433                        else
2434                                ResetOperation(prevResult);
2435                        return prevResult;
2436                }
2437                if (pData->localFileSize > ((wxFileOffset)1 << 32))
2438                        CServerCapabilities::SetCapability(*m_pCurrentServer, resume4GBbug, no);
2439                else
2440                        CServerCapabilities::SetCapability(*m_pCurrentServer, resume2GBbug, no);
2441
2442                pData->opState = filetransfer_transfer;
2443        }
2444
2445        return SendNextCommand();
2446}
2447
2448int CFtpControlSocket::FileTransferSend()
2449{
2450        LogMessage(MessageType::Debug_Verbose, _T("FileTransferSend()"));
2451
2452        if (!m_pCurOpData) {
2453                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2454                ResetOperation(FZ_REPLY_INTERNALERROR);
2455                return FZ_REPLY_ERROR;
2456        }
2457
2458        CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
2459
2460        wxString cmd;
2461        switch (pData->opState)
2462        {
2463        case filetransfer_size:
2464                cmd = _T("SIZE ");
2465                cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
2466                break;
2467        case filetransfer_mdtm:
2468                cmd = _T("MDTM ");
2469                cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
2470                break;
2471        case filetransfer_resumetest:
2472        case filetransfer_transfer:
2473                if (m_pTransferSocket)
2474                {
2475                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Verbose, _T("m_pTransferSocket != 0"));
2476                        delete m_pTransferSocket;
2477                        m_pTransferSocket = 0;
2478                }
2479
2480                {
2481                        auto pFile = std::make_unique<fz::file>();
2482                        if (pData->download) {
2483                                // Be quiet
2484                                wxLogNull nullLog;
2485
2486                                wxFileOffset startOffset = 0;
2487
2488                                // Potentially racy
2489                                bool didExist = wxFile::Exists(pData->localFile);
2490
2491                                if (pData->resume) {
2492                                        if (!pFile->open(fz::to_native(pData->localFile), fz::file::writing, fz::file::existing)) {
2493                                                LogMessage(MessageType::Error, _("Failed to open \"%s\" for appending/writing"), pData->localFile);
2494                                                ResetOperation(FZ_REPLY_ERROR);
2495                                                return FZ_REPLY_ERROR;
2496                                        }
2497
2498                                        pData->fileDidExist = didExist;
2499
2500                                        startOffset = pFile->seek(0, fz::file::end);
2501
2502                                        if (startOffset == wxInvalidOffset) {
2503                                                LogMessage(MessageType::Error, _("Could not seek to the end of the file"));
2504                                                ResetOperation(FZ_REPLY_ERROR);
2505                                                return FZ_REPLY_ERROR;
2506                                        }
2507                                        pData->localFileSize = startOffset;
2508
2509                                        // Check resume capabilities
2510                                        if (pData->opState == filetransfer_resumetest) {
2511                                                int res = FileTransferTestResumeCapability();
2512                                                if ((res & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED) {
2513                                                        // Server does not support resume but remote and local filesizes are equal
2514                                                        return FZ_REPLY_OK;
2515                                                }
2516                                                if (res != FZ_REPLY_OK) {
2517                                                        return res;
2518                                                }
2519                                        }
2520                                }
2521                                else {
2522                                        CreateLocalDir(pData->localFile);
2523
2524                                        if (!pFile->open(fz::to_native(pData->localFile), fz::file::writing, fz::file::empty)) {
2525                                                LogMessage(MessageType::Error, _("Failed to open \"%s\" for writing"), pData->localFile);
2526                                                ResetOperation(FZ_REPLY_ERROR);
2527                                                return FZ_REPLY_ERROR;
2528                                        }
2529
2530                                        pData->fileDidExist = didExist;
2531                                        pData->localFileSize = 0;
2532                                }
2533
2534                                if (pData->resume)
2535                                        pData->resumeOffset = pData->localFileSize;
2536                                else
2537                                        pData->resumeOffset = 0;
2538
2539                                engine_.transfer_status_.Init(pData->remoteFileSize, startOffset, false);
2540
2541                                if (engine_.GetOptions().GetOptionVal(OPTION_PREALLOCATE_SPACE)) {
2542                                        // Try to preallocate the file in order to reduce fragmentation
2543                                        int64_t sizeToPreallocate = pData->remoteFileSize - startOffset;
2544                                        if (sizeToPreallocate > 0) {
2545                                                LogMessage(MessageType::Debug_Info, _T("Preallocating %") + wxString(wxFileOffsetFmtSpec) + _T("d bytes for the file \"%s\""), sizeToPreallocate, pData->localFile);
2546                                                auto oldPos = pFile->seek(0, fz::file::current);
2547                                                if (oldPos >= 0) {
2548                                                        if (pFile->seek(sizeToPreallocate, fz::file::end) == pData->remoteFileSize) {
2549                                                                if (!pFile->truncate())
2550                                                                        LogMessage(MessageType::Debug_Warning, _T("Could not preallocate the file"));
2551                                                        }
2552                                                        pFile->seek(oldPos, fz::file::begin);
2553                                                }
2554                                        }
2555                                }
2556                        }
2557                        else {
2558                                if (!pFile->open(fz::to_native(pData->localFile), fz::file::reading)) {
2559                                        LogMessage(MessageType::Error, _("Failed to open \"%s\" for reading"), pData->localFile);
2560                                        ResetOperation(FZ_REPLY_ERROR);
2561                                        return FZ_REPLY_ERROR;
2562                                }
2563
2564                                wxFileOffset startOffset;
2565                                if (pData->resume) {
2566                                        if (pData->remoteFileSize > 0) {
2567                                                startOffset = pData->remoteFileSize;
2568
2569                                                if (pData->localFileSize < 0) {
2570                                                        auto s = pFile->size();
2571                                                        if (s >= 0) {
2572                                                                pData->localFileSize = s;
2573                                                        }
2574                                                }
2575
2576                                                if (startOffset == pData->localFileSize && pData->binary) {
2577                                                        LogMessage(MessageType::Debug_Info, _T("No need to resume, remote file size matches local file size."));
2578
2579                                                        if (engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2580                                                                CServerCapabilities::GetCapability(*m_pCurrentServer, mfmt_command) == yes)
2581                                                        {
2582                                                                fz::datetime mtime = fz::local_filesys::get_modification_time(fz::to_native(pData->localFile));
2583                                                                if (mtime.empty()) {
2584                                                                        pData->fileTime = mtime;
2585                                                                        pData->opState = filetransfer_mfmt;
2586                                                                        return SendNextCommand();
2587                                                                }
2588                                                        }
2589                                                        ResetOperation(FZ_REPLY_OK);
2590                                                        return FZ_REPLY_OK;
2591                                                }
2592
2593                                                // Assume native 64 bit type exists
2594                                                if (pFile->seek(startOffset, fz::file::begin) == wxInvalidOffset) {
2595                                                        wxString const s = std::to_wstring(startOffset);
2596                                                        LogMessage(MessageType::Error, _("Could not seek to offset %s within file"), s);
2597                                                        ResetOperation(FZ_REPLY_ERROR);
2598                                                        return FZ_REPLY_ERROR;
2599                                                }
2600                                        }
2601                                        else
2602                                                startOffset = 0;
2603                                }
2604                                else
2605                                        startOffset = 0;
2606
2607                                if (CServerCapabilities::GetCapability(*m_pCurrentServer, rest_stream) == yes) {
2608                                        // Use REST + STOR if resuming
2609                                        pData->resumeOffset = startOffset;
2610                                }
2611                                else {
2612                                        // Play it safe, use APPE if resuming
2613                                        pData->resumeOffset = 0;
2614                                }
2615
2616                                auto len = pFile->size();
2617                                engine_.transfer_status_.Init(len, startOffset, false);
2618                        }
2619                        pData->pIOThread = new CIOThread;
2620                        if (!pData->pIOThread->Create(std::move(pFile), !pData->download, pData->binary)) {
2621                                // CIOThread will delete pFile
2622                                delete pData->pIOThread;
2623                                pData->pIOThread = 0;
2624                                LogMessage(MessageType::Error, _("Could not spawn IO thread"));
2625                                ResetOperation(FZ_REPLY_ERROR);
2626                                return FZ_REPLY_ERROR;
2627                        }
2628                }
2629
2630                m_pTransferSocket = new CTransferSocket(engine_, *this, pData->download ? TransferMode::download : TransferMode::upload);
2631                m_pTransferSocket->m_binaryMode = pData->transferSettings.binary;
2632                m_pTransferSocket->SetIOThread(pData->pIOThread);
2633
2634                if (pData->download)
2635                        cmd = _T("RETR ");
2636                else if (pData->resume) {
2637                        if (CServerCapabilities::GetCapability(*m_pCurrentServer, rest_stream) == yes)
2638                                cmd = _T("STOR "); // In this case REST gets sent since resume offset was set earlier
2639                        else {
2640                                wxASSERT(pData->resumeOffset == 0);
2641                                cmd = _T("APPE ");
2642                        }
2643                }
2644                else
2645                        cmd = _T("STOR ");
2646                cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
2647
2648                pData->opState = filetransfer_waittransfer;
2649                return Transfer(cmd, pData);
2650        case filetransfer_mfmt:
2651                {
2652                        cmd = _T("MFMT ");
2653                        cmd += pData->fileTime.format(_T("%Y%m%d%H%M%S "), fz::datetime::utc);
2654                        cmd += pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath);
2655
2656                        break;
2657                }
2658        default:
2659                LogMessage(MessageType::Debug_Warning, _T("Unhandled opState: %d"), pData->opState);
2660                ResetOperation(FZ_REPLY_ERROR);
2661                return FZ_REPLY_ERROR;
2662        }
2663
2664        if (!cmd.empty())
2665                if (!SendCommand(cmd))
2666                        return FZ_REPLY_ERROR;
2667
2668        return FZ_REPLY_WOULDBLOCK;
2669}
2670
2671void CFtpControlSocket::TransferEnd()
2672{
2673        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::TransferEnd()"));
2674
2675        // If m_pTransferSocket is zero, the message was sent by the previous command.
2676        // We can safely ignore it.
2677        // It does not cause problems, since before creating the next transfer socket, other
2678        // messages which were added to the queue later than this one will be processed first.
2679        if (!m_pCurOpData || !m_pTransferSocket || GetCurrentCommandId() != Command::rawtransfer)
2680        {
2681                LogMessage(MessageType::Debug_Verbose, _T("Call to TransferEnd at unusual time, ignoring"));
2682                return;
2683        }
2684
2685        TransferEndReason reason = m_pTransferSocket->GetTransferEndreason();
2686        if (reason == TransferEndReason::none)
2687        {
2688                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Call to TransferEnd at unusual time"));
2689                return;
2690        }
2691
2692        if (reason == TransferEndReason::successful)
2693                SetAlive();
2694
2695        CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
2696        if (pData->pOldData->transferEndReason == TransferEndReason::successful)
2697                pData->pOldData->transferEndReason = reason;
2698
2699        switch (m_pCurOpData->opState)
2700        {
2701        case rawtransfer_transfer:
2702                pData->opState = rawtransfer_waittransferpre;
2703                break;
2704        case rawtransfer_waitfinish:
2705                pData->opState = rawtransfer_waittransfer;
2706                break;
2707        case rawtransfer_waitsocket:
2708                ResetOperation((reason == TransferEndReason::successful) ? FZ_REPLY_OK : FZ_REPLY_ERROR);
2709                break;
2710        default:
2711                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("TransferEnd at unusual op state, ignoring"));
2712                break;
2713        }
2714}
2715
2716bool CFtpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification)
2717{
2718        if (m_pCurOpData)
2719        {
2720                if (!m_pCurOpData->waitForAsyncRequest)
2721                {
2722                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID());
2723                        return false;
2724                }
2725                m_pCurOpData->waitForAsyncRequest = false;
2726        }
2727
2728        const enum RequestId requestId = pNotification->GetRequestID();
2729        switch (requestId)
2730        {
2731        case reqId_fileexists:
2732                {
2733                        if (!m_pCurOpData || m_pCurOpData->opId != Command::transfer)
2734                        {
2735                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %f"), pNotification->GetRequestID());
2736                                return false;
2737                        }
2738
2739                        CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
2740
2741                        CFileExistsNotification *pFileExistsNotification = static_cast<CFileExistsNotification *>(pNotification);
2742                        switch (pFileExistsNotification->overwriteAction)
2743                        {
2744                        case CFileExistsNotification::rename:
2745                                if (pData->download) {
2746                                        wxFileName fn = pData->localFile;
2747                                        fn.SetFullName(pFileExistsNotification->newName);
2748                                        pData->localFile = fn.GetFullPath();
2749
2750                                        int64_t size;
2751                                        bool isLink;
2752                                        if (fz::local_filesys::get_file_info(fz::to_native(pData->localFile), isLink, &size, 0, 0) == fz::local_filesys::file)
2753                                                pData->localFileSize = size;
2754                                        else
2755                                                pData->localFileSize = -1;
2756
2757                                        if (CheckOverwriteFile() == FZ_REPLY_OK)
2758                                                SendNextCommand();
2759                                }
2760                                else {
2761                                        pData->remoteFile = pFileExistsNotification->newName;
2762                                        pData->remoteFileSize = -1;
2763                                        pData->fileTime = fz::datetime();
2764
2765                                        CDirentry entry;
2766                                        bool dir_did_exist;
2767                                        bool matched_case;
2768                                        if (!engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dir_did_exist, matched_case) ||
2769                                                !matched_case)
2770                                        {
2771                                                if (engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2772                                                        CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2773                                                {
2774                                                        pData->opState = filetransfer_mdtm;
2775                                                }
2776                                        }
2777                                        else // found and matched case
2778                                        {
2779                                                pData->remoteFileSize = entry.size;
2780                                                if (entry.has_date())
2781                                                        pData->fileTime = entry.time;
2782
2783                                                if (pData->download &&
2784                                                        !entry.has_time() &&
2785                                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS) &&
2786                                                        CServerCapabilities::GetCapability(*m_pCurrentServer, mdtm_command) == yes)
2787                                                {
2788                                                        pData->opState = filetransfer_mdtm;
2789                                                }
2790                                                else {
2791                                                        if (CheckOverwriteFile() != FZ_REPLY_OK)
2792                                                                break;
2793                                                }
2794                                        }
2795                                        SendNextCommand();
2796                                }
2797                                break;
2798                        default:
2799                                return SetFileExistsAction(pFileExistsNotification);
2800                        }
2801                }
2802                break;
2803        case reqId_interactiveLogin:
2804                {
2805                        if (!m_pCurOpData || m_pCurOpData->opId != Command::connect)
2806                        {
2807                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID());
2808                                return false;
2809                        }
2810
2811                        CFtpLogonOpData* pData = static_cast<CFtpLogonOpData*>(m_pCurOpData);
2812
2813                        CInteractiveLoginNotification *pInteractiveLoginNotification = static_cast<CInteractiveLoginNotification *>(pNotification);
2814                        if (!pInteractiveLoginNotification->passwordSet)
2815                        {
2816                                ResetOperation(FZ_REPLY_CANCELED);
2817                                return false;
2818                        }
2819                        m_pCurrentServer->SetUser(m_pCurrentServer->GetUser(), pInteractiveLoginNotification->server.GetPass());
2820                        pData->gotPassword = true;
2821                        SendNextCommand();
2822                }
2823                break;
2824        case reqId_certificate:
2825                {
2826                        if (!m_pTlsSocket || m_pTlsSocket->GetState() != CTlsSocket::TlsState::verifycert) {
2827                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %d"), pNotification->GetRequestID());
2828                                return false;
2829                        }
2830
2831                        CCertificateNotification* pCertificateNotification = static_cast<CCertificateNotification *>(pNotification);
2832                        m_pTlsSocket->TrustCurrentCert(pCertificateNotification->m_trusted);
2833
2834                        if (!pCertificateNotification->m_trusted) {
2835                                DoClose(FZ_REPLY_CRITICALERROR);
2836                                return false;
2837                        }
2838
2839                        if (m_pCurOpData && m_pCurOpData->opId == Command::connect &&
2840                                m_pCurOpData->opState == LOGON_AUTH_WAIT)
2841                        {
2842                                m_pCurOpData->opState = LOGON_LOGON;
2843                        }
2844                }
2845                break;
2846        default:
2847                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown request %d"), pNotification->GetRequestID());
2848                ResetOperation(FZ_REPLY_INTERNALERROR);
2849                return false;
2850        }
2851
2852        return true;
2853}
2854
2855class CRawCommandOpData : public COpData
2856{
2857public:
2858        CRawCommandOpData(const wxString& command)
2859                : COpData(Command::raw)
2860        {
2861                m_command = command;
2862        }
2863
2864        wxString m_command;
2865};
2866
2867int CFtpControlSocket::RawCommand(const wxString& command)
2868{
2869        wxASSERT(!command.empty());
2870
2871        m_pCurOpData = new CRawCommandOpData(command);
2872
2873        return SendNextCommand();
2874}
2875
2876int CFtpControlSocket::RawCommandSend()
2877{
2878        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RawCommandSend"));
2879
2880        if (!m_pCurOpData)
2881        {
2882                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2883                ResetOperation(FZ_REPLY_INTERNALERROR);
2884                return FZ_REPLY_ERROR;
2885        }
2886
2887        engine_.GetDirectoryCache().InvalidateServer(*m_pCurrentServer);
2888        engine_.GetPathCache().InvalidateServer(*m_pCurrentServer);
2889        m_CurrentPath.clear();
2890
2891        m_lastTypeBinary = -1;
2892
2893        CRawCommandOpData *pData = static_cast<CRawCommandOpData *>(m_pCurOpData);
2894
2895        if (!SendCommand(pData->m_command, false, false))
2896                return FZ_REPLY_ERROR;
2897
2898        return FZ_REPLY_WOULDBLOCK;
2899}
2900
2901int CFtpControlSocket::RawCommandParseResponse()
2902{
2903        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RawCommandParseResponse"));
2904
2905        int code = GetReplyCode();
2906        if (code == 2 || code == 3)
2907        {
2908                ResetOperation(FZ_REPLY_OK);
2909                return FZ_REPLY_OK;
2910        }
2911        else
2912        {
2913                ResetOperation(FZ_REPLY_ERROR);
2914                return FZ_REPLY_ERROR;
2915        }
2916}
2917
2918int CFtpControlSocket::Delete(const CServerPath& path, std::deque<wxString>&& files)
2919{
2920        wxASSERT(!m_pCurOpData);
2921        CFtpDeleteOpData *pData = new CFtpDeleteOpData();
2922        m_pCurOpData = pData;
2923        pData->path = path;
2924        pData->files = files;
2925        pData->omitPath = true;
2926
2927        int res = ChangeDir(pData->path);
2928        if (res != FZ_REPLY_OK)
2929                return res;
2930
2931        // CFileZillaEnginePrivate should have checked this already
2932        wxASSERT(!files.empty());
2933
2934        return SendNextCommand();
2935}
2936
2937int CFtpControlSocket::DeleteSubcommandResult(int prevResult)
2938{
2939        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::DeleteSubcommandResult()"));
2940
2941        if (!m_pCurOpData) {
2942                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2943                ResetOperation(FZ_REPLY_INTERNALERROR);
2944                return FZ_REPLY_ERROR;
2945        }
2946
2947        CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);
2948
2949        if (prevResult != FZ_REPLY_OK)
2950                pData->omitPath = false;
2951
2952        return SendNextCommand();
2953}
2954
2955int CFtpControlSocket::DeleteSend()
2956{
2957        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::DeleteSend"));
2958
2959        if (!m_pCurOpData) {
2960                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2961                ResetOperation(FZ_REPLY_INTERNALERROR);
2962                return FZ_REPLY_ERROR;
2963        }
2964        CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);
2965
2966        const wxString& file = pData->files.front();
2967        if (file.empty()) {
2968                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty filename"));
2969                ResetOperation(FZ_REPLY_INTERNALERROR);
2970                return FZ_REPLY_ERROR;
2971        }
2972
2973        wxString filename = pData->path.FormatFilename(file, pData->omitPath);
2974        if (filename.empty()) {
2975                LogMessage(MessageType::Error, _("Filename cannot be constructed for directory %s and filename %s"), pData->path.GetPath(), file);
2976                ResetOperation(FZ_REPLY_ERROR);
2977                return FZ_REPLY_ERROR;
2978        }
2979
2980        if (!pData->m_time.empty())
2981                pData->m_time = fz::datetime::now();
2982
2983        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->path, file);
2984
2985        if (!SendCommand(_T("DELE ") + filename))
2986                return FZ_REPLY_ERROR;
2987
2988        return FZ_REPLY_WOULDBLOCK;
2989}
2990
2991int CFtpControlSocket::DeleteParseResponse()
2992{
2993        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::DeleteParseResponse()"));
2994
2995        if (!m_pCurOpData) {
2996                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2997                ResetOperation(FZ_REPLY_INTERNALERROR);
2998                return FZ_REPLY_ERROR;
2999        }
3000
3001        CFtpDeleteOpData *pData = static_cast<CFtpDeleteOpData *>(m_pCurOpData);
3002
3003        int code = GetReplyCode();
3004        if (code != 2 && code != 3)
3005                pData->m_deleteFailed = true;
3006        else {
3007                const wxString& file = pData->files.front();
3008
3009                engine_.GetDirectoryCache().RemoveFile(*m_pCurrentServer, pData->path, file);
3010
3011                fz::datetime now = fz::datetime::now();
3012                if (now.empty() && pData->m_time.empty() && (now - pData->m_time).get_seconds() >= 1) {
3013                        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
3014                        pData->m_time = now;
3015                        pData->m_needSendListing = false;
3016                }
3017                else
3018                        pData->m_needSendListing = true;
3019        }
3020
3021        pData->files.pop_front();
3022
3023        if (!pData->files.empty())
3024                return SendNextCommand();
3025
3026        return ResetOperation(pData->m_deleteFailed ? FZ_REPLY_ERROR : FZ_REPLY_OK);
3027}
3028
3029class CFtpRemoveDirOpData : public COpData
3030{
3031public:
3032        CFtpRemoveDirOpData()
3033                : COpData(Command::removedir)
3034                , omitPath()
3035        {
3036        }
3037
3038        virtual ~CFtpRemoveDirOpData() {}
3039
3040        CServerPath path;
3041        CServerPath fullPath;
3042        wxString subDir;
3043        bool omitPath;
3044};
3045
3046int CFtpControlSocket::RemoveDir(const CServerPath& path, const wxString& subDir)
3047{
3048        wxASSERT(!m_pCurOpData);
3049        CFtpRemoveDirOpData *pData = new CFtpRemoveDirOpData();
3050        m_pCurOpData = pData;
3051        pData->path = path;
3052        pData->subDir = subDir;
3053        pData->omitPath = true;
3054        pData->fullPath = path;
3055
3056        if (!pData->fullPath.AddSegment(subDir))
3057        {
3058                LogMessage(MessageType::Error, _("Path cannot be constructed for directory %s and subdir %s"), path.GetPath(), subDir);
3059                ResetOperation(FZ_REPLY_ERROR);
3060                return FZ_REPLY_ERROR;
3061        }
3062
3063        int res = ChangeDir(pData->path);
3064        if (res != FZ_REPLY_OK)
3065                return res;
3066
3067        return SendNextCommand();
3068}
3069
3070int CFtpControlSocket::RemoveDirSubcommandResult(int prevResult)
3071{
3072        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RemoveDirSubcommandResult()"));
3073
3074        if (!m_pCurOpData)
3075        {
3076                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3077                ResetOperation(FZ_REPLY_INTERNALERROR);
3078                return FZ_REPLY_ERROR;
3079        }
3080
3081        CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData);
3082
3083        if (prevResult != FZ_REPLY_OK)
3084                pData->omitPath = false;
3085        else
3086                pData->path = m_CurrentPath;
3087
3088        return SendNextCommand();
3089}
3090
3091int CFtpControlSocket::RemoveDirSend()
3092{
3093        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RemoveDirSend()"));
3094
3095        if (!m_pCurOpData)
3096        {
3097                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3098                ResetOperation(FZ_REPLY_INTERNALERROR);
3099                return FZ_REPLY_ERROR;
3100        }
3101
3102        CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData);
3103
3104        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->path, pData->subDir);
3105
3106        CServerPath path(engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->path, pData->subDir));
3107        if (path.empty())
3108        {
3109                path = pData->path;
3110                path.AddSegment(pData->subDir);
3111        }
3112        engine_.InvalidateCurrentWorkingDirs(path);
3113
3114        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->path, pData->subDir);
3115
3116        if (pData->omitPath)
3117        {
3118                if (!SendCommand(_T("RMD ") + pData->subDir))
3119                        return FZ_REPLY_ERROR;
3120        }
3121        else
3122                if (!SendCommand(_T("RMD ") + pData->fullPath.GetPath()))
3123                        return FZ_REPLY_ERROR;
3124
3125        return FZ_REPLY_WOULDBLOCK;
3126}
3127
3128int CFtpControlSocket::RemoveDirParseResponse()
3129{
3130        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RemoveDirParseResponse()"));
3131
3132        if (!m_pCurOpData)
3133        {
3134                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3135                ResetOperation(FZ_REPLY_INTERNALERROR);
3136                return FZ_REPLY_ERROR;
3137        }
3138
3139        CFtpRemoveDirOpData *pData = static_cast<CFtpRemoveDirOpData *>(m_pCurOpData);
3140
3141        int code = GetReplyCode();
3142        if (code != 2 && code != 3)
3143        {
3144                ResetOperation(FZ_REPLY_ERROR);
3145                return FZ_REPLY_ERROR;
3146        }
3147
3148        engine_.GetDirectoryCache().RemoveDir(*m_pCurrentServer, pData->path, pData->subDir, engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->path, pData->subDir));
3149        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
3150
3151        return ResetOperation(FZ_REPLY_OK);
3152}
3153
3154enum mkdStates
3155{
3156        mkd_init = 0,
3157        mkd_findparent,
3158        mkd_mkdsub,
3159        mkd_cwdsub,
3160        mkd_tryfull
3161};
3162
3163int CFtpControlSocket::Mkdir(const CServerPath& path)
3164{
3165        /* Directory creation works like this: First find a parent directory into
3166         * which we can CWD, then create the subdirs one by one. If either part
3167         * fails, try MKD with the full path directly.
3168         */
3169
3170        if (!m_pCurOpData)
3171                LogMessage(MessageType::Status, _("Creating directory '%s'..."), path.GetPath());
3172
3173        CMkdirOpData *pData = new CMkdirOpData;
3174        pData->path = path;
3175
3176        if (!m_CurrentPath.empty()) {
3177                // Unless the server is broken, a directory already exists if current directory is a subdir of it.
3178                if (m_CurrentPath == path || m_CurrentPath.IsSubdirOf(path, false)) {
3179                        delete pData;
3180                        return FZ_REPLY_OK;
3181                }
3182
3183                if (m_CurrentPath.IsParentOf(path, false))
3184                        pData->commonParent = m_CurrentPath;
3185                else
3186                        pData->commonParent = path.GetCommonParent(m_CurrentPath);
3187        }
3188
3189        if (!path.HasParent())
3190                pData->opState = mkd_tryfull;
3191        else {
3192                pData->currentPath = path.GetParent();
3193                pData->segments.push_back(path.GetLastSegment());
3194
3195                if (pData->currentPath == m_CurrentPath)
3196                        pData->opState = mkd_mkdsub;
3197                else
3198                        pData->opState = mkd_findparent;
3199        }
3200
3201        pData->pNextOpData = m_pCurOpData;
3202        m_pCurOpData = pData;
3203
3204        return SendNextCommand();
3205}
3206
3207int CFtpControlSocket::MkdirParseResponse()
3208{
3209        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::MkdirParseResonse"));
3210
3211        if (!m_pCurOpData) {
3212                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3213                ResetOperation(FZ_REPLY_INTERNALERROR);
3214                return FZ_REPLY_ERROR;
3215        }
3216
3217        CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
3218        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
3219
3220        int code = GetReplyCode();
3221        bool error = false;
3222        switch (pData->opState) {
3223        case mkd_findparent:
3224                if (code == 2 || code == 3) {
3225                        m_CurrentPath = pData->currentPath;
3226                        pData->opState = mkd_mkdsub;
3227                }
3228                else if (pData->currentPath == pData->commonParent)
3229                        pData->opState = mkd_tryfull;
3230                else if (pData->currentPath.HasParent()) {
3231                        const CServerPath& parent = pData->currentPath.GetParent();
3232                        pData->segments.push_back(pData->currentPath.GetLastSegment());
3233                        pData->currentPath = parent;
3234                }
3235                else
3236                        pData->opState = mkd_tryfull;
3237                break;
3238        case mkd_mkdsub:
3239                if (code != 2 && code != 3) {
3240                        // Don't fall back to using the full path if the error message
3241                        // is "already exists".
3242                        // Case 1: Full response a known "already exists" message.
3243                        // Case 2: Substrng of response contains "already exists". pData->path may not
3244                        //         contain this substring as the path might be returned in the reply.
3245                        // Case 3: Substrng of response contains "file exists". pData->path may not
3246                        //         contain this substring as the path might be returned in the reply.
3247                        const wxString response = m_Response.Mid(4).Lower();
3248                        const wxString path = pData->path.GetPath().Lower();
3249                        if (response != _T("directory already exists") &&
3250                                (path.Find(_T("already exists")) != -1 ||
3251                                 response.Find(_T("already exists")) == -1) &&
3252                                (path.Find(_T("file exists")) != -1 ||
3253                                 response.Find(_T("file exists")) == -1)
3254                                )
3255                        {
3256                                pData->opState = mkd_tryfull;
3257                                break;
3258                        }
3259                }
3260
3261                {
3262                        if (pData->segments.empty()) {
3263                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("  pData->segments is empty"));
3264                                ResetOperation(FZ_REPLY_INTERNALERROR);
3265                                return FZ_REPLY_ERROR;
3266                        }
3267
3268                        // If entry did exist and is a file instead of a directory, report failure.
3269                        int result = FZ_REPLY_OK;
3270                        if (code != 2 && code != 3) {
3271                                CDirentry entry;
3272                                bool tmp;
3273                                if (engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->currentPath, pData->segments.back(), tmp, tmp) && !entry.is_dir()) {
3274                                        result = FZ_REPLY_ERROR;
3275                                }
3276                        }
3277
3278                        engine_.GetDirectoryCache().UpdateFile(*m_pCurrentServer, pData->currentPath, pData->segments.back(), true, CDirectoryCache::dir);
3279                        engine_.SendDirectoryListingNotification(pData->currentPath, false, true, false);
3280
3281                        pData->currentPath.AddSegment(pData->segments.back());
3282                        pData->segments.pop_back();
3283
3284                        if (pData->segments.empty() || result != FZ_REPLY_OK) {
3285                                ResetOperation(result);
3286                                return result;
3287                        }
3288                        else
3289                                pData->opState = mkd_cwdsub;
3290                }
3291                break;
3292        case mkd_cwdsub:
3293                if (code == 2 || code == 3) {
3294                        m_CurrentPath = pData->currentPath;
3295                        pData->opState = mkd_mkdsub;
3296                }
3297                else
3298                        pData->opState = mkd_tryfull;
3299                break;
3300        case mkd_tryfull:
3301                if (code != 2 && code != 3)
3302                        error = true;
3303                else {
3304                        ResetOperation(FZ_REPLY_OK);
3305                        return FZ_REPLY_OK;
3306                }
3307                break;
3308        default:
3309                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
3310                ResetOperation(FZ_REPLY_INTERNALERROR);
3311                return FZ_REPLY_ERROR;
3312        }
3313
3314        if (error) {
3315                ResetOperation(FZ_REPLY_ERROR);
3316                return FZ_REPLY_ERROR;
3317        }
3318
3319        return SendNextCommand();
3320}
3321
3322int CFtpControlSocket::MkdirSend()
3323{
3324        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::MkdirSend"));
3325
3326        if (!m_pCurOpData) {
3327                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3328                ResetOperation(FZ_REPLY_INTERNALERROR);
3329                return FZ_REPLY_ERROR;
3330        }
3331
3332        CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
3333        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
3334
3335        if (!pData->holdsLock) {
3336                if (!TryLockCache(lock_mkdir, pData->path))
3337                        return FZ_REPLY_WOULDBLOCK;
3338        }
3339
3340        bool res;
3341        switch (pData->opState)
3342        {
3343        case mkd_findparent:
3344        case mkd_cwdsub:
3345                m_CurrentPath.clear();
3346                res = SendCommand(_T("CWD ") + pData->currentPath.GetPath());
3347                break;
3348        case mkd_mkdsub:
3349                res = SendCommand(_T("MKD ") + pData->segments.back());
3350                break;
3351        case mkd_tryfull:
3352                res = SendCommand(_T("MKD ") + pData->path.GetPath());
3353                break;
3354        default:
3355                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
3356                ResetOperation(FZ_REPLY_INTERNALERROR);
3357                return FZ_REPLY_ERROR;
3358        }
3359
3360        if (!res)
3361                return FZ_REPLY_ERROR;
3362
3363        return FZ_REPLY_WOULDBLOCK;
3364}
3365
3366class CFtpRenameOpData : public COpData
3367{
3368public:
3369        CFtpRenameOpData(const CRenameCommand& command)
3370                : COpData(Command::rename), m_cmd(command)
3371        {
3372                m_useAbsolute = false;
3373        }
3374
3375        virtual ~CFtpRenameOpData() {}
3376
3377        CRenameCommand m_cmd;
3378        bool m_useAbsolute;
3379};
3380
3381enum renameStates
3382{
3383        rename_init = 0,
3384        rename_rnfrom,
3385        rename_rnto
3386};
3387
3388int CFtpControlSocket::Rename(const CRenameCommand& command)
3389{
3390        if (m_pCurOpData)
3391        {
3392                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData not empty"));
3393                ResetOperation(FZ_REPLY_INTERNALERROR);
3394                return FZ_REPLY_ERROR;
3395        }
3396
3397        LogMessage(MessageType::Status, _("Renaming '%s' to '%s'"), command.GetFromPath().FormatFilename(command.GetFromFile()), command.GetToPath().FormatFilename(command.GetToFile()));
3398
3399        CFtpRenameOpData *pData = new CFtpRenameOpData(command);
3400        pData->opState = rename_rnfrom;
3401        m_pCurOpData = pData;
3402
3403        int res = ChangeDir(command.GetFromPath());
3404        if (res != FZ_REPLY_OK)
3405                return res;
3406
3407        return SendNextCommand();
3408}
3409
3410int CFtpControlSocket::RenameParseResponse()
3411{
3412        CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData);
3413        if (!pData)
3414        {
3415                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3416                ResetOperation(FZ_REPLY_INTERNALERROR);
3417                return FZ_REPLY_ERROR;
3418        }
3419
3420        int code = GetReplyCode();
3421        if (code != 2 && code != 3)
3422        {
3423                ResetOperation(FZ_REPLY_ERROR);
3424                return FZ_REPLY_ERROR;
3425        }
3426
3427        if (pData->opState == rename_rnfrom)
3428                pData->opState = rename_rnto;
3429        else
3430        {
3431                const CServerPath& fromPath = pData->m_cmd.GetFromPath();
3432                const CServerPath& toPath = pData->m_cmd.GetToPath();
3433                engine_.GetDirectoryCache().Rename(*m_pCurrentServer, fromPath, pData->m_cmd.GetFromFile(), toPath, pData->m_cmd.GetToFile());
3434
3435                engine_.SendDirectoryListingNotification(fromPath, false, true, false);
3436                if (fromPath != toPath)
3437                        engine_.SendDirectoryListingNotification(toPath, false, true, false);
3438
3439                ResetOperation(FZ_REPLY_OK);
3440                return FZ_REPLY_OK;
3441        }
3442
3443        return SendNextCommand();
3444}
3445
3446int CFtpControlSocket::RenameSubcommandResult(int prevResult)
3447{
3448        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RenameSubcommandResult()"));
3449
3450        CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData);
3451        if (!pData)
3452        {
3453                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3454                ResetOperation(FZ_REPLY_INTERNALERROR);
3455                return FZ_REPLY_ERROR;
3456        }
3457
3458        if (prevResult != FZ_REPLY_OK)
3459                pData->m_useAbsolute = true;
3460
3461        return SendNextCommand();
3462}
3463
3464int CFtpControlSocket::RenameSend()
3465{
3466        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::RenameSend()"));
3467
3468        CFtpRenameOpData *pData = static_cast<CFtpRenameOpData*>(m_pCurOpData);
3469        if (!pData)
3470        {
3471                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3472                ResetOperation(FZ_REPLY_INTERNALERROR);
3473                return FZ_REPLY_ERROR;
3474        }
3475
3476        bool res;
3477        switch (pData->opState)
3478        {
3479        case rename_rnfrom:
3480                res = SendCommand(_T("RNFR ") + pData->m_cmd.GetFromPath().FormatFilename(pData->m_cmd.GetFromFile(), !pData->m_useAbsolute));
3481                break;
3482        case rename_rnto:
3483                {
3484                        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
3485                        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());
3486
3487                        CServerPath path(engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile()));
3488                        if (path.empty()) {
3489                                path = pData->m_cmd.GetFromPath();
3490                                path.AddSegment(pData->m_cmd.GetFromFile());
3491                        }
3492                        engine_.InvalidateCurrentWorkingDirs(path);
3493
3494                        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
3495                        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());
3496
3497                        res = SendCommand(_T("RNTO ") + pData->m_cmd.GetToPath().FormatFilename(pData->m_cmd.GetToFile(), !pData->m_useAbsolute && pData->m_cmd.GetFromPath() == pData->m_cmd.GetToPath()));
3498                        break;
3499                }
3500        default:
3501                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
3502                ResetOperation(FZ_REPLY_INTERNALERROR);
3503                return FZ_REPLY_ERROR;
3504        }
3505
3506        if (!res)
3507        {
3508                ResetOperation(FZ_REPLY_ERROR);
3509                return FZ_REPLY_ERROR;
3510        }
3511
3512        return FZ_REPLY_WOULDBLOCK;
3513}
3514
3515class CFtpChmodOpData : public COpData
3516{
3517public:
3518        CFtpChmodOpData(const CChmodCommand& command)
3519                : COpData(Command::chmod), m_cmd(command)
3520        {
3521                m_useAbsolute = false;
3522        }
3523
3524        virtual ~CFtpChmodOpData() {}
3525
3526        CChmodCommand m_cmd;
3527        bool m_useAbsolute;
3528};
3529
3530enum chmodStates
3531{
3532        chmod_init = 0,
3533        chmod_chmod
3534};
3535
3536int CFtpControlSocket::Chmod(const CChmodCommand& command)
3537{
3538        if (m_pCurOpData)
3539        {
3540                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData not empty"));
3541                ResetOperation(FZ_REPLY_INTERNALERROR);
3542                return FZ_REPLY_ERROR;
3543        }
3544
3545        LogMessage(MessageType::Status, _("Set permissions of '%s' to '%s'"), command.GetPath().FormatFilename(command.GetFile()), command.GetPermission());
3546
3547        CFtpChmodOpData *pData = new CFtpChmodOpData(command);
3548        pData->opState = chmod_chmod;
3549        m_pCurOpData = pData;
3550
3551        int res = ChangeDir(command.GetPath());
3552        if (res != FZ_REPLY_OK)
3553                return res;
3554
3555        return SendNextCommand();
3556}
3557
3558int CFtpControlSocket::ChmodParseResponse()
3559{
3560        CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData);
3561        if (!pData) {
3562                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3563                ResetOperation(FZ_REPLY_INTERNALERROR);
3564                return FZ_REPLY_ERROR;
3565        }
3566
3567        int code = GetReplyCode();
3568        if (code != 2 && code != 3) {
3569                ResetOperation(FZ_REPLY_ERROR);
3570                return FZ_REPLY_ERROR;
3571        }
3572
3573        engine_.GetDirectoryCache().UpdateFile(*m_pCurrentServer, pData->m_cmd.GetPath(), pData->m_cmd.GetFile(), false, CDirectoryCache::unknown);
3574
3575        ResetOperation(FZ_REPLY_OK);
3576        return FZ_REPLY_OK;
3577}
3578
3579int CFtpControlSocket::ChmodSubcommandResult(int prevResult)
3580{
3581        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ChmodSubcommandResult()"));
3582
3583        CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData);
3584        if (!pData)
3585        {
3586                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3587                ResetOperation(FZ_REPLY_INTERNALERROR);
3588                return FZ_REPLY_ERROR;
3589        }
3590
3591        if (prevResult != FZ_REPLY_OK)
3592                pData->m_useAbsolute = true;
3593
3594        return SendNextCommand();
3595}
3596
3597int CFtpControlSocket::ChmodSend()
3598{
3599        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ChmodSend()"));
3600
3601        CFtpChmodOpData *pData = static_cast<CFtpChmodOpData*>(m_pCurOpData);
3602        if (!pData)
3603        {
3604                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
3605                ResetOperation(FZ_REPLY_INTERNALERROR);
3606                return FZ_REPLY_ERROR;
3607        }
3608
3609        bool res;
3610        switch (pData->opState)
3611        {
3612        case chmod_chmod:
3613                res = SendCommand(_T("SITE CHMOD ") + pData->m_cmd.GetPermission() + _T(" ") + pData->m_cmd.GetPath().FormatFilename(pData->m_cmd.GetFile(), !pData->m_useAbsolute));
3614                break;
3615        default:
3616                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
3617                ResetOperation(FZ_REPLY_INTERNALERROR);
3618                return FZ_REPLY_ERROR;
3619        }
3620
3621        if (!res)
3622        {
3623                ResetOperation(FZ_REPLY_ERROR);
3624                return FZ_REPLY_ERROR;
3625        }
3626
3627        return FZ_REPLY_WOULDBLOCK;
3628}
3629
3630bool CFtpControlSocket::IsMisleadingListResponse() const
3631{
3632        // Some servers are broken. Instead of an empty listing, some MVS servers
3633        // for example they return "550 no members found"
3634        // Other servers return "550 No files found."
3635
3636        if (!m_Response.CmpNoCase(_T("550 No members found.")))
3637                return true;
3638
3639        if (!m_Response.CmpNoCase(_T("550 No data sets found.")))
3640                return true;
3641
3642        if (!m_Response.CmpNoCase(_T("550 No files found.")))
3643                return true;
3644
3645        return false;
3646}
3647
3648bool CFtpControlSocket::ParseEpsvResponse(CRawTransferOpData* pData)
3649{
3650        int pos = m_Response.Find(_T("(|||"));
3651        if (pos == -1)
3652                return false;
3653
3654        int pos2 = m_Response.Mid(pos + 4).Find(_T("|)"));
3655        if (pos2 <= 0)
3656                return false;
3657
3658        wxString number = m_Response.Mid(pos + 4, pos2);
3659        unsigned long port;
3660        if (!number.ToULong(&port))
3661                 return false;
3662
3663        if (port == 0 || port > 65535)
3664           return false;
3665
3666        pData->port = port;
3667
3668        if (m_pProxyBackend) {
3669                pData->host = m_pCurrentServer->GetHost();
3670        }
3671        else {
3672                pData->host = m_pSocket->GetPeerIP();
3673        }
3674        return true;
3675}
3676
3677bool CFtpControlSocket::ParsePasvResponse(CRawTransferOpData* pData)
3678{
3679        // Validate ip address
3680        if (!m_pasvReplyRegex.IsValid()) {
3681                wxString digit = _T("0*[0-9]{1,3}");
3682                const wxChar* dot = _T(",");
3683                wxString exp = _T("( |\\()(") + digit + dot + digit + dot + digit + dot + digit + dot + digit + dot + digit + _T(")( |\\)|$)");
3684                m_pasvReplyRegex.Compile(exp);
3685        }
3686
3687        if (!m_pasvReplyRegex.Matches(m_Response))
3688                return false;
3689
3690        pData->host = m_pasvReplyRegex.GetMatch(m_Response, 2);
3691
3692        int i = pData->host.Find(',', true);
3693        long number = 0;
3694        if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number))
3695                return false;
3696
3697        pData->port = number; //get ls byte of server socket
3698        pData->host = pData->host.Left(i);
3699        i = pData->host.Find(',', true);
3700        if (i == -1 || !pData->host.Mid(i + 1).ToLong(&number))
3701                return false;
3702
3703        pData->port += 256 * number; //add ms byte of server socket
3704        pData->host = pData-> host.Left(i);
3705        pData->host.Replace(_T(","), _T("."));
3706
3707        if (m_pProxyBackend) {
3708                // We do not have any information about the proxy's inner workings
3709                return true;
3710        }
3711
3712        const wxString peerIP = m_pSocket->GetPeerIP();
3713        if (!fz::is_routable_address(pData->host.ToStdWstring()) && fz::is_routable_address(peerIP.ToStdWstring())) {
3714                if (engine_.GetOptions().GetOptionVal(OPTION_PASVREPLYFALLBACKMODE) != 1 || pData->bTriedActive) {
3715                        LogMessage(MessageType::Status, _("Server sent passive reply with unroutable address. Using server address instead."));
3716                        LogMessage(MessageType::Debug_Info, _T("  Reply: %s, peer: %s"), pData->host, peerIP);
3717                        pData->host = peerIP;
3718                }
3719                else {
3720                        LogMessage(MessageType::Status, _("Server sent passive reply with unroutable address. Passive mode failed."));
3721                        LogMessage(MessageType::Debug_Info, _T("  Reply: %s, peer: %s"), pData->host, peerIP);
3722                        return false;
3723                }
3724        }
3725        else if (engine_.GetOptions().GetOptionVal(OPTION_PASVREPLYFALLBACKMODE) == 2) {
3726                // Always use server address
3727                pData->host = peerIP;
3728        }
3729
3730        return true;
3731}
3732
3733int CFtpControlSocket::GetExternalIPAddress(wxString& address)
3734{
3735        // Local IP should work. Only a complete moron would use IPv6
3736        // and NAT at the same time.
3737        if (m_pSocket->GetAddressFamily() != CSocket::ipv6)
3738        {
3739                int mode = engine_.GetOptions().GetOptionVal(OPTION_EXTERNALIPMODE);
3740
3741                if (mode)
3742                {
3743                        if (engine_.GetOptions().GetOptionVal(OPTION_NOEXTERNALONLOCAL) &&
3744                                !fz::is_routable_address(m_pSocket->GetPeerIP().ToStdWstring()))
3745                                // Skip next block, use local address
3746                                goto getLocalIP;
3747                }
3748
3749                if (mode == 1)
3750                {
3751                        wxString ip = engine_.GetOptions().GetOption(OPTION_EXTERNALIP);
3752                        if (!ip.empty())
3753                        {
3754                                address = ip;
3755                                return FZ_REPLY_OK;
3756                        }
3757
3758                        LogMessage(MessageType::Debug_Warning, _("No external IP address set, trying default."));
3759                }
3760                else if (mode == 2) {
3761                        if (!m_pIPResolver) {
3762                                const wxString& localAddress = m_pSocket->GetLocalIP(true);
3763
3764                                if (!localAddress.empty() && localAddress == engine_.GetOptions().GetOption(OPTION_LASTRESOLVEDIP)) {
3765                                        LogMessage(MessageType::Debug_Verbose, _T("Using cached external IP address"));
3766
3767                                        address = localAddress;
3768                                        return FZ_REPLY_OK;
3769                                }
3770
3771                                wxString resolverAddress = engine_.GetOptions().GetOption(OPTION_EXTERNALIPRESOLVER);
3772
3773                                LogMessage(MessageType::Debug_Info, _("Retrieving external IP address from %s"), resolverAddress);
3774
3775                                m_pIPResolver = new CExternalIPResolver(*this);
3776                                m_pIPResolver->GetExternalIP(resolverAddress, CSocket::ipv4);
3777                                if (!m_pIPResolver->Done()) {
3778                                        LogMessage(MessageType::Debug_Verbose, _T("Waiting for resolver thread"));
3779                                        return FZ_REPLY_WOULDBLOCK;
3780                                }
3781                        }
3782                        if (!m_pIPResolver->Successful()) {
3783                                delete m_pIPResolver;
3784                                m_pIPResolver = 0;
3785
3786                                LogMessage(MessageType::Debug_Warning, _("Failed to retrieve external ip address, using local address"));
3787                        }
3788                        else {
3789                                LogMessage(MessageType::Debug_Info, _T("Got external IP address"));
3790                                address = m_pIPResolver->GetIP();
3791
3792                                engine_.GetOptions().SetOption(OPTION_LASTRESOLVEDIP, address);
3793
3794                                delete m_pIPResolver;
3795                                m_pIPResolver = 0;
3796
3797                                return FZ_REPLY_OK;
3798                        }
3799                }
3800        }
3801
3802getLocalIP:
3803
3804        address = m_pSocket->GetLocalIP(true);
3805        if (address.empty())
3806        {
3807                LogMessage(MessageType::Error, _("Failed to retrieve local ip address."), 1);
3808                return FZ_REPLY_ERROR;
3809        }
3810
3811        return FZ_REPLY_OK;
3812}
3813
3814void CFtpControlSocket::OnExternalIPAddress()
3815{
3816        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::OnExternalIPAddress()"));
3817        if (!m_pIPResolver) {
3818                LogMessage(MessageType::Debug_Info, _T("Ignoring event"));
3819                return;
3820        }
3821
3822        SendNextCommand();
3823}
3824
3825int CFtpControlSocket::Transfer(const wxString& cmd, CFtpTransferOpData* oldData)
3826{
3827        wxASSERT(oldData);
3828        oldData->tranferCommandSent = false;
3829
3830        CRawTransferOpData *pData = new CRawTransferOpData;
3831        pData->pNextOpData = m_pCurOpData;
3832        m_pCurOpData = pData;
3833
3834        pData->cmd = cmd;
3835        pData->pOldData = oldData;
3836        pData->pOldData->transferEndReason = TransferEndReason::successful;
3837
3838        if (m_pProxyBackend)
3839        {
3840                // Only passive suported
3841                // Theoretically could use reverse proxy ability in SOCKS5, but
3842                // it is too fragile to set up with all those broken routers and
3843                // firewalls sabotaging connections. Regular active mode is hard
3844                // enough already
3845                pData->bPasv = true;
3846                pData->bTriedActive = true;
3847        }
3848        else
3849        {
3850                switch (m_pCurrentServer->GetPasvMode())
3851                {
3852                case MODE_PASSIVE:
3853                        pData->bPasv = true;
3854                        break;
3855                case MODE_ACTIVE:
3856                        pData->bPasv = false;
3857                        break;
3858                default:
3859                        pData->bPasv = engine_.GetOptions().GetOptionVal(OPTION_USEPASV) != 0;
3860                        break;
3861                }
3862        }
3863
3864        if ((pData->pOldData->binary && m_lastTypeBinary == 1) ||
3865                (!pData->pOldData->binary && m_lastTypeBinary == 0))
3866                pData->opState = rawtransfer_port_pasv;
3867        else
3868                pData->opState = rawtransfer_type;
3869
3870        return SendNextCommand();
3871}
3872
3873int CFtpControlSocket::TransferParseResponse()
3874{
3875        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::TransferParseResponse()"));
3876
3877        if (!m_pCurOpData)
3878        {
3879                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
3880                ResetOperation(FZ_REPLY_INTERNALERROR);
3881                return FZ_REPLY_ERROR;
3882        }
3883
3884        CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
3885        if (pData->opState == rawtransfer_init)
3886                return FZ_REPLY_ERROR;
3887
3888        int code = GetReplyCode();
3889
3890        LogMessage(MessageType::Debug_Debug, _T("  code = %d"), code);
3891        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
3892
3893        bool error = false;
3894        switch (pData->opState)
3895        {
3896        case rawtransfer_type:
3897                if (code != 2 && code != 3)
3898                        error = true;
3899                else
3900                {
3901                        pData->opState = rawtransfer_port_pasv;
3902                        m_lastTypeBinary = pData->pOldData->binary ? 1 : 0;
3903                }
3904                break;
3905        case rawtransfer_port_pasv:
3906                if (code != 2 && code != 3)
3907                {
3908                        if (!engine_.GetOptions().GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK))
3909                        {
3910                                error = true;
3911                                break;
3912                        }
3913
3914                        if (pData->bTriedPasv)
3915                                if (pData->bTriedActive)
3916                                        error = true;
3917                                else
3918                                        pData->bPasv = false;
3919                        else
3920                                pData->bPasv = true;
3921                        break;
3922                }
3923                if (pData->bPasv) {
3924                        bool parsed;
3925                        if (GetPassiveCommand(*pData) == _T("EPSV"))
3926                                parsed = ParseEpsvResponse(pData);
3927                        else
3928                                parsed = ParsePasvResponse(pData);
3929                        if (!parsed) {
3930                                if (!engine_.GetOptions().GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK)) {
3931                                        error = true;
3932                                        break;
3933                                }
3934
3935                                if (!pData->bTriedActive)
3936                                        pData->bPasv = false;
3937                                else
3938                                        error = true;
3939                                break;
3940                        }
3941                }
3942                if (pData->pOldData->resumeOffset > 0 || m_sentRestartOffset)
3943                        pData->opState = rawtransfer_rest;
3944                else
3945                        pData->opState = rawtransfer_transfer;
3946                break;
3947        case rawtransfer_rest:
3948                if (pData->pOldData->resumeOffset <= 0)
3949                        m_sentRestartOffset = false;
3950                if (pData->pOldData->resumeOffset > 0 && code != 2 && code != 3)
3951                        error = true;
3952                else
3953                        pData->opState = rawtransfer_transfer;
3954                break;
3955        case rawtransfer_transfer:
3956                if (code == 1) {
3957                        pData->opState = rawtransfer_waitfinish;
3958                }
3959                else if (code == 2 || code == 3) {
3960                        // A few broken servers omit the 1yz reply.
3961                        pData->opState = rawtransfer_waitsocket;
3962                }
3963                else {
3964                        if (pData->pOldData->transferEndReason == TransferEndReason::successful)
3965                                pData->pOldData->transferEndReason = TransferEndReason::transfer_command_failure_immediate;
3966                        error = true;
3967                }
3968                break;
3969        case rawtransfer_waittransferpre:
3970                if (code == 1) {
3971                        pData->opState = rawtransfer_waittransfer;
3972                }
3973                else if (code == 2 || code == 3) {
3974                        // A few broken servers omit the 1yz reply.
3975                        if (pData->pOldData->transferEndReason != TransferEndReason::successful) {
3976                                error = true;
3977                                break;
3978                        }
3979
3980                        ResetOperation(FZ_REPLY_OK);
3981                        return FZ_REPLY_OK;
3982                }
3983                else {
3984                        if (pData->pOldData->transferEndReason == TransferEndReason::successful)
3985                                pData->pOldData->transferEndReason = TransferEndReason::transfer_command_failure_immediate;
3986                        error = true;
3987                }
3988                break;
3989        case rawtransfer_waitfinish:
3990                if (code != 2 && code != 3) {
3991                        if (pData->pOldData->transferEndReason == TransferEndReason::successful)
3992                                pData->pOldData->transferEndReason = TransferEndReason::transfer_command_failure;
3993                        error = true;
3994                }
3995                else
3996                        pData->opState = rawtransfer_waitsocket;
3997                break;
3998        case rawtransfer_waittransfer:
3999                if (code != 2 && code != 3) {
4000                        if (pData->pOldData->transferEndReason == TransferEndReason::successful)
4001                                pData->pOldData->transferEndReason = TransferEndReason::transfer_command_failure;
4002                        error = true;
4003                }
4004                else {
4005                        if (pData->pOldData->transferEndReason != TransferEndReason::successful) {
4006                                error = true;
4007                                break;
4008                        }
4009
4010                        ResetOperation(FZ_REPLY_OK);
4011                        return FZ_REPLY_OK;
4012                }
4013                break;
4014        case rawtransfer_waitsocket:
4015                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Extra reply received during rawtransfer_waitsocket."));
4016                error = true;
4017                break;
4018        default:
4019                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown op state"));
4020                error = true;
4021        }
4022        if (error)
4023        {
4024                ResetOperation(FZ_REPLY_ERROR);
4025                return FZ_REPLY_ERROR;
4026        }
4027
4028        return SendNextCommand();
4029}
4030
4031int CFtpControlSocket::TransferSend()
4032{
4033        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::TransferSend()"));
4034
4035        if (!m_pCurOpData)
4036        {
4037                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
4038                ResetOperation(FZ_REPLY_INTERNALERROR);
4039                return FZ_REPLY_ERROR;
4040        }
4041
4042        if (!m_pTransferSocket)
4043        {
4044                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pTransferSocket"));
4045                ResetOperation(FZ_REPLY_INTERNALERROR);
4046                return FZ_REPLY_ERROR;
4047        }
4048
4049        CRawTransferOpData *pData = static_cast<CRawTransferOpData *>(m_pCurOpData);
4050        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
4051
4052        wxString cmd;
4053        bool measureRTT = false;
4054        switch (pData->opState)
4055        {
4056        case rawtransfer_type:
4057                m_lastTypeBinary = -1;
4058                if (pData->pOldData->binary)
4059                        cmd = _T("TYPE I");
4060                else
4061                        cmd = _T("TYPE A");
4062                measureRTT = true;
4063                break;
4064        case rawtransfer_port_pasv:
4065                if (pData->bPasv) {
4066                        cmd = GetPassiveCommand(*pData);
4067                }
4068                else {
4069                        wxString address;
4070                        int res = GetExternalIPAddress(address);
4071                        if (res == FZ_REPLY_WOULDBLOCK)
4072                                return res;
4073                        else if (res == FZ_REPLY_OK)
4074                        {
4075                                wxString portArgument = m_pTransferSocket->SetupActiveTransfer(address);
4076                                if (!portArgument.empty())
4077                                {
4078                                        pData->bTriedActive = true;
4079                                        if (m_pSocket->GetAddressFamily() == CSocket::ipv6)
4080                                                cmd = _T("EPRT " + portArgument);
4081                                        else
4082                                                cmd = _T("PORT " + portArgument);
4083                                        break;
4084                                }
4085                        }
4086
4087                        if (!engine_.GetOptions().GetOptionVal(OPTION_ALLOW_TRANSFERMODEFALLBACK) || pData->bTriedPasv)
4088                        {
4089                                LogMessage(MessageType::Error, _("Failed to create listening socket for active mode transfer"));
4090                                ResetOperation(FZ_REPLY_ERROR);
4091                                return FZ_REPLY_ERROR;
4092                        }
4093                        LogMessage(MessageType::Debug_Warning, _("Failed to create listening socket for active mode transfer"));
4094                        pData->bTriedActive = true;
4095                        pData->bPasv = true;
4096                        cmd = GetPassiveCommand(*pData);
4097                }
4098                break;
4099        case rawtransfer_rest:
4100                cmd = _T("REST ") + std::to_wstring(pData->pOldData->resumeOffset);
4101                if (pData->pOldData->resumeOffset > 0)
4102                        m_sentRestartOffset = true;
4103                measureRTT = true;
4104                break;
4105        case rawtransfer_transfer:
4106                if (pData->bPasv)
4107                {
4108                        if (!m_pTransferSocket->SetupPassiveTransfer(pData->host, pData->port))
4109                        {
4110                                LogMessage(MessageType::Error, _("Could not establish connection to server"));
4111                                ResetOperation(FZ_REPLY_ERROR);
4112                                return FZ_REPLY_ERROR;
4113                        }
4114                }
4115
4116                cmd = pData->cmd;
4117                pData->pOldData->tranferCommandSent = true;
4118
4119                engine_.transfer_status_.SetStartTime();
4120                m_pTransferSocket->SetActive();
4121                break;
4122        case rawtransfer_waitfinish:
4123        case rawtransfer_waittransferpre:
4124        case rawtransfer_waittransfer:
4125        case rawtransfer_waitsocket:
4126                break;
4127        default:
4128                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("invalid opstate"));
4129                ResetOperation(FZ_REPLY_INTERNALERROR);
4130                return FZ_REPLY_ERROR;
4131        }
4132        if (!cmd.empty())
4133                if (!SendCommand(cmd, false, measureRTT))
4134                        return FZ_REPLY_ERROR;
4135
4136        return FZ_REPLY_WOULDBLOCK;
4137}
4138
4139int CFtpControlSocket::FileTransferTestResumeCapability()
4140{
4141        LogMessage(MessageType::Debug_Verbose, _T("FileTransferTestResumeCapability()"));
4142
4143        if (!m_pCurOpData)
4144        {
4145                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
4146                ResetOperation(FZ_REPLY_INTERNALERROR);
4147                return FZ_REPLY_ERROR;
4148        }
4149
4150        CFtpFileTransferOpData *pData = static_cast<CFtpFileTransferOpData *>(m_pCurOpData);
4151
4152        if (!pData->download)
4153                return FZ_REPLY_OK;
4154
4155        for (int i = 0; i < 2; ++i)
4156        {
4157                if (pData->localFileSize >= ((wxFileOffset)1 << (i ? 31 : 32)))
4158                {
4159                        switch (CServerCapabilities::GetCapability(*GetCurrentServer(), i ? resume2GBbug : resume4GBbug))
4160                        {
4161                        case yes:
4162                                if (pData->remoteFileSize == pData->localFileSize)
4163                                {
4164                                        LogMessage(MessageType::Debug_Info, _("Server does not support resume of files > %d GB. End transfer since file sizes match."), i ? 2 : 4);
4165                                        ResetOperation(FZ_REPLY_OK);
4166                                        return FZ_REPLY_CANCELED;
4167                                }
4168                                LogMessage(MessageType::Error, _("Server does not support resume of files > %d GB."), i ? 2 : 4);
4169                                ResetOperation(FZ_REPLY_CRITICALERROR);
4170                                return FZ_REPLY_ERROR;
4171                        case unknown:
4172                                if (pData->remoteFileSize < pData->localFileSize)
4173                                {
4174                                        // Don't perform test
4175                                        break;
4176                                }
4177                                if (pData->remoteFileSize == pData->localFileSize)
4178                                {
4179                                        LogMessage(MessageType::Debug_Info, _("Server may not support resume of files > %d GB. End transfer since file sizes match."), i ? 2 : 4);
4180                                        ResetOperation(FZ_REPLY_OK);
4181                                        return FZ_REPLY_CANCELED;
4182                                }
4183                                else if (pData->remoteFileSize > pData->localFileSize)
4184                                {
4185                                        LogMessage(MessageType::Status, _("Testing resume capabilities of server"));
4186
4187                                        pData->opState = filetransfer_waitresumetest;
4188                                        pData->resumeOffset = pData->remoteFileSize - 1;
4189
4190                                        m_pTransferSocket = new CTransferSocket(engine_, *this, TransferMode::resumetest);
4191
4192                                        return Transfer(_T("RETR ") + pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath), pData);
4193                                }
4194                                break;
4195                        case no:
4196                                break;
4197                        }
4198                }
4199        }
4200
4201        return FZ_REPLY_OK;
4202}
4203
4204int CFtpControlSocket::Connect(const CServer &server)
4205{
4206        if (m_pCurOpData)
4207        {
4208                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("deleting nonzero pData"));
4209                delete m_pCurOpData;
4210        }
4211
4212        CFtpLogonOpData* pData = new CFtpLogonOpData;
4213        m_pCurOpData = pData;
4214
4215        // Do not use FTP proxy if generic proxy is set
4216        int generic_proxy_type = engine_.GetOptions().GetOptionVal(OPTION_PROXY_TYPE);
4217        if ((generic_proxy_type <= CProxySocket::unknown || generic_proxy_type >= CProxySocket::proxytype_count) &&
4218                (pData->ftp_proxy_type = engine_.GetOptions().GetOptionVal(OPTION_FTP_PROXY_TYPE)) && !server.GetBypassProxy())
4219        {
4220                pData->host = engine_.GetOptions().GetOption(OPTION_FTP_PROXY_HOST);
4221
4222                int pos = -1;
4223                if (!pData->host.empty() && pData->host[0] == '[') {
4224                        // Probably IPv6 address
4225                        pos = pData->host.Find(']');
4226                        if (pos < 0) {
4227                                LogMessage(MessageType::Error, _("Proxy host starts with '[' but no closing bracket found."));
4228                                DoClose(FZ_REPLY_CRITICALERROR);
4229                                return FZ_REPLY_ERROR;
4230                        }
4231                        if (pData->host.size() > static_cast<size_t>(pos + 1) && pData->host[pos + 1]) {
4232                                if (pData->host[pos + 1] != ':') {
4233                                        LogMessage(MessageType::Error, _("Invalid proxy host, after closing bracket only colon and port may follow."));
4234                                        DoClose(FZ_REPLY_CRITICALERROR);
4235                                        return FZ_REPLY_ERROR;
4236                                }
4237                                ++pos;
4238                        }
4239                        else
4240                                pos = -1;
4241                }
4242                else
4243                        pos = pData->host.Find(':');
4244
4245                if (pos != -1) {
4246                        unsigned long port = 0;
4247                        if (!pData->host.Mid(pos + 1).ToULong(&port))
4248                                port = 0;
4249                        pData->host = pData->host.Left(pos);
4250                        pData->port = port;
4251                }
4252                else
4253                        pData->port = 21;
4254
4255                if (pData->host.empty() || pData->port < 1 || pData->port > 65535) {
4256                        LogMessage(MessageType::Error, _("Proxy set but proxy host or port invalid"));
4257                        DoClose(FZ_REPLY_CRITICALERROR);
4258                        return FZ_REPLY_ERROR;
4259                }
4260
4261                LogMessage(MessageType::Status, _("Connecting to %s through %s proxy"), server.FormatHost(), _T("FTP")); // @translator: Connecting to ftp.example.com through SOCKS5 proxy
4262        }
4263        else {
4264                pData->ftp_proxy_type = 0;
4265                pData->host = server.GetHost();
4266                pData->port = server.GetPort();
4267        }
4268
4269        if (server.GetProtocol() != FTPES && server.GetProtocol() != FTP) {
4270                pData->neededCommands[LOGON_AUTH_TLS] = 0;
4271                pData->neededCommands[LOGON_AUTH_SSL] = 0;
4272                pData->neededCommands[LOGON_AUTH_WAIT] = 0;
4273                if (server.GetProtocol() != FTPS) {
4274                        pData->neededCommands[LOGON_PBSZ] = 0;
4275                        pData->neededCommands[LOGON_PROT] = 0;
4276                }
4277        }
4278        if (server.GetPostLoginCommands().empty())
4279                pData->neededCommands[LOGON_CUSTOMCOMMANDS] = 0;
4280
4281        if (!GetLoginSequence(server))
4282                return DoClose(FZ_REPLY_INTERNALERROR);
4283
4284        return CRealControlSocket::Connect(server);
4285}
4286
4287bool CFtpControlSocket::CheckInclusion(const CDirectoryListing& listing1, const CDirectoryListing& listing2)
4288{
4289        // Check if listing2 is contained within listing1
4290
4291        if (listing2.GetCount() > listing1.GetCount())
4292                return false;
4293
4294        std::vector<std::wstring> names1, names2;
4295        listing1.GetFilenames(names1);
4296        listing2.GetFilenames(names2);
4297        std::sort(names1.begin(), names1.end());
4298        std::sort(names2.begin(), names2.end());
4299
4300        std::vector<std::wstring>::const_iterator iter1, iter2;
4301        iter1 = names1.cbegin();
4302        iter2 = names2.cbegin();
4303        while (iter2 != names2.cbegin()) {
4304                if (iter1 == names1.cend())
4305                        return false;
4306
4307                if (*iter1 != *iter2) {
4308                        ++iter1;
4309                        continue;
4310                }
4311
4312                ++iter1;
4313                ++iter2;
4314        }
4315
4316        return true;
4317}
4318
4319void CFtpControlSocket::OnTimer(fz::timer_id id)
4320{
4321        if (id != m_idleTimer) {
4322                CControlSocket::OnTimer(id);
4323                return;
4324        }
4325
4326        if (m_pCurOpData)
4327                return;
4328
4329        if (m_pendingReplies || m_repliesToSkip)
4330                return;
4331
4332        LogMessage(MessageType::Status, _("Sending keep-alive command"));
4333
4334        wxString cmd;
4335        int i = fz::random_number(0, 2);
4336        if (!i)
4337                cmd = _T("NOOP");
4338        else if (i == 1)
4339        {
4340                if (m_lastTypeBinary)
4341                        cmd = _T("TYPE I");
4342                else
4343                        cmd = _T("TYPE A");
4344        }
4345        else
4346                cmd = _T("PWD");
4347
4348        if (!SendCommand(cmd))
4349                return;
4350        ++m_repliesToSkip;
4351}
4352
4353void CFtpControlSocket::StartKeepaliveTimer()
4354{
4355        if (!engine_.GetOptions().GetOptionVal(OPTION_FTP_SENDKEEPALIVE))
4356                return;
4357
4358        if (m_repliesToSkip || m_pendingReplies)
4359                return;
4360
4361        if (!m_lastCommandCompletionTime)
4362                return;
4363
4364        fz::duration const span = fz::monotonic_clock::now() - m_lastCommandCompletionTime;
4365        if (span.get_minutes() >= 30) {
4366                return;
4367        }
4368
4369        stop_timer(m_idleTimer);
4370        m_idleTimer = add_timer(fz::duration::from_seconds(30), true);
4371}
4372
4373int CFtpControlSocket::ParseSubcommandResult(int prevResult)
4374{
4375        LogMessage(MessageType::Debug_Verbose, _T("CFtpControlSocket::ParseSubcommandResult(%d)"), prevResult);
4376        if (!m_pCurOpData)
4377        {
4378                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("ParseSubcommandResult called without active operation"));
4379                ResetOperation(FZ_REPLY_ERROR);
4380                return FZ_REPLY_ERROR;
4381        }
4382
4383        switch (m_pCurOpData->opId)
4384        {
4385        case Command::cwd:
4386                return ChangeDirSubcommandResult(prevResult);
4387        case Command::list:
4388                return ListSubcommandResult(prevResult);
4389        case Command::transfer:
4390                return FileTransferSubcommandResult(prevResult);
4391        case Command::del:
4392                return DeleteSubcommandResult(prevResult);
4393        case Command::removedir:
4394                return RemoveDirSubcommandResult(prevResult);
4395        case Command::rename:
4396                return RenameSubcommandResult(prevResult);
4397        case Command::chmod:
4398                return ChmodSubcommandResult(prevResult);
4399        default:
4400                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown opID (%d) in ParseSubcommandResult"), m_pCurOpData->opId);
4401                ResetOperation(FZ_REPLY_INTERNALERROR);
4402                break;
4403        }
4404
4405        return FZ_REPLY_ERROR;
4406}
4407
4408void CFtpControlSocket::operator()(fz::event_base const& ev)
4409{
4410        if (fz::dispatch<fz::timer_event>(ev, this, &CFtpControlSocket::OnTimer)) {
4411                return;
4412        }
4413
4414        if (fz::dispatch<CExternalIPResolveEvent>(ev, this, &CFtpControlSocket::OnExternalIPAddress)) {
4415                return;
4416        }
4417
4418        CRealControlSocket::operator()(ev);
4419}
4420
4421wxString CFtpControlSocket::GetPassiveCommand(CRawTransferOpData& data)
4422{
4423        wxString ret = _T("PASV");
4424
4425        wxASSERT(data.bPasv);
4426        data.bTriedPasv = true;
4427
4428        if (m_pProxyBackend) {
4429                // We don't actually know the address family the other end of the proxy uses to reach the server. Hence prefer EPSV
4430                // if the server supports it.
4431                if (CServerCapabilities::GetCapability(*m_pCurrentServer, epsv_command) == yes) {
4432                        ret = _T("EPSV");
4433                }
4434        }
4435        else if (m_pSocket->GetAddressFamily() == CSocket::ipv6) {
4436                // EPSV is mandatory for IPv6, don't check capabilities
4437                ret = _T("EPSV");
4438        }
4439        return ret;
4440}
Note: See TracBrowser for help on using the repository browser.