source: filezilla/trunk/fuentes/src/engine/sftpcontrolsocket.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: 70.6 KB
Line 
1#include <filezilla.h>
2
3#include "directorycache.h"
4#include "directorylistingparser.h"
5#include "engineprivate.h"
6#include "pathcache.h"
7#include "proxy.h"
8#include "servercapabilities.h"
9#include "sftpcontrolsocket.h"
10
11#include <libfilezilla/event_loop.hpp>
12#include <libfilezilla/local_filesys.hpp>
13#include <libfilezilla/process.hpp>
14
15#include <wx/filename.h>
16#include <wx/log.h>
17#include <wx/tokenzr.h>
18#include <wx/txtstrm.h>
19
20#define FZSFTP_PROTOCOL_VERSION 4
21
22struct sftp_event_type;
23typedef fz::simple_event<sftp_event_type> CSftpEvent;
24
25struct terminate_event_type;
26typedef fz::simple_event<terminate_event_type> CTerminateEvent;
27
28class CSftpFileTransferOpData : public CFileTransferOpData
29{
30public:
31        CSftpFileTransferOpData(bool is_download, const wxString& local_file, const wxString& remote_file, const CServerPath& remote_path)
32                : CFileTransferOpData(is_download, local_file, remote_file, remote_path)
33        {
34        }
35};
36
37enum filetransferStates
38{
39        filetransfer_init = 0,
40        filetransfer_waitcwd,
41        filetransfer_waitlist,
42        filetransfer_mtime,
43        filetransfer_transfer,
44        filetransfer_chmtime
45};
46
47struct sftp_message
48{
49        sftpEvent type;
50        wxString text;
51        union
52        {
53                sftpRequestTypes reqType;
54                int value;
55        };
56};
57
58class CSftpInputThread final : public fz::thread
59{
60public:
61        CSftpInputThread(CSftpControlSocket* pOwner, fz::process& proc)
62                : process_(proc)
63                , m_pOwner(pOwner)
64        {
65        }
66
67        virtual ~CSftpInputThread()
68        {
69                join();
70
71                fz::scoped_lock l(m_sync);
72                for (auto & msg : m_sftpMessages) {
73                        delete msg;
74                }
75        }
76
77        void GetMessages(std::vector<sftp_message*>& messages)
78        {
79                fz::scoped_lock l(m_sync);
80                messages.swap(m_sftpMessages);
81        }
82
83protected:
84
85        void SendMessage(sftp_message* message)
86        {
87                bool sendEvent;
88
89                {
90                        fz::scoped_lock l(m_sync);
91                        sendEvent = m_sftpMessages.empty();
92                        m_sftpMessages.push_back(message);
93                }
94
95                if (sendEvent)
96                        m_pOwner->send_event<CSftpEvent>();
97        }
98
99        int ReadNumber(bool &error)
100        {
101                int number = 0;
102
103                while(true) {
104                        char c;
105                        int read = process_.read(&c, 1);
106                        if (read != 1) {
107                                if (!read)
108                                        m_pOwner->LogMessage(MessageType::Debug_Warning, _T("Unexpected EOF."));
109                                else
110                                        m_pOwner->LogMessage(MessageType::Debug_Warning, _T("Uknown input stream error"));
111                                error = true;
112                                return 0;
113                        }
114
115                        if (c == '\n')
116                                break;
117                        else if (c >= '0' && c <= '9') {
118                                number *= 10;
119                                number += c - '0';
120                        }
121                }
122                return number;
123        }
124
125        wxString ReadLine(bool &error)
126        {
127                int len = 0;
128                const int buffersize = 4096;
129                char buffer[buffersize];
130
131                while(true) {
132                        char c;
133                        int read = process_.read(&c, 1);
134                        if (read != 1) {
135                                if (!read)
136                                        m_pOwner->LogMessage(MessageType::Debug_Warning, _T("Unexpected EOF."));
137                                else
138                                        m_pOwner->LogMessage(MessageType::Debug_Warning, _T("Uknown input stream error"));
139                                error = true;
140                                return wxString();
141                        }
142
143                        if (c == '\n')
144                                break;
145
146                        if (len == buffersize - 1) {
147                                // Cap string length
148                                continue;
149                        }
150
151                        buffer[len++] = c;
152                }
153
154                while (len && buffer[len - 1] == '\r')
155                        --len;
156
157                buffer[len] = 0;
158
159                const wxString line = m_pOwner->ConvToLocal(buffer, len + 1);
160                if (len && line.empty()) {
161                        m_pOwner->LogMessage(MessageType::Error, _T("Failed to convert reply to local character set."));
162                        error = true;
163                }
164
165                return line;
166        }
167
168        virtual void entry()
169        {
170                bool error = false;
171                while (!error) {
172                        char readType = 0;
173                        int read = process_.read(&readType, 1);
174                        if (read != 1)
175                                break;
176
177                        readType -= '0';
178
179                        sftpEvent eventType = sftpEvent::Unknown;
180                        if( readType >= 0 && readType <= static_cast<char>(sftpEvent::max) ) {
181                                eventType = static_cast<sftpEvent>(readType);
182                        }
183
184                        switch(eventType)
185                        {
186                        case sftpEvent::Reply:
187                        case sftpEvent::Listentry:
188                        case sftpEvent::RequestPreamble:
189                        case sftpEvent::RequestInstruction:
190                        case sftpEvent::Done:
191                        case sftpEvent::Error:
192                        case sftpEvent::Verbose:
193                        case sftpEvent::Status:
194                        case sftpEvent::KexAlgorithm:
195                        case sftpEvent::KexHash:
196                        case sftpEvent::KexCurve:
197                        case sftpEvent::CipherClientToServer:
198                        case sftpEvent::CipherServerToClient:
199                        case sftpEvent::MacClientToServer:
200                        case sftpEvent::MacServerToClient:
201                        case sftpEvent::Hostkey:
202                                {
203                                        sftp_message* message = new sftp_message;
204                                        message->type = eventType;
205                                        message->text = ReadLine(error);
206                                        if (error) {
207                                                delete message;
208                                                goto loopexit;
209                                        }
210                                        SendMessage(message);
211                                }
212                                break;
213                        case sftpEvent::Request:
214                                {
215                                        const wxString& line = ReadLine(error);
216                                        if (error || line.empty())
217                                                goto loopexit;
218                                        int requestType = line[0] - '0';
219                                        if (requestType == sftpReqHostkey || requestType == sftpReqHostkeyChanged) {
220                                                const wxString& strPort = ReadLine(error);
221                                                if (error)
222                                                        goto loopexit;
223                                                long port = 0;
224                                                if (!strPort.ToLong(&port))
225                                                        goto loopexit;
226                                                const wxString& fingerprint = ReadLine(error);
227                                                if (error)
228                                                        goto loopexit;
229
230                                                m_pOwner->SendAsyncRequest(new CHostKeyNotification(line.Mid(1), port, fingerprint, requestType == sftpReqHostkeyChanged));
231                                        }
232                                        else if (requestType == sftpReqPassword)
233                                        {
234                                                sftp_message* message = new sftp_message;
235                                                message->type = eventType;
236                                                message->reqType = sftpReqPassword;
237                                                message->text = line.Mid(1);
238                                                SendMessage(message);
239                                        }
240                                }
241                                break;
242                        case sftpEvent::Recv:
243                        case sftpEvent::Send:
244                        case sftpEvent::UsedQuotaRecv:
245                        case sftpEvent::UsedQuotaSend:
246                                {
247                                        sftp_message* message = new sftp_message;
248                                        message->type = eventType;
249                                        SendMessage(message);
250                                }
251                                break;
252                        case sftpEvent::Transfer:
253                                {
254                                        sftp_message* message = new sftp_message;
255                                        message->type = eventType;
256                                        message->value = ReadNumber(error);
257                                        if (error) {
258                                                delete message;
259                                                goto loopexit;
260                                        }
261                                        if (!message->value)
262                                                delete message;
263                                        else
264                                                SendMessage(message);
265                                }
266                                break;
267                        default:
268                                {
269                                        char tmp[2];
270                                        tmp[0] = static_cast<char>(eventType) + '0';
271                                        tmp[1] = 0;
272                                        m_pOwner->LogMessage(MessageType::Debug_Info, _T("Unknown eventType: %s"), tmp);
273                                }
274                                break;
275                        }
276                }
277loopexit:
278
279                m_pOwner->send_event<CTerminateEvent>();
280        }
281
282        fz::process& process_;
283        CSftpControlSocket* m_pOwner;
284
285        std::vector<sftp_message*> m_sftpMessages;
286        fz::mutex m_sync;
287};
288
289class CSftpDeleteOpData final : public COpData
290{
291public:
292        CSftpDeleteOpData()
293                : COpData(Command::del)
294        {
295        }
296
297        virtual ~CSftpDeleteOpData() {}
298
299        CServerPath path;
300        std::deque<wxString> files;
301
302        // Set to fz::datetime::Now initially and after
303        // sending an updated listing to the UI.
304        fz::datetime m_time;
305
306        bool m_needSendListing{};
307
308        // Set to true if deletion of at least one file failed
309        bool m_deleteFailed{};
310};
311
312CSftpControlSocket::CSftpControlSocket(CFileZillaEnginePrivate & engine)
313        : CControlSocket(engine)
314{
315        m_useUTF8 = true;
316}
317
318CSftpControlSocket::~CSftpControlSocket()
319{
320        remove_handler();
321        DoClose();
322}
323
324enum connectStates
325{
326        connect_init,
327        connect_proxy,
328        connect_keys,
329        connect_open
330};
331
332class CSftpConnectOpData : public COpData
333{
334public:
335        CSftpConnectOpData()
336                : COpData(Command::connect)
337        {
338                criticalFailure = false;
339                pKeyFiles = 0;
340        }
341
342        virtual ~CSftpConnectOpData()
343        {
344                delete pKeyFiles;
345        }
346
347        wxString lastChallenge;
348        bool criticalFailure;
349
350        wxStringTokenizer* pKeyFiles;
351};
352
353int CSftpControlSocket::Connect(const CServer &server)
354{
355        LogMessage(MessageType::Status, _("Connecting to %s..."), server.FormatHost());
356        SetWait(true);
357
358        m_sftpEncryptionDetails = CSftpEncryptionNotification();
359
360        delete m_pCSConv;
361        if (server.GetEncodingType() == ENCODING_CUSTOM) {
362                LogMessage(MessageType::Debug_Info, _T("Using custom encoding: %s"), server.GetCustomEncoding());
363                m_pCSConv = new wxCSConv(server.GetCustomEncoding());
364                m_useUTF8 = false;
365        }
366        else {
367                m_pCSConv = 0;
368                m_useUTF8 = true;
369        }
370
371        delete m_pCurrentServer;
372        m_pCurrentServer = new CServer(server);
373
374        if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown) {
375                CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, yes, 0);
376        }
377
378        CSftpConnectOpData* pData = new CSftpConnectOpData;
379        m_pCurOpData = pData;
380
381        pData->opState = connect_init;
382
383        wxStringTokenizer* pTokenizer;
384        if (m_pCurrentServer->GetLogonType() == KEY)
385                pTokenizer = new wxStringTokenizer(m_pCurrentServer->GetKeyFile() + _T("\n"), _T("\n"), wxTOKEN_DEFAULT);
386        else
387                pTokenizer = new wxStringTokenizer(engine_.GetOptions().GetOption(OPTION_SFTP_KEYFILES), _T("\n"), wxTOKEN_DEFAULT);
388
389        if (!pTokenizer->HasMoreTokens())
390                delete pTokenizer;
391        else
392                pData->pKeyFiles = pTokenizer;
393
394        m_pProcess = new fz::process();
395
396        engine_.GetRateLimiter().AddObject(this);
397
398        auto executable = fz::to_native(engine_.GetOptions().GetOption(OPTION_FZSFTP_EXECUTABLE));
399        if (executable.empty())
400                executable = fzT("fzsftp");
401        LogMessage(MessageType::Debug_Verbose, _T("Going to execute %s"), executable);
402
403        std::vector<fz::native_string> args = {fzT("-v")};
404        if (engine_.GetOptions().GetOptionVal(OPTION_SFTP_COMPRESSION)) {
405                args.push_back(fzT("-C"));
406        }
407        if (!m_pProcess->spawn(executable, args)) {
408                LogMessage(MessageType::Debug_Warning, _T("Could not create process: %s"), wxSysErrorMsg());
409                DoClose();
410                return FZ_REPLY_ERROR;
411        }
412
413        m_pInputThread = new CSftpInputThread(this, *m_pProcess);
414        if (!m_pInputThread->run()) {
415                LogMessage(MessageType::Debug_Warning, _T("Thread creation failed"));
416                delete m_pInputThread;
417                m_pInputThread = 0;
418                DoClose();
419                return FZ_REPLY_ERROR;
420        }
421
422        return FZ_REPLY_WOULDBLOCK;
423}
424
425int CSftpControlSocket::ConnectParseResponse(bool successful, const wxString& reply)
426{
427        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ConnectParseResponse(%s)"), reply);
428
429        if (!successful) {
430                DoClose(FZ_REPLY_ERROR);
431                return FZ_REPLY_ERROR;
432        }
433
434        if (!m_pCurOpData) {
435                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
436                DoClose(FZ_REPLY_INTERNALERROR);
437                return FZ_REPLY_ERROR;
438        }
439
440        CSftpConnectOpData *pData = static_cast<CSftpConnectOpData *>(m_pCurOpData);
441        if (!pData)
442        {
443                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData of wrong type"));
444                DoClose(FZ_REPLY_INTERNALERROR);
445                return FZ_REPLY_ERROR;
446        }
447
448        switch (pData->opState)
449        {
450        case connect_init:
451                if (reply != wxString::Format(_T("fzSftp started, protocol_version=%d"), FZSFTP_PROTOCOL_VERSION)) {
452                        LogMessage(MessageType::Error, _("fzsftp belongs to a different version of FileZilla"));
453                        DoClose(FZ_REPLY_INTERNALERROR);
454                        return FZ_REPLY_ERROR;
455                }
456                if (engine_.GetOptions().GetOptionVal(OPTION_PROXY_TYPE) && !m_pCurrentServer->GetBypassProxy())
457                        pData->opState = connect_proxy;
458                else if (pData->pKeyFiles)
459                        pData->opState = connect_keys;
460                else
461                        pData->opState = connect_open;
462                break;
463        case connect_proxy:
464                if (pData->pKeyFiles)
465                        pData->opState = connect_keys;
466                else
467                        pData->opState = connect_open;
468                break;
469        case connect_keys:
470                wxASSERT(pData->pKeyFiles);
471                if (!pData->pKeyFiles->HasMoreTokens())
472                        pData->opState = connect_open;
473                break;
474        case connect_open:
475                engine_.AddNotification(new CSftpEncryptionNotification(m_sftpEncryptionDetails));
476                ResetOperation(FZ_REPLY_OK);
477                return FZ_REPLY_OK;
478        default:
479                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown op state: %d"), pData->opState);
480                DoClose(FZ_REPLY_INTERNALERROR);
481                return FZ_REPLY_ERROR;
482        }
483
484        return SendNextCommand();
485}
486
487int CSftpControlSocket::ConnectSend()
488{
489        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ConnectSend()"));
490        if (!m_pCurOpData)
491        {
492                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
493                DoClose(FZ_REPLY_INTERNALERROR);
494                return FZ_REPLY_ERROR;
495        }
496
497        CSftpConnectOpData *pData = static_cast<CSftpConnectOpData *>(m_pCurOpData);
498        if (!pData)
499        {
500                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData of wrong type"));
501                DoClose(FZ_REPLY_INTERNALERROR);
502                return FZ_REPLY_ERROR;
503        }
504
505        bool res;
506        switch (pData->opState)
507        {
508        case connect_proxy:
509                {
510                        int type;
511                        switch (engine_.GetOptions().GetOptionVal(OPTION_PROXY_TYPE))
512                        {
513                        case CProxySocket::HTTP:
514                                type = 1;
515                                break;
516                        case CProxySocket::SOCKS5:
517                                type = 2;
518                                break;
519                        case CProxySocket::SOCKS4:
520                                type = 3;
521                                break;
522                        default:
523                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unsupported proxy type"));
524                                DoClose(FZ_REPLY_INTERNALERROR);
525                                return FZ_REPLY_ERROR;
526                        }
527
528                        wxString cmd = wxString::Format(_T("proxy %d \"%s\" %d"), type,
529                                                                                        engine_.GetOptions().GetOption(OPTION_PROXY_HOST),
530                                                                                        engine_.GetOptions().GetOptionVal(OPTION_PROXY_PORT));
531                        wxString user = engine_.GetOptions().GetOption(OPTION_PROXY_USER);
532                        if (!user.empty())
533                                cmd += _T(" \"") + user + _T("\"");
534
535                        wxString show = cmd;
536
537                        wxString pass = engine_.GetOptions().GetOption(OPTION_PROXY_PASS);
538                        if (!pass.empty())
539                        {
540                                cmd += _T(" \"") + pass + _T("\"");
541                                show += _T(" \"") + wxString('*', pass.Len()) + _T("\"");
542                        }
543                        res = SendCommand(cmd, show);
544                }
545                break;
546        case connect_keys:
547                res = SendCommand(_T("keyfile \"") + pData->pKeyFiles->GetNextToken() + _T("\""));
548                break;
549        case connect_open:
550                res = SendCommand(wxString::Format(_T("open \"%s@%s\" %d"), m_pCurrentServer->GetUser(), m_pCurrentServer->GetHost(), m_pCurrentServer->GetPort()));
551                break;
552        default:
553                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown op state: %d"), pData->opState);
554                DoClose(FZ_REPLY_INTERNALERROR);
555                return FZ_REPLY_ERROR;
556        }
557
558        if (res)
559                return FZ_REPLY_WOULDBLOCK;
560        else
561                return FZ_REPLY_ERROR;
562}
563
564void CSftpControlSocket::OnSftpEvent()
565{
566        if (!m_pCurrentServer)
567                return;
568
569        if (!m_pInputThread)
570                return;
571
572        std::vector<sftp_message*> messages;
573        m_pInputThread->GetMessages(messages);
574        for (auto iter = messages.begin(); iter != messages.end(); ++iter) {
575                if (!m_pInputThread) {
576                        delete *iter;
577                        continue;
578                }
579
580                sftp_message* message = *iter;
581
582                switch (message->type)
583                {
584                case sftpEvent::Reply:
585                        LogMessageRaw(MessageType::Response, message->text);
586                        ProcessReply(FZ_REPLY_OK, message->text);
587                        break;
588                case sftpEvent::Status:
589                        LogMessageRaw(MessageType::Status, message->text);
590                        break;
591                case sftpEvent::Error:
592                        LogMessageRaw(MessageType::Error, message->text);
593                        break;
594                case sftpEvent::Verbose:
595                        LogMessageRaw(MessageType::Debug_Info, message->text);
596                        break;
597                case sftpEvent::Done:
598                        {
599                                int result;
600                                if (message->text == _T("1")) {
601                                        result = FZ_REPLY_OK;
602                                }
603                                else if (message->text == _T("2")) {
604                                        result = FZ_REPLY_CRITICALERROR;
605                                }
606                                else {
607                                        result = FZ_REPLY_ERROR;
608                                }
609                                ProcessReply(result);
610                                break;
611                        }
612                case sftpEvent::RequestPreamble:
613                        m_requestPreamble = message->text;
614                        break;
615                case sftpEvent::RequestInstruction:
616                        m_requestInstruction = message->text;
617                        break;
618                case sftpEvent::Request:
619                        switch(message->reqType)
620                        {
621                        case sftpReqPassword:
622                                if (!m_pCurOpData || m_pCurOpData->opId != Command::connect) {
623                                        LogMessage(MessageType::Debug_Warning, _T("sftpReqPassword outside connect operation, ignoring."));
624                                        break;
625                                }
626                                else {
627                                        CSftpConnectOpData *pData = static_cast<CSftpConnectOpData*>(m_pCurOpData);
628
629                                        wxString const challengeIdentifier = m_requestPreamble + _T("\n") + m_requestInstruction + _T("\n") + message->text;
630
631                                        if (m_pCurrentServer->GetLogonType() == INTERACTIVE || m_requestPreamble == _T("SSH key passphrase")) {
632                                                CInteractiveLoginNotification::type t = CInteractiveLoginNotification::interactive;
633                                                if (m_requestPreamble == _T("SSH key passphrase")) {
634                                                        t = CInteractiveLoginNotification::keyfile;
635                                                }
636
637                                                wxString challenge;
638                                                if (!m_requestPreamble.empty() && t != CInteractiveLoginNotification::keyfile)
639                                                        challenge += m_requestPreamble + _T("\n");
640                                                if (!m_requestInstruction.empty())
641                                                        challenge += m_requestInstruction + _T("\n");
642                                                if (message->text != _T("Password:"))
643                                                        challenge += message->text;
644                                                CInteractiveLoginNotification *pNotification = new CInteractiveLoginNotification(t, challenge, pData->lastChallenge == challengeIdentifier);
645                                                pNotification->server = *m_pCurrentServer;
646
647                                                pData->lastChallenge = challengeIdentifier;
648
649                                                SendAsyncRequest(pNotification);
650                                        }
651                                        else {
652                                                const wxString newChallenge = m_requestPreamble + _T("\n") + m_requestInstruction + message->text;
653
654                                                if (!pData->lastChallenge.empty()) {
655                                                        // Check for same challenge. Will most likely fail as well, so abort early.
656                                                        if (pData->lastChallenge == challengeIdentifier) {
657                                                                LogMessage(MessageType::Error, _("Authentication failed."));
658                                                        }
659                                                        else {
660                                                                LogMessage(MessageType::Error, _("Server sent an additional login prompt. You need to use the interactive login type."));
661                                                        }
662                                                        DoClose(FZ_REPLY_CRITICALERROR | FZ_REPLY_PASSWORDFAILED);
663
664                                                        for (;iter != messages.end(); ++iter)
665                                                                delete *iter;
666                                                        return;
667                                                }
668
669                                                pData->lastChallenge = challengeIdentifier;
670
671                                                const wxString pass = m_pCurrentServer->GetPass();
672                                                wxString show = _T("Pass: ");
673                                                show.Append('*', pass.Length());
674                                                SendCommand(pass, show);
675                                        }
676                                }
677                                break;
678                        default:
679                                wxFAIL_MSG(_T("given notification codes should have been handled by thread"));
680                                break;
681                        }
682                        break;
683                case sftpEvent::Listentry:
684                        ListParseEntry(message->text);
685                        break;
686                case sftpEvent::Transfer:
687                        {
688                                bool tmp;
689                                CTransferStatus status = engine_.transfer_status_.Get(tmp);
690                                if (!status.empty() && !status.madeProgress) {
691                                        if (m_pCurOpData && m_pCurOpData->opId == Command::transfer) {
692                                                CSftpFileTransferOpData *pData = static_cast<CSftpFileTransferOpData *>(m_pCurOpData);
693                                                if (pData->download) {
694                                                        if (message->value > 0)
695                                                                engine_.transfer_status_.SetMadeProgress();
696                                                }
697                                                else
698                                                {
699                                                        if (status.currentOffset > status.startOffset + 65565)
700                                                                engine_.transfer_status_.SetMadeProgress();
701                                                }
702                                        }
703                                }
704
705                                engine_.transfer_status_.Update(message->value);
706                        }
707                        break;
708                case sftpEvent::Recv:
709                        SetActive(CFileZillaEngine::recv);
710                        break;
711                case sftpEvent::Send:
712                        SetActive(CFileZillaEngine::send);
713                        break;
714                case sftpEvent::UsedQuotaRecv:
715                        OnQuotaRequest(CRateLimiter::inbound);
716                        break;
717                case sftpEvent::UsedQuotaSend:
718                        OnQuotaRequest(CRateLimiter::outbound);
719                        break;
720                case sftpEvent::KexAlgorithm:
721                        m_sftpEncryptionDetails.kexAlgorithm = message->text;
722                        break;
723                case sftpEvent::KexHash:
724                        m_sftpEncryptionDetails.kexHash = message->text;
725                        break;
726                case sftpEvent::KexCurve:
727                        m_sftpEncryptionDetails.kexCurve = message->text;
728                        break;
729                case sftpEvent::CipherClientToServer:
730                        m_sftpEncryptionDetails.cipherClientToServer = message->text;
731                        break;
732                case sftpEvent::CipherServerToClient:
733                        m_sftpEncryptionDetails.cipherServerToClient = message->text;
734                        break;
735                case sftpEvent::MacClientToServer:
736                        m_sftpEncryptionDetails.macClientToServer = message->text;
737                        break;
738                case sftpEvent::MacServerToClient:
739                        m_sftpEncryptionDetails.macServerToClient = message->text;
740                        break;
741                case sftpEvent::Hostkey:
742                        m_sftpEncryptionDetails.hostKey = message->text;
743                        break;
744                default:
745                        wxFAIL_MSG(_T("given notification codes not handled"));
746                        break;
747                }
748                delete message;
749        }
750}
751
752void CSftpControlSocket::OnTerminate()
753{
754        if (m_pProcess) {
755                DoClose();
756        }
757}
758
759bool CSftpControlSocket::SendCommand(wxString const& cmd, const wxString& show)
760{
761        SetWait(true);
762
763        if (!show.empty())
764                LogMessageRaw(MessageType::Command, show);
765        else
766                LogMessageRaw(MessageType::Command, cmd);
767
768        // Check for newlines in command
769        // a command like "ls\nrm foo/bar" is dangerous
770        if (cmd.Find('\n') != -1 ||
771                cmd.Find('\r') != -1)
772        {
773                LogMessage(MessageType::Debug_Warning, _T("Command containing newline characters, aborting"));
774                ResetOperation(FZ_REPLY_INTERNALERROR);
775                return false;
776        }
777
778        return AddToStream(cmd + _T("\n"));
779}
780
781bool CSftpControlSocket::AddToStream(const wxString& cmd, bool force_utf8 /*=false*/)
782{
783        if (!m_pProcess)
784                return false;
785
786        wxCharBuffer const str = ConvToServer(cmd, force_utf8);
787        if (!str) {
788                LogMessage(MessageType::Error, _("Could not convert command to server encoding"));
789                return false;
790        }
791
792        unsigned int len = strlen(str);
793        return m_pProcess->write(str, len);
794}
795
796bool CSftpControlSocket::SetAsyncRequestReply(CAsyncRequestNotification *pNotification)
797{
798        if (m_pCurOpData)
799        {
800                if (!m_pCurOpData->waitForAsyncRequest)
801                {
802                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Not waiting for request reply, ignoring request reply %d"), pNotification->GetRequestID());
803                        return false;
804                }
805                m_pCurOpData->waitForAsyncRequest = false;
806        }
807
808        RequestId const requestId = pNotification->GetRequestID();
809        switch(requestId)
810        {
811        case reqId_fileexists:
812                {
813                        CFileExistsNotification *pFileExistsNotification = static_cast<CFileExistsNotification *>(pNotification);
814                        return SetFileExistsAction(pFileExistsNotification);
815                }
816        case reqId_hostkey:
817        case reqId_hostkeyChanged:
818                {
819                        if (GetCurrentCommandId() != Command::connect ||
820                                !m_pCurrentServer)
821                        {
822                                LogMessage(MessageType::Debug_Info, _T("SetAsyncRequestReply called to wrong time"));
823                                return false;
824                        }
825
826                        CHostKeyNotification *pHostKeyNotification = static_cast<CHostKeyNotification *>(pNotification);
827                        wxString show;
828                        if (requestId == reqId_hostkey)
829                                show = _("Trust new Hostkey:");
830                        else
831                                show = _("Trust changed Hostkey:");
832                        show += ' ';
833                        if (!pHostKeyNotification->m_trust)
834                        {
835                                SendCommand(_T(""), show + _("No"));
836                                if (m_pCurOpData && m_pCurOpData->opId == Command::connect)
837                                {
838                                        CSftpConnectOpData *pData = static_cast<CSftpConnectOpData *>(m_pCurOpData);
839                                        pData->criticalFailure = true;
840                                }
841                        }
842                        else if (pHostKeyNotification->m_alwaysTrust)
843                                SendCommand(_T("y"), show + _("Yes"));
844                        else
845                                SendCommand(_T("n"), show + _("Once"));
846                }
847                break;
848        case reqId_interactiveLogin:
849                {
850                        CInteractiveLoginNotification *pInteractiveLoginNotification = static_cast<CInteractiveLoginNotification *>(pNotification);
851
852                        if (!pInteractiveLoginNotification->passwordSet) {
853                                DoClose(FZ_REPLY_CANCELED);
854                                return false;
855                        }
856                        const wxString pass = pInteractiveLoginNotification->server.GetPass();
857                        m_pCurrentServer->SetUser(m_pCurrentServer->GetUser(), pass);
858                        wxString show = _T("Pass: ");
859                        show.Append('*', pass.Length());
860                        SendCommand(pass, show);
861                }
862                break;
863        default:
864                LogMessage(MessageType::Debug_Warning, _T("Unknown async request reply id: %d"), requestId);
865                return false;
866        }
867
868        return true;
869}
870
871class CSftpListOpData final : public COpData
872{
873public:
874        CSftpListOpData()
875                : COpData(Command::list)
876                , pParser()
877                , refresh()
878                , fallback_to_current()
879                , mtime_index()
880        {
881        }
882
883        virtual ~CSftpListOpData()
884        {
885                delete pParser;
886        }
887
888        CDirectoryListingParser* pParser;
889
890        CServerPath path;
891        wxString subDir;
892
893        // Set to true to get a directory listing even if a cache
894        // lookup can be made after finding out true remote directory
895        bool refresh;
896        bool fallback_to_current;
897
898        CDirectoryListing directoryListing;
899        int mtime_index;
900};
901
902enum listStates
903{
904        list_init = 0,
905        list_waitcwd,
906        list_list,
907        list_mtime
908};
909
910int CSftpControlSocket::List(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, int flags /*=0*/)
911{
912        CServerPath newPath = m_CurrentPath;
913        if (!path.empty()) {
914                newPath = path;
915        }
916        if (!newPath.ChangePath(subDir)) {
917                newPath.clear();
918        }
919
920        if (newPath.empty()) {
921                LogMessage(MessageType::Status, _("Retrieving directory listing..."));
922        }
923        else {
924                LogMessage(MessageType::Status, _("Retrieving directory listing of \"%s\"..."), newPath.GetPath());
925        }
926
927        if (m_pCurOpData) {
928                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("List called from other command"));
929        }
930
931        if (!m_pCurrentServer) {
932                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurrenServer == 0"));
933                ResetOperation(FZ_REPLY_INTERNALERROR);
934                return FZ_REPLY_ERROR;
935        }
936
937        CSftpListOpData *pData = new CSftpListOpData;
938        pData->pNextOpData = m_pCurOpData;
939        m_pCurOpData = pData;
940
941        pData->opState = list_waitcwd;
942
943        if (path.GetType() == DEFAULT)
944                path.SetType(m_pCurrentServer->GetType());
945        pData->path = path;
946        pData->subDir = subDir;
947        pData->refresh = (flags & LIST_FLAG_REFRESH) != 0;
948        pData->fallback_to_current = !path.empty() && (flags & LIST_FLAG_FALLBACK_CURRENT) != 0;
949
950        int res = ChangeDir(path, subDir, (flags & LIST_FLAG_LINK) != 0);
951        if (res != FZ_REPLY_OK)
952                return res;
953
954        return ParseSubcommandResult(FZ_REPLY_OK);
955}
956
957int CSftpControlSocket::ListParseResponse(bool successful, const wxString& reply)
958{
959        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ListParseResponse(%s)"), reply);
960
961        if (!m_pCurOpData)
962        {
963                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
964                ResetOperation(FZ_REPLY_INTERNALERROR);
965                return FZ_REPLY_ERROR;
966        }
967
968        CSftpListOpData *pData = static_cast<CSftpListOpData *>(m_pCurOpData);
969        if (!pData)
970        {
971                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData of wrong type"));
972                ResetOperation(FZ_REPLY_INTERNALERROR);
973                return FZ_REPLY_ERROR;
974        }
975
976        if (pData->opState == list_list)
977        {
978                if (!successful)
979                {
980                        ResetOperation(FZ_REPLY_ERROR);
981                        return FZ_REPLY_ERROR;
982                }
983
984                if (!pData->pParser)
985                {
986                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("pData->pParser is 0"));
987                        ResetOperation(FZ_REPLY_INTERNALERROR);
988                        return FZ_REPLY_ERROR;
989                }
990
991                pData->directoryListing = pData->pParser->Parse(m_CurrentPath);
992
993                int res = ListCheckTimezoneDetection();
994                if (res != FZ_REPLY_OK)
995                        return res;
996
997                engine_.GetDirectoryCache().Store(pData->directoryListing, *m_pCurrentServer);
998
999                engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1000
1001                ResetOperation(FZ_REPLY_OK);
1002                return FZ_REPLY_OK;
1003        }
1004        else if (pData->opState == list_mtime) {
1005                if (successful && !reply.empty()) {
1006                        time_t seconds = 0;
1007                        bool parsed = true;
1008                        for (unsigned int i = 0; i < reply.Len(); ++i) {
1009                                wxChar c = reply[i];
1010                                if (c < '0' || c > '9') {
1011                                        parsed = false;
1012                                        break;
1013                                }
1014                                seconds *= 10;
1015                                seconds += c - '0';
1016                        }
1017                        if (parsed) {
1018                                fz::datetime date(seconds, fz::datetime::seconds);
1019                                if (date.empty()) {
1020                                        wxASSERT(pData->directoryListing[pData->mtime_index].has_date());
1021                                        fz::datetime listTime = pData->directoryListing[pData->mtime_index].time;
1022                                        listTime -= fz::duration::from_minutes(m_pCurrentServer->GetTimezoneOffset());
1023
1024                                        int serveroffset = static_cast<int>((date - listTime).get_seconds());
1025                                        if (!pData->directoryListing[pData->mtime_index].has_seconds()) {
1026                                                // Round offset to full minutes
1027                                                if (serveroffset < 0)
1028                                                        serveroffset -= 59;
1029                                                serveroffset -= serveroffset % 60;
1030                                        }
1031
1032                                        LogMessage(MessageType::Status, _("Timezone offset of server is %d seconds."), -serveroffset);
1033
1034                                        fz::duration span = fz::duration::from_seconds(serveroffset);
1035                                        const int count = pData->directoryListing.GetCount();
1036                                        for (int i = 0; i < count; ++i) {
1037                                                CDirentry& entry = pData->directoryListing[i];
1038                                                entry.time += span;
1039                                        }
1040
1041                                        // TODO: Correct cached listings
1042
1043                                        CServerCapabilities::SetCapability(*m_pCurrentServer, timezone_offset, yes, serveroffset);
1044                                }
1045                        }
1046                }
1047
1048                engine_.GetDirectoryCache().Store(pData->directoryListing, *m_pCurrentServer);
1049
1050                engine_.SendDirectoryListingNotification(m_CurrentPath, !pData->pNextOpData, true, false);
1051
1052                ResetOperation(FZ_REPLY_OK);
1053                return FZ_REPLY_OK;
1054        }
1055
1056        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("ListParseResponse called at inproper time: %d"), pData->opState);
1057        ResetOperation(FZ_REPLY_INTERNALERROR);
1058        return FZ_REPLY_ERROR;
1059}
1060
1061int CSftpControlSocket::ListParseEntry(const wxString& entry)
1062{
1063        if (!m_pCurOpData) {
1064                LogMessageRaw(MessageType::RawList, entry);
1065                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Empty m_pCurOpData"));
1066                ResetOperation(FZ_REPLY_INTERNALERROR);
1067                return FZ_REPLY_ERROR;
1068        }
1069
1070        if (m_pCurOpData->opId != Command::list) {
1071                LogMessageRaw(MessageType::RawList, entry);
1072                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Listentry received, but current operation is not Command::list"));
1073                ResetOperation(FZ_REPLY_INTERNALERROR);
1074                return FZ_REPLY_ERROR;
1075        }
1076
1077        CSftpListOpData *pData = static_cast<CSftpListOpData *>(m_pCurOpData);
1078        if (!pData) {
1079                LogMessageRaw(MessageType::RawList, entry);
1080                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData of wrong type"));
1081                ResetOperation(FZ_REPLY_INTERNALERROR);
1082                return FZ_REPLY_ERROR;
1083        }
1084
1085        if (pData->opState != list_list) {
1086                LogMessageRaw(MessageType::RawList, entry);
1087                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("ListParseResponse called at inproper time: %d"), pData->opState);
1088                ResetOperation(FZ_REPLY_INTERNALERROR);
1089                return FZ_REPLY_ERROR;
1090        }
1091
1092        if (!pData->pParser)
1093        {
1094                LogMessageRaw(MessageType::RawList, entry);
1095                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("pData->pParser is 0"));
1096                ResetOperation(FZ_REPLY_INTERNALERROR);
1097                return FZ_REPLY_INTERNALERROR;
1098        }
1099
1100        if (entry.Find('\r') != -1 || entry.Find('\n') != -1)
1101        {
1102                LogMessageRaw(MessageType::RawList, entry);
1103                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Listing entry contains \\r at pos %d and \\n at pos %d. Please contect FileZilla team."), entry.Find('\r'), entry.Find('\n'));
1104                ResetOperation(FZ_REPLY_INTERNALERROR);
1105                return FZ_REPLY_INTERNALERROR;
1106        }
1107
1108        pData->pParser->AddLine(entry.c_str());
1109
1110        return FZ_REPLY_WOULDBLOCK;
1111}
1112
1113int CSftpControlSocket::ListSubcommandResult(int prevResult)
1114{
1115        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ListSubcommandResult()"));
1116
1117        if (!m_pCurOpData)
1118        {
1119                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1120                ResetOperation(FZ_REPLY_INTERNALERROR);
1121                return FZ_REPLY_ERROR;
1122        }
1123
1124        CSftpListOpData *pData = static_cast<CSftpListOpData *>(m_pCurOpData);
1125        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
1126
1127        if (pData->opState != list_waitcwd)
1128        {
1129                ResetOperation(FZ_REPLY_INTERNALERROR);
1130                return FZ_REPLY_ERROR;
1131        }
1132
1133        if (prevResult != FZ_REPLY_OK)
1134        {
1135                if (pData->fallback_to_current)
1136                {
1137                        // List current directory instead
1138                        pData->fallback_to_current = false;
1139                        pData->path.clear();
1140                        pData->subDir = _T("");
1141                        int res = ChangeDir();
1142                        if (res != FZ_REPLY_OK)
1143                                return res;
1144                }
1145                else
1146                {
1147                        ResetOperation(prevResult);
1148                        return FZ_REPLY_ERROR;
1149                }
1150        }
1151
1152        if (pData->path.empty())
1153                pData->path = m_CurrentPath;
1154
1155        if (!pData->refresh)
1156        {
1157                wxASSERT(!pData->pNextOpData);
1158
1159                // Do a cache lookup now that we know the correct directory
1160
1161                int hasUnsureEntries;
1162                bool is_outdated = false;
1163                bool found = engine_.GetDirectoryCache().DoesExist(*m_pCurrentServer, m_CurrentPath, hasUnsureEntries, is_outdated);
1164                if (found)
1165                {
1166                        // We're done if listins is recent and has no outdated entries
1167                        if (!is_outdated && !hasUnsureEntries)
1168                        {
1169                                engine_.SendDirectoryListingNotification(m_CurrentPath, true, false, false);
1170
1171                                ResetOperation(FZ_REPLY_OK);
1172
1173                                return FZ_REPLY_OK;
1174                        }
1175                }
1176        }
1177
1178        if (!pData->holdsLock)
1179        {
1180                if (!TryLockCache(lock_list, m_CurrentPath))
1181                        return FZ_REPLY_WOULDBLOCK;
1182        }
1183
1184        pData->opState = list_list;
1185
1186        return SendNextCommand();
1187}
1188
1189int CSftpControlSocket::ListSend()
1190{
1191        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ListSend()"));
1192
1193        if (!m_pCurOpData)
1194        {
1195                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1196                ResetOperation(FZ_REPLY_INTERNALERROR);
1197                return FZ_REPLY_ERROR;
1198        }
1199
1200        CSftpListOpData *pData = static_cast<CSftpListOpData *>(m_pCurOpData);
1201        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
1202
1203        if (pData->opState == list_list) {
1204                pData->pParser = new CDirectoryListingParser(this, *m_pCurrentServer, listingEncoding::unknown, true);
1205                pData->pParser->SetTimezoneOffset(GetTimezoneOffset());
1206                if (!SendCommand(_T("ls")))
1207                        return FZ_REPLY_ERROR;
1208                return FZ_REPLY_WOULDBLOCK;
1209        }
1210        else if (pData->opState == list_mtime) {
1211                LogMessage(MessageType::Status, _("Calculating timezone offset of server..."));
1212                const wxString& name = pData->directoryListing[pData->mtime_index].name;
1213                wxString quotedFilename = QuoteFilename(pData->directoryListing.path.FormatFilename(name, true));
1214                if (!SendCommand(_T("mtime ") + WildcardEscape(quotedFilename),
1215                        _T("mtime ") + quotedFilename))
1216                        return FZ_REPLY_ERROR;
1217                return FZ_REPLY_WOULDBLOCK;
1218        }
1219
1220        LogMessage(MessageType::Debug_Warning, _T("Unknown opStatein CSftpControlSocket::ListSend"));
1221        ResetOperation(FZ_REPLY_INTERNALERROR);
1222        return FZ_REPLY_ERROR;
1223}
1224
1225class CSftpChangeDirOpData : public CChangeDirOpData
1226{
1227};
1228
1229enum cwdStates
1230{
1231        cwd_init = 0,
1232        cwd_pwd,
1233        cwd_cwd,
1234        cwd_cwd_subdir
1235};
1236
1237int CSftpControlSocket::ChangeDir(CServerPath path /*=CServerPath()*/, wxString subDir /*=_T("")*/, bool link_discovery /*=false*/)
1238{
1239        cwdStates state = cwd_init;
1240
1241        if (path.GetType() == DEFAULT)
1242                path.SetType(m_pCurrentServer->GetType());
1243
1244        CServerPath target;
1245        if (path.empty()) {
1246                if (m_CurrentPath.empty())
1247                        state = cwd_pwd;
1248                else
1249                        return FZ_REPLY_OK;
1250        }
1251        else {
1252                if (!subDir.empty()) {
1253                        // Check if the target is in cache already
1254                        target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, subDir);
1255                        if (!target.empty()) {
1256                                if (m_CurrentPath == target)
1257                                        return FZ_REPLY_OK;
1258
1259                                path = target;
1260                                subDir = _T("");
1261                                state = cwd_cwd;
1262                        }
1263                        else {
1264                                // Target unknown, check for the parent's target
1265                                target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, _T(""));
1266                                if (m_CurrentPath == path || (!target.empty() && target == m_CurrentPath)) {
1267                                        target.clear();
1268                                        state = cwd_cwd_subdir;
1269                                }
1270                                else
1271                                        state = cwd_cwd;
1272                        }
1273                }
1274                else {
1275                        target = engine_.GetPathCache().Lookup(*m_pCurrentServer, path, _T(""));
1276                        if (m_CurrentPath == path || (!target.empty() && target == m_CurrentPath))
1277                                return FZ_REPLY_OK;
1278                        state = cwd_cwd;
1279                }
1280        }
1281
1282        CSftpChangeDirOpData *pData = new CSftpChangeDirOpData;
1283        pData->pNextOpData = m_pCurOpData;
1284        pData->opState = state;
1285        pData->path = path;
1286        pData->subDir = subDir;
1287        pData->target = target;
1288        pData->link_discovery = link_discovery;
1289
1290        if (pData->pNextOpData && pData->pNextOpData->opId == Command::transfer &&
1291                !static_cast<CSftpFileTransferOpData *>(pData->pNextOpData)->download)
1292        {
1293                pData->tryMkdOnFail = true;
1294                wxASSERT(subDir.empty());
1295        }
1296
1297        m_pCurOpData = pData;
1298
1299        return SendNextCommand();
1300}
1301
1302int CSftpControlSocket::ChangeDirParseResponse(bool successful, const wxString& reply)
1303{
1304        if (!m_pCurOpData)
1305        {
1306                ResetOperation(FZ_REPLY_ERROR);
1307                return FZ_REPLY_ERROR;
1308        }
1309        CSftpChangeDirOpData *pData = static_cast<CSftpChangeDirOpData *>(m_pCurOpData);
1310
1311        bool error = false;
1312        switch (pData->opState)
1313        {
1314        case cwd_pwd:
1315                if (!successful || reply.empty())
1316                        error = true;
1317                if (ParsePwdReply(reply))
1318                {
1319                        ResetOperation(FZ_REPLY_OK);
1320                        return FZ_REPLY_OK;
1321                }
1322                else
1323                        error = true;
1324                break;
1325        case cwd_cwd:
1326                if (!successful)
1327                {
1328                        // Create remote directory if part of a file upload
1329                        if (pData->tryMkdOnFail)
1330                        {
1331                                pData->tryMkdOnFail = false;
1332                                int res = Mkdir(pData->path);
1333                                if (res != FZ_REPLY_OK)
1334                                        return res;
1335                        }
1336                        else
1337                                error = true;
1338                }
1339                else if (reply.empty())
1340                        error = true;
1341                else if (ParsePwdReply(reply)) {
1342                        engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path);
1343
1344                        if (pData->subDir.empty()) {
1345                                ResetOperation(FZ_REPLY_OK);
1346                                return FZ_REPLY_OK;
1347                        }
1348
1349                        pData->target.clear();
1350                        pData->opState = cwd_cwd_subdir;
1351                }
1352                else
1353                        error = true;
1354                break;
1355        case cwd_cwd_subdir:
1356                if (!successful || reply.empty())
1357                {
1358                        if (pData->link_discovery)
1359                        {
1360                                LogMessage(MessageType::Debug_Info, _T("Symlink does not link to a directory, probably a file"));
1361                                ResetOperation(FZ_REPLY_LINKNOTDIR);
1362                                return FZ_REPLY_ERROR;
1363                        }
1364                        else
1365                                error = true;
1366                }
1367                else if (ParsePwdReply(reply)) {
1368                        engine_.GetPathCache().Store(*m_pCurrentServer, m_CurrentPath, pData->path, pData->subDir);
1369
1370                        ResetOperation(FZ_REPLY_OK);
1371                        return FZ_REPLY_OK;
1372                }
1373                else
1374                        error = true;
1375                break;
1376        default:
1377                error = true;
1378                break;
1379        }
1380
1381        if (error)
1382        {
1383                ResetOperation(FZ_REPLY_ERROR);
1384                return FZ_REPLY_ERROR;
1385        }
1386
1387        return SendNextCommand();
1388}
1389
1390int CSftpControlSocket::ChangeDirSubcommandResult(int WXUNUSED(prevResult))
1391{
1392        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ChangeDirSubcommandResult()"));
1393
1394        return SendNextCommand();
1395}
1396
1397int CSftpControlSocket::ChangeDirSend()
1398{
1399        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ChangeDirSend()"));
1400
1401        if (!m_pCurOpData)
1402        {
1403                ResetOperation(FZ_REPLY_ERROR);
1404                return FZ_REPLY_ERROR;
1405        }
1406        CSftpChangeDirOpData *pData = static_cast<CSftpChangeDirOpData *>(m_pCurOpData);
1407
1408        wxString cmd;
1409        switch (pData->opState)
1410        {
1411        case cwd_pwd:
1412                cmd = _T("pwd");
1413                break;
1414        case cwd_cwd:
1415                if (pData->tryMkdOnFail && !pData->holdsLock)
1416                {
1417                        if (IsLocked(lock_mkdir, pData->path))
1418                        {
1419                                // Some other engine is already creating this directory or
1420                                // performing an action that will lead to its creation
1421                                pData->tryMkdOnFail = false;
1422                        }
1423                        if (!TryLockCache(lock_mkdir, pData->path))
1424                                return FZ_REPLY_WOULDBLOCK;
1425                }
1426                cmd = _T("cd ") + QuoteFilename(pData->path.GetPath());
1427                m_CurrentPath.clear();
1428                break;
1429        case cwd_cwd_subdir:
1430                if (pData->subDir.empty())
1431                {
1432                        ResetOperation(FZ_REPLY_INTERNALERROR);
1433                        return FZ_REPLY_ERROR;
1434                }
1435                else
1436                        cmd = _T("cd ") + QuoteFilename(pData->subDir);
1437                m_CurrentPath.clear();
1438                break;
1439        }
1440
1441        if (!cmd.empty())
1442                if (!SendCommand(cmd))
1443                        return FZ_REPLY_ERROR;
1444
1445        return FZ_REPLY_WOULDBLOCK;
1446}
1447
1448void CSftpControlSocket::ProcessReply(int result, const wxString& reply /*=_T("")*/)
1449{
1450        Command commandId = GetCurrentCommandId();
1451        switch (commandId)
1452        {
1453        case Command::connect:
1454                ConnectParseResponse(result == FZ_REPLY_OK, reply);
1455                break;
1456        case Command::list:
1457                ListParseResponse(result == FZ_REPLY_OK, reply);
1458                break;
1459        case Command::transfer:
1460                FileTransferParseResponse(result, reply);
1461                break;
1462        case Command::cwd:
1463                ChangeDirParseResponse(result == FZ_REPLY_OK, reply);
1464                break;
1465        case Command::mkdir:
1466                MkdirParseResponse(result == FZ_REPLY_OK, reply);
1467                break;
1468        case Command::del:
1469                DeleteParseResponse(result == FZ_REPLY_OK, reply);
1470                break;
1471        case Command::removedir:
1472                RemoveDirParseResponse(result == FZ_REPLY_OK, reply);
1473                break;
1474        case Command::chmod:
1475                ChmodParseResponse(result == FZ_REPLY_OK, reply);
1476                break;
1477        case Command::rename:
1478                RenameParseResponse(result == FZ_REPLY_OK, reply);
1479                break;
1480        default:
1481                LogMessage(MessageType::Debug_Warning, _T("No action for parsing replies to command %d"), (int)commandId);
1482                ResetOperation(FZ_REPLY_INTERNALERROR);
1483                break;
1484        }
1485}
1486
1487int CSftpControlSocket::ResetOperation(int nErrorCode)
1488{
1489        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ResetOperation(%d)"), nErrorCode);
1490
1491        if (m_pCurOpData && m_pCurOpData->opId == Command::connect)
1492        {
1493                CSftpConnectOpData *pData = static_cast<CSftpConnectOpData *>(m_pCurOpData);
1494                if (pData->opState == connect_init && (nErrorCode & FZ_REPLY_CANCELED) != FZ_REPLY_CANCELED)
1495                        LogMessage(MessageType::Error, _("fzsftp could not be started"));
1496                if (pData->criticalFailure)
1497                        nErrorCode |= FZ_REPLY_CRITICALERROR;
1498        }
1499        if (m_pCurOpData && m_pCurOpData->opId == Command::del && !(nErrorCode & FZ_REPLY_DISCONNECTED))
1500        {
1501                CSftpDeleteOpData *pData = static_cast<CSftpDeleteOpData *>(m_pCurOpData);
1502                if (pData->m_needSendListing)
1503                        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
1504        }
1505
1506        return CControlSocket::ResetOperation(nErrorCode);
1507}
1508
1509int CSftpControlSocket::SendNextCommand()
1510{
1511        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::SendNextCommand()"));
1512        if (!m_pCurOpData)
1513        {
1514                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("SendNextCommand called without active operation"));
1515                ResetOperation(FZ_REPLY_ERROR);
1516                return FZ_REPLY_ERROR;
1517        }
1518
1519        if (m_pCurOpData->waitForAsyncRequest)
1520        {
1521                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Waiting for async request, ignoring SendNextCommand"));
1522                return FZ_REPLY_WOULDBLOCK;
1523        }
1524
1525        switch (m_pCurOpData->opId)
1526        {
1527        case Command::connect:
1528                return ConnectSend();
1529        case Command::list:
1530                return ListSend();
1531        case Command::transfer:
1532                return FileTransferSend();
1533        case Command::cwd:
1534                return ChangeDirSend();
1535        case Command::mkdir:
1536                return MkdirSend();
1537        case Command::rename:
1538                return RenameSend();
1539        case Command::chmod:
1540                return ChmodSend();
1541        case Command::del:
1542                return DeleteSend();
1543        default:
1544                LogMessage(MessageType::Debug_Warning, __TFILE__, __LINE__, _T("Unknown opID (%d) in SendNextCommand"), m_pCurOpData->opId);
1545                ResetOperation(FZ_REPLY_INTERNALERROR);
1546                break;
1547        }
1548
1549        return FZ_REPLY_ERROR;
1550}
1551
1552int CSftpControlSocket::FileTransfer(const wxString localFile, const CServerPath &remotePath,
1553                                                                        const wxString &remoteFile, bool download,
1554                                                                        const CFileTransferCommand::t_transferSettings& transferSettings)
1555{
1556        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::FileTransfer(...)"));
1557
1558        if (localFile.empty()) {
1559                if (!download)
1560                        ResetOperation(FZ_REPLY_CRITICALERROR | FZ_REPLY_NOTSUPPORTED);
1561                else
1562                        ResetOperation(FZ_REPLY_SYNTAXERROR);
1563                return FZ_REPLY_ERROR;
1564        }
1565
1566        if (download) {
1567                wxString filename = remotePath.FormatFilename(remoteFile);
1568                LogMessage(MessageType::Status, _("Starting download of %s"), filename);
1569        }
1570        else {
1571                LogMessage(MessageType::Status, _("Starting upload of %s"), localFile);
1572        }
1573        if (m_pCurOpData) {
1574                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("deleting nonzero pData"));
1575                delete m_pCurOpData;
1576        }
1577
1578        CSftpFileTransferOpData *pData = new CSftpFileTransferOpData(download, localFile, remoteFile, remotePath);
1579        m_pCurOpData = pData;
1580
1581        pData->transferSettings = transferSettings;
1582
1583        int64_t size;
1584        bool isLink;
1585        if (fz::local_filesys::get_file_info(fz::to_native(pData->localFile), isLink, &size, 0, 0) == fz::local_filesys::file)
1586                pData->localFileSize = size;
1587
1588        pData->opState = filetransfer_waitcwd;
1589
1590        if (pData->remotePath.GetType() == DEFAULT)
1591                pData->remotePath.SetType(m_pCurrentServer->GetType());
1592
1593        int res = ChangeDir(pData->remotePath);
1594        if (res != FZ_REPLY_OK)
1595                return res;
1596
1597        return ParseSubcommandResult(FZ_REPLY_OK);
1598}
1599
1600int CSftpControlSocket::FileTransferSubcommandResult(int prevResult)
1601{
1602        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::FileTransferSubcommandResult()"));
1603
1604        if (!m_pCurOpData) {
1605                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1606                ResetOperation(FZ_REPLY_INTERNALERROR);
1607                return FZ_REPLY_ERROR;
1608        }
1609
1610        CSftpFileTransferOpData *pData = static_cast<CSftpFileTransferOpData *>(m_pCurOpData);
1611
1612        if (pData->opState == filetransfer_waitcwd) {
1613                if (prevResult == FZ_REPLY_OK) {
1614                        CDirentry entry;
1615                        bool dirDidExist;
1616                        bool matchedCase;
1617                        bool found = engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
1618                        if (!found) {
1619                                if (!dirDidExist)
1620                                        pData->opState = filetransfer_waitlist;
1621                                else if (pData->download &&
1622                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS))
1623                                {
1624                                        pData->opState = filetransfer_mtime;
1625                                }
1626                                else
1627                                        pData->opState = filetransfer_transfer;
1628                        }
1629                        else {
1630                                if (entry.is_unsure())
1631                                        pData->opState = filetransfer_waitlist;
1632                                else {
1633                                        if (matchedCase) {
1634                                                pData->remoteFileSize = entry.size;
1635                                                if (entry.has_date())
1636                                                        pData->fileTime = entry.time;
1637
1638                                                if (pData->download && !entry.has_time() &&
1639                                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS))
1640                                                {
1641                                                        pData->opState = filetransfer_mtime;
1642                                                }
1643                                                else
1644                                                        pData->opState = filetransfer_transfer;
1645                                        }
1646                                        else
1647                                                pData->opState = filetransfer_mtime;
1648                                }
1649                        }
1650                        if (pData->opState == filetransfer_waitlist) {
1651                                int res = List(CServerPath(), _T(""), LIST_FLAG_REFRESH);
1652                                if (res != FZ_REPLY_OK)
1653                                        return res;
1654                                ResetOperation(FZ_REPLY_INTERNALERROR);
1655                                return FZ_REPLY_ERROR;
1656                        }
1657                        else if (pData->opState == filetransfer_transfer) {
1658                                int res = CheckOverwriteFile();
1659                                if (res != FZ_REPLY_OK)
1660                                        return res;
1661                        }
1662                }
1663                else {
1664                        pData->tryAbsolutePath = true;
1665                        pData->opState = filetransfer_mtime;
1666                }
1667        }
1668        else if (pData->opState == filetransfer_waitlist) {
1669                if (prevResult == FZ_REPLY_OK) {
1670                        CDirentry entry;
1671                        bool dirDidExist;
1672                        bool matchedCase;
1673                        bool found = engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dirDidExist, matchedCase);
1674                        if (!found) {
1675                                if (!dirDidExist)
1676                                        pData->opState = filetransfer_mtime;
1677                                else if (pData->download &&
1678                                        engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS))
1679                                {
1680                                        pData->opState = filetransfer_mtime;
1681                                }
1682                                else
1683                                        pData->opState = filetransfer_transfer;
1684                        }
1685                        else {
1686                                if (matchedCase && !entry.is_unsure()) {
1687                                        pData->remoteFileSize = entry.size;
1688                                        if (!entry.has_date())
1689                                                pData->fileTime = entry.time;
1690
1691                                        if (pData->download && !entry.has_time() &&
1692                                                engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS))
1693                                        {
1694                                                pData->opState = filetransfer_mtime;
1695                                        }
1696                                        else
1697                                                pData->opState = filetransfer_transfer;
1698                                }
1699                                else
1700                                        pData->opState = filetransfer_mtime;
1701                        }
1702                        if (pData->opState == filetransfer_transfer) {
1703                                int res = CheckOverwriteFile();
1704                                if (res != FZ_REPLY_OK)
1705                                        return res;
1706                        }
1707                }
1708                else
1709                        pData->opState = filetransfer_mtime;
1710        }
1711        else {
1712                LogMessage(MessageType::Debug_Warning, _T("  Unknown opState (%d)"), pData->opState);
1713                ResetOperation(FZ_REPLY_INTERNALERROR);
1714                return FZ_REPLY_ERROR;
1715        }
1716
1717        return SendNextCommand();
1718}
1719
1720int CSftpControlSocket::FileTransferSend()
1721{
1722        LogMessage(MessageType::Debug_Verbose, _T("FileTransferSend()"));
1723
1724        if (!m_pCurOpData)
1725        {
1726                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1727                ResetOperation(FZ_REPLY_INTERNALERROR);
1728                return FZ_REPLY_ERROR;
1729        }
1730
1731        CSftpFileTransferOpData *pData = static_cast<CSftpFileTransferOpData *>(m_pCurOpData);
1732
1733        if (pData->opState == filetransfer_transfer)
1734        {
1735                wxString cmd;
1736                if (pData->resume)
1737                        cmd = _T("re");
1738                if (pData->download)
1739                {
1740                        if (!pData->resume)
1741                                CreateLocalDir(pData->localFile);
1742
1743                        engine_.transfer_status_.Init(pData->remoteFileSize, pData->resume ? pData->localFileSize : 0, false);
1744                        cmd += _T("get ");
1745                        cmd += QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath)) + _T(" ");
1746
1747                        wxString localFile = QuoteFilename(pData->localFile);
1748                        wxString logstr = cmd;
1749                        logstr += localFile;
1750                        LogMessageRaw(MessageType::Command, logstr);
1751
1752                        if (!AddToStream(cmd) || !AddToStream(localFile + _T("\n"), true)) {
1753                                ResetOperation(FZ_REPLY_ERROR);
1754                                return FZ_REPLY_ERROR;
1755                        }
1756                }
1757                else {
1758                        engine_.transfer_status_.Init(pData->localFileSize, pData->resume ? pData->remoteFileSize : 0, false);
1759                        cmd += _T("put ");
1760
1761                        wxString logstr = cmd;
1762                        wxString localFile = QuoteFilename(pData->localFile) + _T(" ");
1763                        wxString remoteFile = QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath));
1764
1765                        logstr += localFile;
1766                        logstr += remoteFile;
1767                        LogMessageRaw(MessageType::Command, logstr);
1768
1769                        if (!AddToStream(cmd) || !AddToStream(localFile, true) ||
1770                                !AddToStream(remoteFile + _T("\n")))
1771                        {
1772                                ResetOperation(FZ_REPLY_ERROR);
1773                                return FZ_REPLY_ERROR;
1774                        }
1775                }
1776                engine_.transfer_status_.SetStartTime();
1777
1778                pData->transferInitiated = true;
1779        }
1780        else if (pData->opState == filetransfer_mtime)
1781        {
1782                wxString quotedFilename = QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath));
1783                if (!SendCommand(_T("mtime ") + WildcardEscape(quotedFilename),
1784                        _T("mtime ") + quotedFilename))
1785                        return FZ_REPLY_ERROR;
1786        }
1787        else if (pData->opState == filetransfer_chmtime)
1788        {
1789                wxASSERT(pData->fileTime.empty());
1790                if (pData->download)
1791                {
1792                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("  filetransfer_chmtime during download"));
1793                        ResetOperation(FZ_REPLY_INTERNALERROR);
1794                        return FZ_REPLY_ERROR;
1795                }
1796
1797                wxString quotedFilename = QuoteFilename(pData->remotePath.FormatFilename(pData->remoteFile, !pData->tryAbsolutePath));
1798                // Y2K38
1799                time_t ticks = pData->fileTime.get_time_t();
1800                wxString seconds = wxString::Format(_T("%d"), (int)ticks);
1801                if (!SendCommand(_T("chmtime ") + seconds + _T(" ") + WildcardEscape(quotedFilename),
1802                        _T("chmtime ") + seconds + _T(" ") + quotedFilename))
1803                        return FZ_REPLY_ERROR;
1804        }
1805
1806        return FZ_REPLY_WOULDBLOCK;
1807}
1808
1809int CSftpControlSocket::FileTransferParseResponse(int result, const wxString& reply)
1810{
1811        LogMessage(MessageType::Debug_Verbose, _T("FileTransferParseResponse(%d)"), result);
1812
1813        if (!m_pCurOpData) {
1814                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1815                ResetOperation(FZ_REPLY_INTERNALERROR);
1816                return FZ_REPLY_ERROR;
1817        }
1818
1819        CSftpFileTransferOpData *pData = static_cast<CSftpFileTransferOpData *>(m_pCurOpData);
1820
1821        if (pData->opState == filetransfer_transfer) {
1822                if (result != FZ_REPLY_OK) {
1823                        ResetOperation(result);
1824                        return FZ_REPLY_ERROR;
1825                }
1826
1827                if (engine_.GetOptions().GetOptionVal(OPTION_PRESERVE_TIMESTAMPS)) {
1828                        if (pData->download) {
1829                                if (pData->fileTime.empty()) {
1830                                        if (!fz::local_filesys::set_modification_time(fz::to_native(pData->localFile), pData->fileTime))
1831                                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Could not set modification time"));
1832                                }
1833                        }
1834                        else {
1835                                pData->fileTime = fz::local_filesys::get_modification_time(fz::to_native(pData->localFile));
1836                                if (pData->fileTime.empty()) {
1837                                        pData->opState = filetransfer_chmtime;
1838                                        return SendNextCommand();
1839                                }
1840                        }
1841                }
1842        }
1843        else if (pData->opState == filetransfer_mtime) {
1844                if (result == FZ_REPLY_OK && !reply.empty()) {
1845                        time_t seconds = 0;
1846                        bool parsed = true;
1847                        for (unsigned int i = 0; i < reply.Len(); ++i) {
1848                                wxChar c = reply[i];
1849                                if (c < '0' || c > '9') {
1850                                        parsed = false;
1851                                        break;
1852                                }
1853                                seconds *= 10;
1854                                seconds += c - '0';
1855                        }
1856                        if (parsed) {
1857                                fz::datetime fileTime = fz::datetime(seconds, fz::datetime::seconds);
1858                                if (fileTime.empty()) {
1859                                        pData->fileTime = fileTime;
1860                                        pData->fileTime += fz::duration::from_minutes(m_pCurrentServer->GetTimezoneOffset());
1861                                }
1862                        }
1863                }
1864                pData->opState = filetransfer_transfer;
1865                int res = CheckOverwriteFile();
1866                if (res != FZ_REPLY_OK)
1867                        return res;
1868
1869                return SendNextCommand();
1870        }
1871        else if (pData->opState == filetransfer_chmtime) {
1872                if (pData->download) {
1873                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("  filetransfer_chmtime during download"));
1874                        ResetOperation(FZ_REPLY_INTERNALERROR);
1875                        return FZ_REPLY_ERROR;
1876                }
1877        }
1878        else {
1879                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("  Called at improper time: opState == %d"), pData->opState);
1880                ResetOperation(FZ_REPLY_INTERNALERROR);
1881                return FZ_REPLY_ERROR;
1882        }
1883
1884        ResetOperation(FZ_REPLY_OK);
1885        return FZ_REPLY_OK;
1886}
1887
1888int CSftpControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/)
1889{
1890        engine_.GetRateLimiter().RemoveObject(this);
1891
1892        if (m_pProcess) {
1893                m_pProcess->kill();
1894        }
1895
1896        if (m_pInputThread) {
1897                auto thread = m_pInputThread;
1898                m_pInputThread = 0;
1899                delete thread;
1900        }
1901        if (m_pProcess) {
1902                delete m_pProcess;
1903                m_pProcess = 0;
1904        }
1905        return CControlSocket::DoClose(nErrorCode);
1906}
1907
1908void CSftpControlSocket::Cancel()
1909{
1910        if (GetCurrentCommandId() != Command::none) {
1911                DoClose(FZ_REPLY_CANCELED);
1912        }
1913}
1914
1915enum mkdStates
1916{
1917        mkd_init = 0,
1918        mkd_findparent,
1919        mkd_mkdsub,
1920        mkd_cwdsub,
1921        mkd_tryfull
1922};
1923
1924int CSftpControlSocket::Mkdir(const CServerPath& path)
1925{
1926        /* Directory creation works like this: First find a parent directory into
1927         * which we can CWD, then create the subdirs one by one. If either part
1928         * fails, try MKD with the full path directly.
1929         */
1930
1931        if (!m_pCurOpData)
1932                LogMessage(MessageType::Status, _("Creating directory '%s'..."), path.GetPath());
1933
1934        CMkdirOpData *pData = new CMkdirOpData;
1935        pData->path = path;
1936
1937        if (!m_CurrentPath.empty()) {
1938                // Unless the server is broken, a directory already exists if current directory is a subdir of it.
1939                if (m_CurrentPath == path || m_CurrentPath.IsSubdirOf(path, false)) {
1940                        delete pData;
1941                        return FZ_REPLY_OK;
1942                }
1943
1944                if (m_CurrentPath.IsParentOf(path, false))
1945                        pData->commonParent = m_CurrentPath;
1946                else
1947                        pData->commonParent = path.GetCommonParent(m_CurrentPath);
1948        }
1949
1950        if (!path.HasParent())
1951                pData->opState = mkd_tryfull;
1952        else {
1953                pData->currentPath = path.GetParent();
1954                pData->segments.push_back(path.GetLastSegment());
1955
1956                if (pData->currentPath == m_CurrentPath)
1957                        pData->opState = mkd_mkdsub;
1958                else
1959                        pData->opState = mkd_findparent;
1960        }
1961
1962        pData->pNextOpData = m_pCurOpData;
1963        m_pCurOpData = pData;
1964
1965        return SendNextCommand();
1966}
1967
1968int CSftpControlSocket::MkdirParseResponse(bool successful, const wxString&)
1969{
1970        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::MkdirParseResonse"));
1971
1972        if (!m_pCurOpData)
1973        {
1974                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
1975                ResetOperation(FZ_REPLY_INTERNALERROR);
1976                return FZ_REPLY_ERROR;
1977        }
1978
1979        CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
1980        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
1981
1982        bool error = false;
1983        switch (pData->opState)
1984        {
1985        case mkd_findparent:
1986                if (successful) {
1987                        m_CurrentPath = pData->currentPath;
1988                        pData->opState = mkd_mkdsub;
1989                }
1990                else if (pData->currentPath == pData->commonParent)
1991                        pData->opState = mkd_tryfull;
1992                else if (pData->currentPath.HasParent()) {
1993                        pData->segments.push_back(pData->currentPath.GetLastSegment());
1994                        pData->currentPath = pData->currentPath.GetParent();
1995                }
1996                else
1997                        pData->opState = mkd_tryfull;
1998                break;
1999        case mkd_mkdsub:
2000                if (successful) {
2001                        if (pData->segments.empty()) {
2002                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("  pData->segments is empty"));
2003                                ResetOperation(FZ_REPLY_INTERNALERROR);
2004                                return FZ_REPLY_ERROR;
2005                        }
2006                        engine_.GetDirectoryCache().UpdateFile(*m_pCurrentServer, pData->currentPath, pData->segments.back(), true, CDirectoryCache::dir);
2007                        engine_.SendDirectoryListingNotification(pData->currentPath, false, true, false);
2008
2009                        pData->currentPath.AddSegment(pData->segments.back());
2010                        pData->segments.pop_back();
2011
2012                        if (pData->segments.empty()) {
2013                                ResetOperation(FZ_REPLY_OK);
2014                                return FZ_REPLY_OK;
2015                        }
2016                        else
2017                                pData->opState = mkd_cwdsub;
2018                }
2019                else
2020                        pData->opState = mkd_tryfull;
2021                break;
2022        case mkd_cwdsub:
2023                if (successful) {
2024                        m_CurrentPath = pData->currentPath;
2025                        pData->opState = mkd_mkdsub;
2026                }
2027                else
2028                        pData->opState = mkd_tryfull;
2029                break;
2030        case mkd_tryfull:
2031                if (!successful)
2032                        error = true;
2033                else {
2034                        ResetOperation(FZ_REPLY_OK);
2035                        return FZ_REPLY_OK;
2036                }
2037                break;
2038        default:
2039                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
2040                ResetOperation(FZ_REPLY_INTERNALERROR);
2041                return FZ_REPLY_ERROR;
2042        }
2043
2044        if (error) {
2045                ResetOperation(FZ_REPLY_ERROR);
2046                return FZ_REPLY_ERROR;
2047        }
2048
2049        return MkdirSend();
2050}
2051
2052int CSftpControlSocket::MkdirSend()
2053{
2054        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::MkdirSend"));
2055
2056        if (!m_pCurOpData) {
2057                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2058                ResetOperation(FZ_REPLY_INTERNALERROR);
2059                return FZ_REPLY_ERROR;
2060        }
2061
2062        CMkdirOpData *pData = static_cast<CMkdirOpData *>(m_pCurOpData);
2063        LogMessage(MessageType::Debug_Debug, _T("  state = %d"), pData->opState);
2064
2065        if (!pData->holdsLock) {
2066                if (!TryLockCache(lock_mkdir, pData->path))
2067                        return FZ_REPLY_WOULDBLOCK;
2068        }
2069
2070        bool res;
2071        switch (pData->opState)
2072        {
2073        case mkd_findparent:
2074        case mkd_cwdsub:
2075                m_CurrentPath.clear();
2076                res = SendCommand(_T("cd ") + QuoteFilename(pData->currentPath.GetPath()));
2077                break;
2078        case mkd_mkdsub:
2079                res = SendCommand(_T("mkdir ") + QuoteFilename(pData->segments.back()));
2080                break;
2081        case mkd_tryfull:
2082                res = SendCommand(_T("mkdir ") + QuoteFilename(pData->path.GetPath()));
2083                break;
2084        default:
2085                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
2086                ResetOperation(FZ_REPLY_INTERNALERROR);
2087                return FZ_REPLY_ERROR;
2088        }
2089
2090        if (!res)
2091                return FZ_REPLY_ERROR;
2092
2093        return FZ_REPLY_WOULDBLOCK;
2094}
2095
2096wxString CSftpControlSocket::QuoteFilename(wxString filename)
2097{
2098        filename.Replace(_T("\""), _T("\"\""));
2099        return _T("\"") + filename + _T("\"");
2100}
2101
2102int CSftpControlSocket::Delete(const CServerPath& path, std::deque<wxString>&& files)
2103{
2104        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::Delete"));
2105        wxASSERT(!m_pCurOpData);
2106        CSftpDeleteOpData *pData = new CSftpDeleteOpData();
2107        m_pCurOpData = pData;
2108        pData->path = path;
2109        pData->files = files;
2110
2111        // CFileZillaEnginePrivate should have checked this already
2112        wxASSERT(!files.empty());
2113
2114        return SendNextCommand();
2115}
2116
2117int CSftpControlSocket::DeleteParseResponse(bool successful, const wxString&)
2118{
2119        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::DeleteParseResponse"));
2120
2121        if (!m_pCurOpData) {
2122                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2123                ResetOperation(FZ_REPLY_INTERNALERROR);
2124                return FZ_REPLY_ERROR;
2125        }
2126
2127        CSftpDeleteOpData *pData = static_cast<CSftpDeleteOpData *>(m_pCurOpData);
2128
2129        if (!successful)
2130                pData->m_deleteFailed = true;
2131        else {
2132                const wxString& file = pData->files.front();
2133
2134                engine_.GetDirectoryCache().RemoveFile(*m_pCurrentServer, pData->path, file);
2135
2136                auto const now = fz::datetime::now();
2137                if (now.empty() && pData->m_time.empty() && (now - pData->m_time).get_seconds() >= 1) {
2138                        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
2139                        pData->m_time = now;
2140                        pData->m_needSendListing = false;
2141                }
2142                else
2143                        pData->m_needSendListing = true;
2144        }
2145
2146        pData->files.pop_front();
2147
2148        if (!pData->files.empty())
2149                return SendNextCommand();
2150
2151        return ResetOperation(pData->m_deleteFailed ? FZ_REPLY_ERROR : FZ_REPLY_OK);
2152}
2153
2154int CSftpControlSocket::DeleteSend()
2155{
2156        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::DeleteSend"));
2157
2158        if (!m_pCurOpData) {
2159                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2160                ResetOperation(FZ_REPLY_INTERNALERROR);
2161                return FZ_REPLY_ERROR;
2162        }
2163        CSftpDeleteOpData *pData = static_cast<CSftpDeleteOpData *>(m_pCurOpData);
2164
2165        const wxString& file = pData->files.front();
2166        if (file.empty()) {
2167                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty filename"));
2168                ResetOperation(FZ_REPLY_INTERNALERROR);
2169                return FZ_REPLY_ERROR;
2170        }
2171
2172        wxString filename = pData->path.FormatFilename(file);
2173        if (filename.empty()) {
2174                LogMessage(MessageType::Error, _("Filename cannot be constructed for directory %s and filename %s"), pData->path.GetPath(), file);
2175                ResetOperation(FZ_REPLY_ERROR);
2176                return FZ_REPLY_ERROR;
2177        }
2178
2179        if (!pData->m_time.empty())
2180                pData->m_time = fz::datetime::now();
2181
2182        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->path, file);
2183
2184        if (!SendCommand(_T("rm ") + WildcardEscape(QuoteFilename(filename)),
2185                          _T("rm ") + QuoteFilename(filename)))
2186                return FZ_REPLY_ERROR;
2187
2188        return FZ_REPLY_WOULDBLOCK;
2189}
2190
2191class CSftpRemoveDirOpData : public COpData
2192{
2193public:
2194        CSftpRemoveDirOpData()
2195                : COpData(Command::removedir)
2196        {
2197        }
2198
2199        virtual ~CSftpRemoveDirOpData() {}
2200
2201        CServerPath path;
2202        wxString subDir;
2203};
2204
2205int CSftpControlSocket::RemoveDir(const CServerPath& path /*=CServerPath()*/, const wxString& subDir /*=_T("")*/)
2206{
2207        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::RemoveDir"));
2208
2209        wxASSERT(!m_pCurOpData);
2210        CSftpRemoveDirOpData *pData = new CSftpRemoveDirOpData();
2211        m_pCurOpData = pData;
2212        pData->path = path;
2213        pData->subDir = subDir;
2214
2215        CServerPath fullPath = engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->path, pData->subDir);
2216        if (fullPath.empty()) {
2217                fullPath = pData->path;
2218
2219                if (!fullPath.AddSegment(subDir)) {
2220                        LogMessage(MessageType::Error, _("Path cannot be constructed for directory %s and subdir %s"), path.GetPath(), subDir);
2221                        return FZ_REPLY_ERROR;
2222                }
2223        }
2224
2225        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, path, subDir);
2226
2227        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->path, pData->subDir);
2228
2229        engine_.InvalidateCurrentWorkingDirs(fullPath);
2230        if (!SendCommand(_T("rmdir ") + WildcardEscape(QuoteFilename(fullPath.GetPath())),
2231                          _T("rmdir ") + QuoteFilename(fullPath.GetPath())))
2232                return FZ_REPLY_ERROR;
2233
2234        return FZ_REPLY_WOULDBLOCK;
2235}
2236
2237int CSftpControlSocket::RemoveDirParseResponse(bool successful, const wxString&)
2238{
2239        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::RemoveDirParseResponse"));
2240
2241        if (!m_pCurOpData)
2242        {
2243                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
2244                ResetOperation(FZ_REPLY_INTERNALERROR);
2245                return FZ_REPLY_ERROR;
2246        }
2247
2248        if (!successful)
2249        {
2250                ResetOperation(FZ_REPLY_ERROR);
2251                return FZ_REPLY_ERROR;
2252        }
2253
2254        CSftpRemoveDirOpData *pData = static_cast<CSftpRemoveDirOpData *>(m_pCurOpData);
2255        if (pData->path.empty())
2256        {
2257                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty pData->path"));
2258                ResetOperation(FZ_REPLY_INTERNALERROR);
2259                return FZ_REPLY_ERROR;
2260        }
2261
2262        engine_.GetDirectoryCache().RemoveDir(*m_pCurrentServer, pData->path, pData->subDir, engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->path, pData->subDir));
2263        engine_.SendDirectoryListingNotification(pData->path, false, true, false);
2264
2265        return ResetOperation(FZ_REPLY_OK);
2266}
2267
2268class CSftpChmodOpData : public COpData
2269{
2270public:
2271        CSftpChmodOpData(const CChmodCommand& command)
2272                : COpData(Command::chmod), m_cmd(command)
2273        {
2274                m_useAbsolute = false;
2275        }
2276
2277        virtual ~CSftpChmodOpData() {}
2278
2279        CChmodCommand m_cmd;
2280        bool m_useAbsolute;
2281};
2282
2283enum chmodStates
2284{
2285        chmod_init = 0,
2286        chmod_chmod
2287};
2288
2289int CSftpControlSocket::Chmod(const CChmodCommand& command)
2290{
2291        if (m_pCurOpData)
2292        {
2293                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData not empty"));
2294                ResetOperation(FZ_REPLY_INTERNALERROR);
2295                return FZ_REPLY_ERROR;
2296        }
2297
2298        LogMessage(MessageType::Status, _("Set permissions of '%s' to '%s'"), command.GetPath().FormatFilename(command.GetFile()), command.GetPermission());
2299
2300        CSftpChmodOpData *pData = new CSftpChmodOpData(command);
2301        pData->opState = chmod_chmod;
2302        m_pCurOpData = pData;
2303
2304        int res = ChangeDir(command.GetPath());
2305        if (res != FZ_REPLY_OK)
2306                return res;
2307
2308        return SendNextCommand();
2309}
2310
2311int CSftpControlSocket::ChmodParseResponse(bool successful, const wxString&)
2312{
2313        CSftpChmodOpData *pData = static_cast<CSftpChmodOpData*>(m_pCurOpData);
2314        if (!pData)
2315        {
2316                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2317                ResetOperation(FZ_REPLY_INTERNALERROR);
2318                return FZ_REPLY_ERROR;
2319        }
2320
2321        if (!successful)
2322        {
2323                ResetOperation(FZ_REPLY_ERROR);
2324                return FZ_REPLY_ERROR;
2325        }
2326
2327        ResetOperation(FZ_REPLY_OK);
2328        return FZ_REPLY_OK;
2329}
2330
2331int CSftpControlSocket::ChmodSubcommandResult(int prevResult)
2332{
2333        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ChmodSend()"));
2334
2335        CSftpChmodOpData *pData = static_cast<CSftpChmodOpData*>(m_pCurOpData);
2336        if (!pData)
2337        {
2338                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2339                ResetOperation(FZ_REPLY_INTERNALERROR);
2340                return FZ_REPLY_ERROR;
2341        }
2342
2343        if (prevResult != FZ_REPLY_OK)
2344                pData->m_useAbsolute = true;
2345
2346        return SendNextCommand();
2347}
2348
2349int CSftpControlSocket::ChmodSend()
2350{
2351        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ChmodSend()"));
2352
2353        CSftpChmodOpData *pData = static_cast<CSftpChmodOpData*>(m_pCurOpData);
2354        if (!pData)
2355        {
2356                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2357                ResetOperation(FZ_REPLY_INTERNALERROR);
2358                return FZ_REPLY_ERROR;
2359        }
2360
2361        bool res;
2362        switch (pData->opState)
2363        {
2364        case chmod_chmod:
2365                {
2366                        engine_.GetDirectoryCache().UpdateFile(*m_pCurrentServer, pData->m_cmd.GetPath(), pData->m_cmd.GetFile(), false, CDirectoryCache::unknown);
2367
2368                        wxString quotedFilename = QuoteFilename(pData->m_cmd.GetPath().FormatFilename(pData->m_cmd.GetFile(), !pData->m_useAbsolute));
2369
2370                        res = SendCommand(_T("chmod ") + pData->m_cmd.GetPermission() + _T(" ") + WildcardEscape(quotedFilename),
2371                                           _T("chmod ") + pData->m_cmd.GetPermission() + _T(" ") + quotedFilename);
2372                }
2373                break;
2374        default:
2375                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
2376                ResetOperation(FZ_REPLY_INTERNALERROR);
2377                return FZ_REPLY_ERROR;
2378        }
2379
2380        if (!res)
2381        {
2382                ResetOperation(FZ_REPLY_ERROR);
2383                return FZ_REPLY_ERROR;
2384        }
2385
2386        return FZ_REPLY_WOULDBLOCK;
2387}
2388
2389class CSftpRenameOpData : public COpData
2390{
2391public:
2392        CSftpRenameOpData(const CRenameCommand& command)
2393                : COpData(Command::rename), m_cmd(command)
2394        {
2395                m_useAbsolute = false;
2396        }
2397
2398        virtual ~CSftpRenameOpData() {}
2399
2400        CRenameCommand m_cmd;
2401        bool m_useAbsolute;
2402};
2403
2404enum renameStates
2405{
2406        rename_init = 0,
2407        rename_rename
2408};
2409
2410int CSftpControlSocket::Rename(const CRenameCommand& command)
2411{
2412        if (m_pCurOpData)
2413        {
2414                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData not empty"));
2415                ResetOperation(FZ_REPLY_INTERNALERROR);
2416                return FZ_REPLY_ERROR;
2417        }
2418
2419        LogMessage(MessageType::Status, _("Renaming '%s' to '%s'"), command.GetFromPath().FormatFilename(command.GetFromFile()), command.GetToPath().FormatFilename(command.GetToFile()));
2420
2421        CSftpRenameOpData *pData = new CSftpRenameOpData(command);
2422        pData->opState = rename_rename;
2423        m_pCurOpData = pData;
2424
2425        int res = ChangeDir(command.GetFromPath());
2426        if (res != FZ_REPLY_OK)
2427                return res;
2428
2429        return SendNextCommand();
2430}
2431
2432int CSftpControlSocket::RenameParseResponse(bool successful, const wxString&)
2433{
2434        CSftpRenameOpData *pData = static_cast<CSftpRenameOpData*>(m_pCurOpData);
2435        if (!pData)
2436        {
2437                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2438                ResetOperation(FZ_REPLY_INTERNALERROR);
2439                return FZ_REPLY_ERROR;
2440        }
2441
2442        if (!successful)
2443        {
2444                ResetOperation(FZ_REPLY_ERROR);
2445                return FZ_REPLY_ERROR;
2446        }
2447
2448        const CServerPath& fromPath = pData->m_cmd.GetFromPath();
2449        const CServerPath& toPath = pData->m_cmd.GetToPath();
2450
2451        engine_.GetDirectoryCache().Rename(*m_pCurrentServer, fromPath, pData->m_cmd.GetFromFile(), toPath, pData->m_cmd.GetToFile());
2452
2453        engine_.SendDirectoryListingNotification(fromPath, false, true, false);
2454        if (fromPath != toPath)
2455                engine_.SendDirectoryListingNotification(toPath, false, true, false);
2456
2457        ResetOperation(FZ_REPLY_OK);
2458        return FZ_REPLY_OK;
2459}
2460
2461int CSftpControlSocket::RenameSubcommandResult(int prevResult)
2462{
2463        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::RenameSubcommandResult()"));
2464
2465        CSftpRenameOpData *pData = static_cast<CSftpRenameOpData*>(m_pCurOpData);
2466        if (!pData)
2467        {
2468                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2469                ResetOperation(FZ_REPLY_INTERNALERROR);
2470                return FZ_REPLY_ERROR;
2471        }
2472
2473        if (prevResult != FZ_REPLY_OK)
2474                pData->m_useAbsolute = true;
2475
2476        return SendNextCommand();
2477}
2478
2479int CSftpControlSocket::RenameSend()
2480{
2481        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::RenameSend()"));
2482
2483        CSftpRenameOpData *pData = static_cast<CSftpRenameOpData*>(m_pCurOpData);
2484        if (!pData)
2485        {
2486                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurOpData empty"));
2487                ResetOperation(FZ_REPLY_INTERNALERROR);
2488                return FZ_REPLY_ERROR;
2489        }
2490
2491        bool res;
2492        switch (pData->opState)
2493        {
2494        case rename_rename:
2495                {
2496                        bool wasDir = false;
2497                        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile(), &wasDir);
2498                        engine_.GetDirectoryCache().InvalidateFile(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());
2499
2500                        wxString fromQuoted = QuoteFilename(pData->m_cmd.GetFromPath().FormatFilename(pData->m_cmd.GetFromFile(), !pData->m_useAbsolute));
2501                        wxString toQuoted = QuoteFilename(pData->m_cmd.GetToPath().FormatFilename(pData->m_cmd.GetToFile(), !pData->m_useAbsolute && pData->m_cmd.GetFromPath() == pData->m_cmd.GetToPath()));
2502
2503                        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
2504                        engine_.GetPathCache().InvalidatePath(*m_pCurrentServer, pData->m_cmd.GetToPath(), pData->m_cmd.GetToFile());
2505
2506                        if (wasDir)
2507                        {
2508                                // Need to invalidate current working directories
2509                                CServerPath path = engine_.GetPathCache().Lookup(*m_pCurrentServer, pData->m_cmd.GetFromPath(), pData->m_cmd.GetFromFile());
2510                                if (path.empty())
2511                                {
2512                                        path = pData->m_cmd.GetFromPath();
2513                                        path.AddSegment(pData->m_cmd.GetFromFile());
2514                                }
2515                                engine_.InvalidateCurrentWorkingDirs(path);
2516                        }
2517
2518                        res = SendCommand(_T("mv ") + WildcardEscape(fromQuoted) + _T(" ") + toQuoted,
2519                                           _T("mv ") + fromQuoted + _T(" ") + toQuoted);
2520                }
2521                break;
2522        default:
2523                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("unknown op state: %d"), pData->opState);
2524                ResetOperation(FZ_REPLY_INTERNALERROR);
2525                return FZ_REPLY_ERROR;
2526        }
2527
2528        if (!res)
2529        {
2530                ResetOperation(FZ_REPLY_ERROR);
2531                return FZ_REPLY_ERROR;
2532        }
2533
2534        return FZ_REPLY_WOULDBLOCK;
2535}
2536
2537wxString CSftpControlSocket::WildcardEscape(const wxString& file)
2538{
2539        // see src/putty/wildcard.c
2540
2541        wxString escapedFile;
2542        escapedFile.Alloc(file.Len());
2543        for (unsigned int i = 0; i < file.Len(); ++i)
2544        {
2545                const wxChar& c = file[i];
2546                switch (c)
2547                {
2548                case '[':
2549                case ']':
2550                case '*':
2551                case '?':
2552                case '\\':
2553                        escapedFile.Append('\\');
2554                        break;
2555                default:
2556                        break;
2557                }
2558                escapedFile.Append(c);
2559        }
2560        return escapedFile;
2561}
2562
2563void CSftpControlSocket::OnRateAvailable(CRateLimiter::rate_direction direction)
2564{
2565        OnQuotaRequest(direction);
2566}
2567
2568void CSftpControlSocket::OnQuotaRequest(CRateLimiter::rate_direction direction)
2569{
2570        int64_t bytes = GetAvailableBytes(direction);
2571        if (bytes > 0) {
2572                int b;
2573                if (bytes > INT_MAX)
2574                        b = INT_MAX;
2575                else
2576                        b = bytes;
2577                AddToStream(wxString::Format(_T("-%d%d,%d\n"), (int)direction, b, engine_.GetOptions().GetOptionVal(OPTION_SPEEDLIMIT_INBOUND + static_cast<int>(direction))));
2578                UpdateUsage(direction, b);
2579        }
2580        else if (bytes == 0)
2581                Wait(direction);
2582        else if (bytes < 0)
2583                AddToStream(wxString::Format(_T("-%d-\n"), (int)direction));
2584}
2585
2586
2587int CSftpControlSocket::ParseSubcommandResult(int prevResult)
2588{
2589        LogMessage(MessageType::Debug_Verbose, _T("CSftpControlSocket::ParseSubcommandResult(%d)"), prevResult);
2590        if (!m_pCurOpData)
2591        {
2592                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("ParseSubcommandResult called without active operation"));
2593                ResetOperation(FZ_REPLY_ERROR);
2594                return FZ_REPLY_ERROR;
2595        }
2596
2597        switch (m_pCurOpData->opId)
2598        {
2599        case Command::cwd:
2600                return ChangeDirSubcommandResult(prevResult);
2601        case Command::list:
2602                return ListSubcommandResult(prevResult);
2603        case Command::transfer:
2604                return FileTransferSubcommandResult(prevResult);
2605        case Command::rename:
2606                return RenameSubcommandResult(prevResult);
2607        case Command::chmod:
2608                return ChmodSubcommandResult(prevResult);
2609        default:
2610                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown opID (%d) in ParseSubcommandResult"), m_pCurOpData->opId);
2611                ResetOperation(FZ_REPLY_INTERNALERROR);
2612                break;
2613        }
2614
2615        return FZ_REPLY_ERROR;
2616}
2617
2618int CSftpControlSocket::ListCheckTimezoneDetection()
2619{
2620        wxASSERT(m_pCurOpData);
2621
2622        CSftpListOpData *pData = static_cast<CSftpListOpData *>(m_pCurOpData);
2623
2624        if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset) == unknown) {
2625                const int count = pData->directoryListing.GetCount();
2626                for (int i = 0; i < count; ++i) {
2627                        if (!pData->directoryListing[i].has_time())
2628                                continue;
2629
2630                        if (pData->directoryListing[i].is_link())
2631                                continue;
2632
2633                        pData->opState = list_mtime;
2634                        pData->mtime_index = i;
2635                        return SendNextCommand();
2636                }
2637        }
2638
2639        return FZ_REPLY_OK;
2640}
2641
2642void CSftpControlSocket::operator()(fz::event_base const& ev)
2643{
2644        if (fz::dispatch<CSftpEvent, CTerminateEvent>(ev, this,
2645                &CSftpControlSocket::OnSftpEvent,
2646                &CSftpControlSocket::OnTerminate)) {
2647                return;
2648        }
2649
2650        CControlSocket::operator()(ev);
2651}
Note: See TracBrowser for help on using the repository browser.