source: filezilla/trunk/fuentes/src/engine/sftpcontrolsocket.cpp @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 3 years ago

First release to xenial

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