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

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

Update new version: 3.15.02

File size: 35.7 KB
Line 
1#include <filezilla.h>
2#include "ControlSocket.h"
3#include "directorycache.h"
4#include "engineprivate.h"
5#include "local_path.h"
6#include "logging_private.h"
7#include "proxy.h"
8#include "servercapabilities.h"
9#include "sizeformatting_base.h"
10
11#include <libfilezilla/event_loop.hpp>
12#include <libfilezilla/iputils.hpp>
13#include <libfilezilla/local_filesys.hpp>
14
15#include <wx/file.h>
16#include <wx/filename.h>
17
18#ifndef __WXMSW__
19        #define mutex mutex_override // Sadly on some platforms system headers include conflicting names
20        #include <netdb.h>
21        #undef mutex
22        #ifndef AI_IDN
23                #include <idna.h>
24                extern "C" {
25                        #include <idn-free.h>
26                }
27        #endif
28#endif
29
30struct obtain_lock_event_type;
31typedef fz::simple_event<obtain_lock_event_type> CObtainLockEvent;
32
33std::list<CControlSocket::t_lockInfo> CControlSocket::m_lockInfoList;
34
35COpData::COpData(Command op_Id)
36        : opId(op_Id)
37{
38        opState = 0;
39
40        pNextOpData = 0;
41
42        waitForAsyncRequest = false;
43
44        holdsLock = false;
45}
46
47COpData::~COpData()
48{
49        delete pNextOpData;
50}
51
52CControlSocket::CControlSocket(CFileZillaEnginePrivate & engine)
53        : CLogging(engine)
54        , event_handler(engine.event_loop_)
55        , engine_(engine)
56{
57        m_pCurOpData = 0;
58        m_nOpState = 0;
59        m_pCurrentServer = 0;
60
61        m_pCSConv = 0;
62        m_useUTF8 = false;
63
64        m_closed = false;
65
66        m_invalidateCurrentPath = false;
67}
68
69CControlSocket::~CControlSocket()
70{
71        remove_handler();
72
73        DoClose();
74
75        delete m_pCSConv;
76        m_pCSConv = 0;
77}
78
79int CControlSocket::Disconnect()
80{
81        LogMessage(MessageType::Status, _("Disconnected from server"));
82
83        DoClose();
84        return FZ_REPLY_OK;
85}
86
87Command CControlSocket::GetCurrentCommandId() const
88{
89        if (m_pCurOpData)
90                return m_pCurOpData->opId;
91
92        return engine_.GetCurrentCommandId();
93}
94
95void CControlSocket::LogTransferResultMessage(int nErrorCode, CFileTransferOpData *pData)
96{
97        bool tmp;
98
99        CTransferStatus const status = engine_.transfer_status_.Get(tmp);
100        if (!status.empty() && (nErrorCode == FZ_REPLY_OK || status.madeProgress)) {
101                int elapsed = static_cast<int>((fz::datetime::now() - status.started).get_seconds());
102                if (elapsed <= 0)
103                        elapsed = 1;
104                wxString time = wxString::Format(
105                        wxPLURAL("%d second", "%d seconds", elapsed),
106                        elapsed);
107
108                int64_t transferred = status.currentOffset - status.startOffset;
109                wxString size = CSizeFormatBase::Format(&engine_.GetOptions(), transferred, true);
110
111                MessageType msgType = MessageType::Error;
112                wxString msg;
113                if (nErrorCode == FZ_REPLY_OK) {
114                        msgType = MessageType::Status;
115                        msg = _("File transfer successful, transferred %s in %s");
116                }
117                else if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
118                        msg = _("File transfer aborted by user after transferring %s in %s");
119                else if ((nErrorCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR)
120                        msg = _("Critical file transfer error after transferring %s in %s");
121                else
122                        msg = _("File transfer failed after transferring %s in %s");
123                LogMessage(msgType, msg, size, time);
124        }
125        else {
126                if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
127                        LogMessage(MessageType::Error, _("File transfer aborted by user"));
128                else if (nErrorCode == FZ_REPLY_OK) {
129                        if (pData->transferInitiated)
130                                LogMessage(MessageType::Status, _("File transfer successful"));
131                        else
132                                LogMessage(MessageType::Status, _("File transfer skipped"));
133                }
134                else if ((nErrorCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR)
135                        LogMessage(MessageType::Error, _("Critical file transfer error"));
136                else
137                        LogMessage(MessageType::Error, _("File transfer failed"));
138        }
139}
140
141int CControlSocket::ResetOperation(int nErrorCode)
142{
143        LogMessage(MessageType::Debug_Verbose, _T("CControlSocket::ResetOperation(%d)"), nErrorCode);
144
145        if (nErrorCode & FZ_REPLY_WOULDBLOCK) {
146                LogMessage(MessageType::Debug_Warning, _T("ResetOperation with FZ_REPLY_WOULDBLOCK in nErrorCode (%d)"), nErrorCode);
147        }
148
149        if (m_pCurOpData && m_pCurOpData->holdsLock)
150                UnlockCache();
151
152        if (m_pCurOpData && m_pCurOpData->pNextOpData) {
153                COpData *pNext = m_pCurOpData->pNextOpData;
154                m_pCurOpData->pNextOpData = 0;
155                delete m_pCurOpData;
156                m_pCurOpData = pNext;
157                if (nErrorCode == FZ_REPLY_OK ||
158                        nErrorCode == FZ_REPLY_ERROR ||
159                        nErrorCode == FZ_REPLY_CRITICALERROR)
160                {
161                        return ParseSubcommandResult(nErrorCode);
162                }
163                else
164                        return ResetOperation(nErrorCode);
165        }
166
167        wxString prefix;
168        if ((nErrorCode & FZ_REPLY_CRITICALERROR) == FZ_REPLY_CRITICALERROR &&
169                (!m_pCurOpData || m_pCurOpData->opId != Command::transfer))
170        {
171                prefix = _("Critical error:") + _T(" ");
172        }
173
174        if (m_pCurOpData) {
175                const Command commandId = m_pCurOpData->opId;
176                switch (commandId)
177                {
178                case Command::none:
179                        if( !prefix.empty() ) {
180                                LogMessage(MessageType::Error, _("Critical error"));
181                        }
182                        break;
183                case Command::connect:
184                        if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
185                                LogMessage(MessageType::Error, prefix + _("Connection attempt interrupted by user"));
186                        else if (nErrorCode != FZ_REPLY_OK)
187                                LogMessage(MessageType::Error, prefix + _("Could not connect to server"));
188                        break;
189                case Command::list:
190                        if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
191                                LogMessage(MessageType::Error, prefix + _("Directory listing aborted by user"));
192                        else if (nErrorCode != FZ_REPLY_OK)
193                                LogMessage(MessageType::Error, prefix + _("Failed to retrieve directory listing"));
194                        else {
195                                if (m_CurrentPath.empty()) {
196                                        LogMessage(MessageType::Status, _("Directory listing successful"));
197                                }
198                                else {
199                                        LogMessage(MessageType::Status, _("Directory listing of \"%s\" successful"), m_CurrentPath.GetPath());
200                                }
201                        }
202                        break;
203                case Command::transfer:
204                        {
205                                CFileTransferOpData *pData = static_cast<CFileTransferOpData *>(m_pCurOpData);
206                                if (!pData->download && pData->transferInitiated) {
207                                        if (!m_pCurrentServer)
208                                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("m_pCurrentServer is 0"));
209                                        else {
210                                                bool updated = engine_.GetDirectoryCache().UpdateFile(*m_pCurrentServer, pData->remotePath, pData->remoteFile, true, CDirectoryCache::file, (nErrorCode == FZ_REPLY_OK) ? pData->localFileSize : -1);
211                                                if (updated)
212                                                        engine_.SendDirectoryListingNotification(pData->remotePath, false, true, false);
213                                        }
214                                }
215                                LogTransferResultMessage(nErrorCode, pData);
216                        }
217                        break;
218                default:
219                        if ((nErrorCode & FZ_REPLY_CANCELED) == FZ_REPLY_CANCELED)
220                                LogMessage(MessageType::Error, prefix + _("Interrupted by user"));
221                        break;
222                }
223
224                delete m_pCurOpData;
225                m_pCurOpData = 0;
226        }
227
228        engine_.transfer_status_.Reset();
229
230        SetWait(false);
231
232        if (m_invalidateCurrentPath) {
233                m_CurrentPath.clear();
234                m_invalidateCurrentPath = false;
235        }
236
237        return engine_.ResetOperation(nErrorCode);
238}
239
240int CControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/)
241{
242        LogMessage(MessageType::Debug_Debug, _T("CControlSocket::DoClose(%d)"), nErrorCode);
243        if (m_closed) {
244                wxASSERT(!m_pCurOpData);
245                return nErrorCode;
246        }
247
248        m_closed = true;
249
250        nErrorCode = ResetOperation(FZ_REPLY_ERROR | FZ_REPLY_DISCONNECTED | nErrorCode);
251
252        delete m_pCurrentServer;
253        m_pCurrentServer = 0;
254
255        return nErrorCode;
256}
257
258wxString CControlSocket::ConvertDomainName(wxString const& domain)
259{
260#ifdef __WXMSW__
261        int len = IdnToAscii(IDN_ALLOW_UNASSIGNED, domain.wc_str(), domain.size() + 1, 0, 0);
262        if (!len) {
263                LogMessage(MessageType::Debug_Warning, _T("Could not convert domain name"));
264                return domain;
265        }
266
267        wchar_t* output = new wchar_t[len];
268        int res = IdnToAscii(IDN_ALLOW_UNASSIGNED, domain.wc_str(), domain.size() + 1, output, len);
269        if (!res) {
270                delete [] output;
271                LogMessage(MessageType::Debug_Warning, _T("Could not convert domain name"));
272                return domain;
273        }
274
275        wxString ret(output);
276        delete [] output;
277        return ret;
278#elif defined(AI_IDN)
279        return domain;
280#else
281        wxScopedCharBuffer const utf8 = domain.utf8_str();
282
283        char *output = 0;
284        if (idna_to_ascii_8z(utf8, &output, IDNA_ALLOW_UNASSIGNED)) {
285                LogMessage(MessageType::Debug_Warning, _T("Could not convert domain name"));
286                return domain;
287        }
288
289        wxString result = wxConvCurrent->cMB2WX(output);
290        idn_free(output);
291        return result;
292#endif
293}
294
295void CControlSocket::Cancel()
296{
297        if (GetCurrentCommandId() != Command::none)
298        {
299                if (GetCurrentCommandId() == Command::connect)
300                        DoClose(FZ_REPLY_CANCELED);
301                else
302                        ResetOperation(FZ_REPLY_CANCELED);
303        }
304}
305
306const CServer* CControlSocket::GetCurrentServer() const
307{
308        return m_pCurrentServer;
309}
310
311bool CControlSocket::ParsePwdReply(wxString reply, bool unquoted /*=false*/, const CServerPath& defaultPath /*=CServerPath()*/)
312{
313        if (!unquoted)
314        {
315                int pos1 = reply.Find('"');
316                int pos2 = reply.Find('"', true);
317                if (pos1 == -1 || pos1 >= pos2) {
318                        pos1 = reply.Find('\'');
319                        pos2 = reply.Find('\'', true);
320
321                        if (pos1 != -1 && pos1 < pos2)
322                                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Broken server sending single-quoted path instead of double-quoted path."));
323                }
324                if (pos1 == -1 || pos1 >= pos2) {
325                        LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Broken server, no quoted path found in pwd reply, trying first token as path"));
326                        pos1 = reply.Find(' ');
327                        if (pos1 != -1) {
328                                reply = reply.Mid(pos1 + 1);
329                                pos2 = reply.Find(' ');
330                                if (pos2 != -1)
331                                        reply = reply.Left(pos2);
332                        }
333                        else
334                                reply.clear();
335                }
336                else {
337                        reply = reply.Mid(pos1 + 1, pos2 - pos1 - 1);
338                        reply.Replace(_T("\"\""), _T("\""));
339                }
340        }
341
342        m_CurrentPath.SetType(m_pCurrentServer->GetType());
343        if (reply.empty() || !m_CurrentPath.SetPath(reply)) {
344                if (reply.empty())
345                        LogMessage(MessageType::Error, _("Server returned empty path."));
346                else
347                        LogMessage(MessageType::Error, _("Failed to parse returned path."));
348
349                if (!defaultPath.empty()) {
350                        LogMessage(MessageType::Debug_Warning, _T("Assuming path is '%s'."), defaultPath.GetPath());
351                        m_CurrentPath = defaultPath;
352                        return true;
353                }
354                return false;
355        }
356
357        return true;
358}
359
360int CControlSocket::CheckOverwriteFile()
361{
362        if (!m_pCurOpData)
363        {
364                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("Empty m_pCurOpData"));
365                ResetOperation(FZ_REPLY_INTERNALERROR);
366                return FZ_REPLY_ERROR;
367        }
368
369        CFileTransferOpData *pData = static_cast<CFileTransferOpData *>(m_pCurOpData);
370
371        if (pData->download)
372        {
373                if (!wxFile::Exists(pData->localFile))
374                        return FZ_REPLY_OK;
375        }
376
377        CDirentry entry;
378        bool dirDidExist;
379        bool matchedCase;
380        CServerPath remotePath;
381        if (pData->tryAbsolutePath || m_CurrentPath.empty())
382                remotePath = pData->remotePath;
383        else
384                remotePath = m_CurrentPath;
385        bool found = engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, remotePath, pData->remoteFile, dirDidExist, matchedCase);
386
387        // Ignore entries with wrong case
388        if (found && !matchedCase)
389                found = false;
390
391        if (!pData->download) {
392                if (!found && pData->remoteFileSize < 0 && !pData->fileTime.empty())
393                        return FZ_REPLY_OK;
394        }
395
396        CFileExistsNotification *pNotification = new CFileExistsNotification;
397
398        pNotification->download = pData->download;
399        pNotification->localFile = pData->localFile;
400        pNotification->remoteFile = pData->remoteFile;
401        pNotification->remotePath = pData->remotePath;
402        pNotification->localSize = pData->localFileSize;
403        pNotification->remoteSize = pData->remoteFileSize;
404        pNotification->ascii = !pData->transferSettings.binary;
405
406        if (pData->download && pNotification->localSize >= 0)
407                pNotification->canResume = true;
408        else if (!pData->download && pNotification->remoteSize >= 0)
409                pNotification->canResume = true;
410        else
411                pNotification->canResume = false;
412
413        pNotification->localTime = fz::local_filesys::get_modification_time(fz::to_native(pData->localFile));
414
415        if (pData->fileTime.empty())
416                pNotification->remoteTime = pData->fileTime;
417
418        if (found) {
419                if (!pData->fileTime.empty()) {
420                        if (entry.has_date()) {
421                                pNotification->remoteTime = entry.time;
422                                pData->fileTime = entry.time;
423                        }
424                }
425        }
426
427        SendAsyncRequest(pNotification);
428
429        return FZ_REPLY_WOULDBLOCK;
430}
431
432CFileTransferOpData::CFileTransferOpData(bool is_download, const wxString& local_file, const wxString& remote_file, const CServerPath& remote_path) :
433        COpData(Command::transfer),
434        localFile(local_file), remoteFile(remote_file), remotePath(remote_path),
435        download(is_download)
436{
437}
438
439CFileTransferOpData::~CFileTransferOpData()
440{
441}
442
443wxString CControlSocket::ConvToLocal(const char* buffer, size_t len)
444{
445        size_t outLen{};
446        if (m_useUTF8) {
447                wxChar* out = ConvToLocalBuffer(buffer, wxConvUTF8, len, outLen);
448                if (out) {
449                        wxString str(out, outLen - 1);
450                        delete [] out;
451                        return str;
452                }
453
454                // Fall back to local charset on error
455                if (m_pCurrentServer->GetEncodingType() != ENCODING_UTF8) {
456                        LogMessage(MessageType::Status, _("Invalid character sequence received, disabling UTF-8. Select UTF-8 option in site manager to force UTF-8."));
457                        m_useUTF8 = false;
458                }
459        }
460
461        if (m_pCSConv) {
462                wxChar* out = ConvToLocalBuffer(buffer, *m_pCSConv, len, outLen);
463                if (out) {
464                        wxString str(out, outLen - 1);
465                        delete [] out;
466                        return str;
467                }
468        }
469
470        wxCSConv conv(_T("ISO-8859-1"));
471        wxString str = conv.cMB2WX(buffer);
472        if (str.empty())
473                str = wxConvCurrent->cMB2WX(buffer);
474
475        return str;
476}
477
478wxChar* CControlSocket::ConvToLocalBuffer(const char* buffer, wxMBConv& conv, size_t len, size_t& outlen)
479{
480        wxASSERT(buffer && len > 0 && !buffer[len - 1]);
481        outlen = conv.ToWChar(0, 0, buffer, len);
482        if (!outlen || outlen == wxCONV_FAILED)
483                return 0;
484
485        wchar_t* unicode = new wchar_t[outlen];
486        conv.ToWChar(unicode, outlen, buffer, len);
487        return unicode;
488}
489
490wxChar* CControlSocket::ConvToLocalBuffer(const char* buffer, size_t len, size_t& outlen)
491{
492        if (m_useUTF8) {
493#ifdef __WXMSW__
494                // wxConvUTF8 is generic and slow.
495                // Use the highly optimized MultiByteToWideChar on Windows
496                // This helps when processing large directory listings.
497                int outlen2 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer, len, 0, 0);
498                if (outlen2 > 0) {
499                        wxChar* out = new wxChar[outlen2];
500                        MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer, len, out, outlen2);
501                        outlen = static_cast<size_t>(outlen2);
502                        return out;
503                }
504#else
505                wxChar* res = ConvToLocalBuffer(buffer, wxConvUTF8, len, outlen);
506                if (res && *res)
507                        return res;
508#endif
509
510                // Fall back to local charset on error
511                if (m_pCurrentServer->GetEncodingType() != ENCODING_UTF8) {
512                        LogMessage(MessageType::Status, _("Invalid character sequence received, disabling UTF-8. Select UTF-8 option in site manager to force UTF-8."));
513                        m_useUTF8 = false;
514                }
515        }
516
517        if (m_pCSConv) {
518                wxChar* res = ConvToLocalBuffer(buffer, *m_pCSConv, len, outlen);
519                if (res && *res)
520                        return res;
521        }
522
523        // Fallback: Conversion using current locale
524        wxChar* res = ConvToLocalBuffer(buffer, *wxConvCurrent, len, outlen);
525
526        return res;
527}
528
529wxCharBuffer CControlSocket::ConvToServer(const wxString& str, bool force_utf8 /*=false*/)
530{
531        if (m_useUTF8 || force_utf8) {
532                wxCharBuffer const buffer = str.utf8_str();
533                if (buffer || force_utf8)
534                        return buffer;
535        }
536
537        if (m_pCSConv) {
538                wxCharBuffer const buffer = str.mb_str(*m_pCSConv);
539                if (buffer)
540                        return buffer;
541        }
542
543        wxCharBuffer buffer = str.mb_str(*wxConvCurrent);
544        if (!buffer)
545                buffer = str.To8BitData();
546
547        return buffer;
548}
549
550void CControlSocket::OnTimer(fz::timer_id)
551{
552        m_timer = 0; // It's a one-shot timer, no need to stop it
553
554        int const timeout = engine_.GetOptions().GetOptionVal(OPTION_TIMEOUT);
555        if (timeout > 0) {
556                fz::duration elapsed = fz::monotonic_clock::now() - m_lastActivity;
557
558                if ((!m_pCurOpData || !m_pCurOpData->waitForAsyncRequest) && !IsWaitingForLock()) {
559                        if (elapsed > fz::duration::from_seconds(timeout)) {
560                                LogMessage(MessageType::Error, wxPLURAL("Connection timed out after %d second of inactivity", "Connection timed out after %d seconds of inactivity", timeout), timeout);
561                                DoClose(FZ_REPLY_TIMEOUT);
562                                return;
563                        }
564                }
565                else {
566                        elapsed = fz::duration();
567                }
568
569                m_timer = add_timer(fz::duration::from_milliseconds(timeout * 1000) - elapsed, true);
570        }
571}
572
573void CControlSocket::SetAlive()
574{
575        m_lastActivity = fz::monotonic_clock::now();
576}
577
578void CControlSocket::SetWait(bool wait)
579{
580        if (wait) {
581                if (m_timer)
582                        return;
583
584                m_lastActivity = fz::monotonic_clock::now();
585
586                int timeout = engine_.GetOptions().GetOptionVal(OPTION_TIMEOUT);
587                if (!timeout)
588                        return;
589
590                m_timer = add_timer(fz::duration::from_milliseconds(timeout * 1000 + 100), true); // Add a bit of slack
591        }
592        else {
593                stop_timer(m_timer);
594                m_timer = 0;
595        }
596}
597
598int CControlSocket::SendNextCommand()
599{
600        ResetOperation(FZ_REPLY_INTERNALERROR);
601        return FZ_REPLY_ERROR;
602}
603
604int CControlSocket::ParseSubcommandResult(int)
605{
606        ResetOperation(FZ_REPLY_INTERNALERROR);
607        return FZ_REPLY_ERROR;
608}
609
610const std::list<CControlSocket::t_lockInfo>::iterator CControlSocket::GetLockStatus()
611{
612        std::list<t_lockInfo>::iterator iter;
613        for (iter = m_lockInfoList.begin(); iter != m_lockInfoList.end(); ++iter)
614                if (iter->pControlSocket == this)
615                        break;
616
617        return iter;
618}
619
620bool CControlSocket::TryLockCache(enum locking_reason reason, const CServerPath& directory)
621{
622        wxASSERT(m_pCurrentServer);
623        wxASSERT(m_pCurOpData);
624
625        std::list<t_lockInfo>::iterator own = GetLockStatus();
626        if (own == m_lockInfoList.end())
627        {
628                t_lockInfo info;
629                info.directory = directory;
630                info.pControlSocket = this;
631                info.waiting = true;
632                info.reason = reason;
633                info.lockcount = 0;
634                m_lockInfoList.push_back(info);
635                own = --m_lockInfoList.end();
636        }
637        else
638        {
639                if (own->lockcount)
640                {
641                        if (!m_pCurOpData->holdsLock)
642                        {
643                                m_pCurOpData->holdsLock = true;
644                                own->lockcount++;
645                        }
646                        return true;
647                }
648                wxASSERT(own->waiting);
649                wxASSERT(own->reason == reason);
650        }
651
652        // Needs to be set in any case so that ResetOperation
653        // unlocks or cancels the lock wait
654        m_pCurOpData->holdsLock = true;
655
656        // Try to find other instance holding the lock
657        for (std::list<t_lockInfo>::const_iterator iter = m_lockInfoList.begin(); iter != own; ++iter)
658        {
659                if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer)
660                        continue;
661                if (directory != iter->directory)
662                        continue;
663                if (reason != iter->reason)
664                        continue;
665
666                // Some other instance is holding the lock
667                return false;
668        }
669
670        own->lockcount++;
671        own->waiting = false;
672        return true;
673}
674
675bool CControlSocket::IsLocked(enum locking_reason reason, const CServerPath& directory)
676{
677        wxASSERT(m_pCurrentServer);
678
679        std::list<t_lockInfo>::iterator own = GetLockStatus();
680        if (own != m_lockInfoList.end())
681                return true;
682
683        // Try to find other instance holding the lock
684        for (std::list<t_lockInfo>::const_iterator iter = m_lockInfoList.begin(); iter != own; ++iter)
685        {
686                if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer)
687                        continue;
688                if (directory != iter->directory)
689                        continue;
690                if (reason != iter->reason)
691                        continue;
692
693                // Some instance is holding the lock
694                return true;
695        }
696
697        return false;
698}
699
700void CControlSocket::UnlockCache()
701{
702        if (!m_pCurOpData || !m_pCurOpData->holdsLock)
703                return;
704        m_pCurOpData->holdsLock = false;
705
706        std::list<t_lockInfo>::iterator iter = GetLockStatus();
707        if (iter == m_lockInfoList.end())
708                return;
709
710        wxASSERT(!iter->waiting || iter->lockcount == 0);
711        if (!iter->waiting)
712        {
713                iter->lockcount--;
714                wxASSERT(iter->lockcount >= 0);
715                if (iter->lockcount)
716                        return;
717        }
718
719        CServerPath directory = iter->directory;
720        enum locking_reason reason = iter->reason;
721
722        m_lockInfoList.erase(iter);
723
724        // Find other instance waiting for the lock
725        if (!m_pCurrentServer) {
726                LogMessage(MessageType::Debug_Warning, _T("UnlockCache called with !m_pCurrentServer"));
727                return;
728        }
729        for (auto & lockInfo : m_lockInfoList) {
730                if (!lockInfo.pControlSocket->m_pCurrentServer){
731                        LogMessage(MessageType::Debug_Warning, _T("UnlockCache found other instance with !m_pCurrentServer"));
732                        continue;
733                }
734
735                if (*m_pCurrentServer != *lockInfo.pControlSocket->m_pCurrentServer)
736                        continue;
737
738                if (lockInfo.directory != directory)
739                        continue;
740
741                if (lockInfo.reason != reason)
742                        continue;
743
744                // Send notification
745                lockInfo.pControlSocket->send_event<CObtainLockEvent>();
746                break;
747        }
748}
749
750enum CControlSocket::locking_reason CControlSocket::ObtainLockFromEvent()
751{
752        if (!m_pCurOpData)
753                return lock_unknown;
754
755        std::list<t_lockInfo>::iterator own = GetLockStatus();
756        if (own == m_lockInfoList.end())
757                return lock_unknown;
758
759        if (!own->waiting)
760                return lock_unknown;
761
762        for (std::list<t_lockInfo>::const_iterator iter = m_lockInfoList.begin(); iter != own; ++iter)
763        {
764                if (*m_pCurrentServer != *iter->pControlSocket->m_pCurrentServer)
765                        continue;
766
767                if (iter->directory != own->directory)
768                        continue;
769
770                if (iter->reason != own->reason)
771                        continue;
772
773                // Another instance comes before us
774                return lock_unknown;
775        }
776
777        own->waiting = false;
778        own->lockcount++;
779
780        return own->reason;
781}
782
783void CControlSocket::OnObtainLock()
784{
785        if (ObtainLockFromEvent() == lock_unknown)
786                return;
787
788        SendNextCommand();
789
790        UnlockCache();
791}
792
793bool CControlSocket::IsWaitingForLock()
794{
795        std::list<t_lockInfo>::iterator own = GetLockStatus();
796        if (own == m_lockInfoList.end())
797                return false;
798
799        return own->waiting == true;
800}
801
802void CControlSocket::InvalidateCurrentWorkingDir(const CServerPath& path)
803{
804        wxASSERT(!path.empty());
805        if (m_CurrentPath.empty())
806                return;
807
808        if (m_CurrentPath == path || path.IsParentOf(m_CurrentPath, false))
809        {
810                if (m_pCurOpData)
811                        m_invalidateCurrentPath = true;
812                else
813                        m_CurrentPath.clear();
814        }
815}
816
817fz::duration CControlSocket::GetTimezoneOffset() const
818{
819        fz::duration ret;
820        if (m_pCurrentServer) {
821                int seconds = 0;
822                if (CServerCapabilities::GetCapability(*m_pCurrentServer, timezone_offset, &seconds) == yes) {
823                        ret = fz::duration::from_seconds(seconds);
824                }
825        }
826        return ret;
827}
828
829void CControlSocket::SendAsyncRequest(CAsyncRequestNotification* pNotification)
830{
831        wxASSERT(pNotification);
832
833        pNotification->requestNumber = engine_.GetNextAsyncRequestNumber();
834
835        if (m_pCurOpData)
836                m_pCurOpData->waitForAsyncRequest = true;
837        engine_.AddNotification(pNotification);
838}
839
840// ------------------
841// CRealControlSocket
842// ------------------
843
844CRealControlSocket::CRealControlSocket(CFileZillaEnginePrivate & engine)
845        : CControlSocket(engine)
846{
847        m_pSocket = new CSocket(this);
848
849        m_pBackend = new CSocketBackend(this, *m_pSocket, engine_.GetRateLimiter());
850        m_pProxyBackend = 0;
851
852        m_pSendBuffer = 0;
853        m_nSendBufferLen = 0;
854}
855
856CRealControlSocket::~CRealControlSocket()
857{
858        m_pSocket->Close();
859        if (m_pProxyBackend && m_pProxyBackend != m_pBackend)
860                delete m_pProxyBackend;
861        delete m_pBackend;
862        m_pBackend = 0;
863
864        delete m_pSocket;
865}
866
867bool CRealControlSocket::Send(const char *buffer, int len)
868{
869        SetWait(true);
870        if (m_pSendBuffer) {
871                char *tmp = m_pSendBuffer;
872                m_pSendBuffer = new char[m_nSendBufferLen + len];
873                memcpy(m_pSendBuffer, tmp, m_nSendBufferLen);
874                memcpy(m_pSendBuffer + m_nSendBufferLen, buffer, len);
875                m_nSendBufferLen += len;
876                delete [] tmp;
877        }
878        else {
879                int error;
880                int written = m_pBackend->Write(buffer, len, error);
881                if (written < 0) {
882                        if (error != EAGAIN) {
883                                LogMessage(MessageType::Error, _("Could not write to socket: %s"), CSocket::GetErrorDescription(error));
884                                LogMessage(MessageType::Error, _("Disconnected from server"));
885                                DoClose();
886                                return false;
887                        }
888                        written = 0;
889                }
890
891                if (written)
892                        SetActive(CFileZillaEngine::send);
893
894                if (written < len) {
895                        m_nSendBufferLen = len - written;
896                        m_pSendBuffer = new char[m_nSendBufferLen];
897                        memcpy(m_pSendBuffer, buffer, len - written);
898                }
899        }
900
901        return true;
902}
903
904void CRealControlSocket::operator()(fz::event_base const& ev)
905{
906        if (!fz::dispatch<CSocketEvent, CHostAddressEvent>(ev, this,
907                &CRealControlSocket::OnSocketEvent,
908                &CRealControlSocket::OnHostAddress))
909        {
910                CControlSocket::operator()(ev);
911        }
912}
913
914void CRealControlSocket::OnSocketEvent(CSocketEventSource*, SocketEventType t, int error)
915{
916        if (!m_pBackend)
917                return;
918
919        switch (t)
920        {
921        case SocketEventType::connection_next:
922                if (error)
923                        LogMessage(MessageType::Status, _("Connection attempt failed with \"%s\", trying next address."), CSocket::GetErrorDescription(error));
924                SetAlive();
925                break;
926        case SocketEventType::connection:
927                if (error) {
928                        LogMessage(MessageType::Status, _("Connection attempt failed with \"%s\"."), CSocket::GetErrorDescription(error));
929                        OnClose(error);
930                }
931                else {
932                        if (m_pProxyBackend && !m_pProxyBackend->Detached()) {
933                                m_pProxyBackend->Detach();
934                                m_pBackend = new CSocketBackend(this, *m_pSocket, engine_.GetRateLimiter());
935                        }
936                        OnConnect();
937                }
938                break;
939        case SocketEventType::read:
940                OnReceive();
941                break;
942        case SocketEventType::write:
943                OnSend();
944                break;
945        case SocketEventType::close:
946                OnClose(error);
947                break;
948        default:
949                LogMessage(MessageType::Debug_Warning, _T("Unhandled socket event %d"), t);
950                break;
951        }
952}
953
954void CRealControlSocket::OnHostAddress(CSocketEventSource*, wxString const& address)
955{
956        if (!m_pBackend)
957                return;
958
959        LogMessage(MessageType::Status, _("Connecting to %s..."), address);
960}
961
962void CRealControlSocket::OnConnect()
963{
964}
965
966void CRealControlSocket::OnReceive()
967{
968}
969
970void CRealControlSocket::OnSend()
971{
972        if (m_pSendBuffer)
973        {
974                if (!m_nSendBufferLen)
975                {
976                        delete [] m_pSendBuffer;
977                        m_pSendBuffer = 0;
978                        return;
979                }
980
981                int error;
982                int written = m_pBackend->Write(m_pSendBuffer, m_nSendBufferLen, error);
983                if (written < 0)
984                {
985                        if (error != EAGAIN)
986                        {
987                                LogMessage(MessageType::Error, _("Could not write to socket: %s"), CSocket::GetErrorDescription(error));
988                                if (GetCurrentCommandId() != Command::connect)
989                                        LogMessage(MessageType::Error, _("Disconnected from server"));
990                                DoClose();
991                        }
992                        return;
993                }
994
995                if (written) {
996                        SetActive(CFileZillaEngine::send);
997                }
998
999                if (written == m_nSendBufferLen) {
1000                        m_nSendBufferLen = 0;
1001                        delete [] m_pSendBuffer;
1002                        m_pSendBuffer = 0;
1003                }
1004                else {
1005                        memmove(m_pSendBuffer, m_pSendBuffer + written, m_nSendBufferLen - written);
1006                        m_nSendBufferLen -= written;
1007                }
1008        }
1009}
1010
1011void CRealControlSocket::OnClose(int error)
1012{
1013        LogMessage(MessageType::Debug_Verbose, _T("CRealControlSocket::OnClose(%d)"), error);
1014
1015        if (GetCurrentCommandId() != Command::connect)
1016        {
1017                if (!error)
1018                        LogMessage(MessageType::Error, _("Connection closed by server"));
1019                else
1020                        LogMessage(MessageType::Error, _("Disconnected from server: %s"), CSocket::GetErrorDescription(error));
1021        }
1022        DoClose();
1023}
1024
1025int CRealControlSocket::Connect(const CServer &server)
1026{
1027        SetWait(true);
1028
1029        if (server.GetEncodingType() == ENCODING_CUSTOM)
1030        {
1031                LogMessage(MessageType::Debug_Info, _T("Using custom encoding: %s"), server.GetCustomEncoding());
1032                m_pCSConv = new wxCSConv(server.GetCustomEncoding());
1033        }
1034
1035        delete m_pCurrentServer;
1036        m_pCurrentServer = new CServer(server);
1037
1038        // International domain names
1039        m_pCurrentServer->SetHost(ConvertDomainName(server.GetHost()), server.GetPort());
1040
1041        return ContinueConnect();
1042}
1043
1044int CRealControlSocket::ContinueConnect()
1045{
1046        wxString host;
1047        unsigned int port = 0;
1048
1049        const int proxy_type = engine_.GetOptions().GetOptionVal(OPTION_PROXY_TYPE);
1050        if (proxy_type > CProxySocket::unknown && proxy_type < CProxySocket::proxytype_count && !m_pCurrentServer->GetBypassProxy()) {
1051                LogMessage(MessageType::Status, _("Connecting to %s through %s proxy"), m_pCurrentServer->FormatHost(), CProxySocket::Name(static_cast<CProxySocket::ProxyType>(proxy_type)));
1052
1053                host = engine_.GetOptions().GetOption(OPTION_PROXY_HOST);
1054                port = engine_.GetOptions().GetOptionVal(OPTION_PROXY_PORT);
1055
1056                delete m_pBackend;
1057                m_pProxyBackend = new CProxySocket(this, m_pSocket, this);
1058                m_pBackend = m_pProxyBackend;
1059                int res = m_pProxyBackend->Handshake((enum CProxySocket::ProxyType)proxy_type,
1060                                                                                        m_pCurrentServer->GetHost(), m_pCurrentServer->GetPort(),
1061                                                                                        engine_.GetOptions().GetOption(OPTION_PROXY_USER),
1062                                                                                        engine_.GetOptions().GetOption(OPTION_PROXY_PASS));
1063
1064                if (res != EINPROGRESS) {
1065                        LogMessage(MessageType::Error, _("Could not start proxy handshake: %s"), CSocket::GetErrorDescription(res));
1066                        DoClose();
1067                        return FZ_REPLY_ERROR;
1068                }
1069        }
1070        else {
1071                if (m_pCurOpData && m_pCurOpData->opId == Command::connect) {
1072                        CConnectOpData* pData(static_cast<CConnectOpData*>(m_pCurOpData));
1073                        host = ConvertDomainName(pData->host);
1074                        port = pData->port;
1075                }
1076                if (host.empty()) {
1077                        host = m_pCurrentServer->GetHost();
1078                        port = m_pCurrentServer->GetPort();
1079                }
1080        }
1081        if (fz::get_address_type(host.ToStdWstring()) == fz::address_type::unknown) {
1082                LogMessage(MessageType::Status, _("Resolving address of %s"), host);
1083        }
1084
1085        int res = m_pSocket->Connect(host, port);
1086
1087        // Treat success same as EINPROGRESS, we wait for connect notification in any case
1088        if (res && res != EINPROGRESS) {
1089                LogMessage(MessageType::Error, _("Could not connect to server: %s"), CSocket::GetErrorDescription(res));
1090                DoClose();
1091                return FZ_REPLY_ERROR;
1092        }
1093
1094        return FZ_REPLY_WOULDBLOCK;
1095}
1096
1097int CRealControlSocket::DoClose(int nErrorCode /*=FZ_REPLY_DISCONNECTED*/)
1098{
1099        ResetSocket();
1100
1101        return CControlSocket::DoClose(nErrorCode);
1102}
1103
1104void CRealControlSocket::ResetSocket()
1105{
1106        m_pSocket->Close();
1107
1108        if (m_pSendBuffer)
1109        {
1110                delete [] m_pSendBuffer;
1111                m_pSendBuffer = 0;
1112                m_nSendBufferLen = 0;
1113        }
1114
1115        if (m_pProxyBackend)
1116        {
1117                if (m_pProxyBackend != m_pBackend)
1118                        delete m_pProxyBackend;
1119                m_pProxyBackend = 0;
1120        }
1121        delete m_pBackend;
1122        m_pBackend = 0;
1123}
1124
1125bool CControlSocket::SetFileExistsAction(CFileExistsNotification *pFileExistsNotification)
1126{
1127        wxASSERT(pFileExistsNotification);
1128
1129        if (!m_pCurOpData || m_pCurOpData->opId != Command::transfer)
1130        {
1131                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Info, _T("No or invalid operation in progress, ignoring request reply %f"), pFileExistsNotification->GetRequestID());
1132                return false;
1133        }
1134
1135        CFileTransferOpData *pData = static_cast<CFileTransferOpData *>(m_pCurOpData);
1136
1137        switch (pFileExistsNotification->overwriteAction)
1138        {
1139        case CFileExistsNotification::overwrite:
1140                SendNextCommand();
1141                break;
1142        case CFileExistsNotification::overwriteNewer:
1143                if (!pFileExistsNotification->localTime.empty() || !pFileExistsNotification->remoteTime.empty())
1144                        SendNextCommand();
1145                else if (pFileExistsNotification->download && pFileExistsNotification->localTime.earlier_than(pFileExistsNotification->remoteTime))
1146                        SendNextCommand();
1147                else if (!pFileExistsNotification->download && pFileExistsNotification->localTime.later_than(pFileExistsNotification->remoteTime))
1148                        SendNextCommand();
1149                else
1150                {
1151                        if (pData->download)
1152                        {
1153                                wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
1154                                LogMessage(MessageType::Status, _("Skipping download of %s"), filename);
1155                        }
1156                        else
1157                        {
1158                                LogMessage(MessageType::Status, _("Skipping upload of %s"), pData->localFile);
1159                        }
1160                        ResetOperation(FZ_REPLY_OK);
1161                }
1162                break;
1163        case CFileExistsNotification::overwriteSize:
1164                /* First compare flags both size known but different, one size known and the other not (obviously they are different).
1165                Second compare flags the remaining case in which we need to send command : both size unknown */
1166                if ((pFileExistsNotification->localSize != pFileExistsNotification->remoteSize) || (pFileExistsNotification->localSize < 0))
1167                        SendNextCommand();
1168                else {
1169                        if (pData->download) {
1170                                wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
1171                                LogMessage(MessageType::Status, _("Skipping download of %s"), filename);
1172                        }
1173                        else {
1174                                LogMessage(MessageType::Status, _("Skipping upload of %s"), pData->localFile);
1175                        }
1176                        ResetOperation(FZ_REPLY_OK);
1177                }
1178                break;
1179        case CFileExistsNotification::overwriteSizeOrNewer:
1180                if (!pFileExistsNotification->localTime.empty() || !pFileExistsNotification->remoteTime.empty())
1181                        SendNextCommand();
1182                /* First compare flags both size known but different, one size known and the other not (obviously they are different).
1183                Second compare flags the remaining case in which we need to send command : both size unknown */
1184                else if ((pFileExistsNotification->localSize != pFileExistsNotification->remoteSize) || (pFileExistsNotification->localSize < 0))
1185                        SendNextCommand();
1186                else if (pFileExistsNotification->download && pFileExistsNotification->localTime.earlier_than(pFileExistsNotification->remoteTime))
1187                        SendNextCommand();
1188                else if (!pFileExistsNotification->download && pFileExistsNotification->localTime.later_than(pFileExistsNotification->remoteTime))
1189                        SendNextCommand();
1190                else
1191                {
1192                        if (pData->download)
1193                        {
1194                                wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
1195                                LogMessage(MessageType::Status, _("Skipping download of %s"), filename);
1196                        }
1197                        else
1198                        {
1199                                LogMessage(MessageType::Status, _("Skipping upload of %s"), pData->localFile);
1200                        }
1201                        ResetOperation(FZ_REPLY_OK);
1202                }
1203                break;
1204        case CFileExistsNotification::resume:
1205                if (pData->download && pData->localFileSize >= 0)
1206                        pData->resume = true;
1207                else if (!pData->download && pData->remoteFileSize >= 0)
1208                        pData->resume = true;
1209                SendNextCommand();
1210                break;
1211        case CFileExistsNotification::rename:
1212                if (pData->download) {
1213                        wxFileName fn = pData->localFile;
1214                        fn.SetFullName(pFileExistsNotification->newName);
1215                        pData->localFile = fn.GetFullPath();
1216
1217                        int64_t size;
1218                        bool isLink;
1219                        if (fz::local_filesys::get_file_info(fz::to_native(pData->localFile), isLink, &size, 0, 0) == fz::local_filesys::file)
1220                                pData->localFileSize = size;
1221                        else
1222                                pData->localFileSize = -1;
1223
1224                        if (CheckOverwriteFile() == FZ_REPLY_OK)
1225                                SendNextCommand();
1226                }
1227                else {
1228                        pData->remoteFile = pFileExistsNotification->newName;
1229
1230                        CDirentry entry;
1231                        bool dir_did_exist;
1232                        bool matched_case;
1233                        if (engine_.GetDirectoryCache().LookupFile(entry, *m_pCurrentServer, pData->tryAbsolutePath ? pData->remotePath : m_CurrentPath, pData->remoteFile, dir_did_exist, matched_case) &&
1234                                matched_case)
1235                        {
1236                                pData->remoteFileSize = entry.size;
1237                                if (entry.has_date())
1238                                        pData->fileTime = entry.time;
1239
1240                                if (CheckOverwriteFile() != FZ_REPLY_OK)
1241                                        break;
1242                        }
1243                        else {
1244                                pData->fileTime = fz::datetime();
1245                                pData->remoteFileSize = -1;
1246                        }
1247
1248                        SendNextCommand();
1249                }
1250                break;
1251        case CFileExistsNotification::skip:
1252                if (pData->download)
1253                {
1254                        wxString filename = pData->remotePath.FormatFilename(pData->remoteFile);
1255                        LogMessage(MessageType::Status, _("Skipping download of %s"), filename);
1256                }
1257                else
1258                {
1259                        LogMessage(MessageType::Status, _("Skipping upload of %s"), pData->localFile);
1260                }
1261                ResetOperation(FZ_REPLY_OK);
1262                break;
1263        default:
1264                LogMessage(__TFILE__, __LINE__, this, MessageType::Debug_Warning, _T("Unknown file exists action: %d"), pFileExistsNotification->overwriteAction);
1265                ResetOperation(FZ_REPLY_INTERNALERROR);
1266                return false;
1267        }
1268
1269        return true;
1270}
1271
1272void CControlSocket::CreateLocalDir(const wxString &local_file)
1273{
1274        wxString file;
1275        CLocalPath local_path(local_file, &file);
1276        if (local_path.empty() || !local_path.HasParent())
1277                return;
1278
1279        // Only go back as far as needed. On comparison, wxWidgets'
1280        // wxFileName::Mkdir always starts at the root.
1281        std::vector<wxString> segments;
1282        while (!local_path.Exists() && local_path.HasParent()) {
1283                wxString segment;
1284                local_path.MakeParent(&segment);
1285                segments.push_back(segment);
1286        }
1287
1288        CLocalPath last_successful;
1289        for (auto iter = segments.rbegin(); iter != segments.rend(); ++iter) {
1290                local_path.AddSegment(*iter);
1291
1292#ifdef __WXMSW__
1293                BOOL res = CreateDirectory(local_path.GetPath().wc_str(), 0);
1294                if (!res && GetLastError() != ERROR_ALREADY_EXISTS)
1295                        break;
1296#else
1297                const wxCharBuffer s = local_path.GetPath().fn_str();
1298
1299                int res = mkdir(s, 0777);
1300                if (res && errno != EEXIST)
1301                        break;
1302#endif
1303                last_successful = local_path;
1304        }
1305
1306        if (last_successful.empty())
1307                return;
1308
1309        // Send out notification
1310        CLocalDirCreatedNotification *n = new CLocalDirCreatedNotification;
1311        n->dir = last_successful;
1312        engine_.AddNotification(n);
1313}
1314
1315int CControlSocket::List(CServerPath, wxString, int)
1316{
1317        return FZ_REPLY_NOTSUPPORTED;
1318}
1319
1320int CControlSocket::FileTransfer(const wxString, const CServerPath &,
1321                                        const wxString &, bool,
1322                                        const CFileTransferCommand::t_transferSettings&)
1323{
1324        return FZ_REPLY_NOTSUPPORTED;
1325}
1326
1327int CControlSocket::RawCommand(const wxString&)
1328{
1329        return FZ_REPLY_NOTSUPPORTED;
1330}
1331
1332int CControlSocket::Delete(const CServerPath&, std::deque<wxString>&&)
1333{
1334        return FZ_REPLY_NOTSUPPORTED;
1335}
1336
1337int CControlSocket::RemoveDir(const CServerPath&, const wxString&)
1338{
1339        return FZ_REPLY_NOTSUPPORTED;
1340}
1341
1342int CControlSocket::Mkdir(const CServerPath&)
1343{
1344        return FZ_REPLY_NOTSUPPORTED;
1345}
1346
1347int CControlSocket::Rename(const CRenameCommand&)
1348{
1349        return FZ_REPLY_NOTSUPPORTED;
1350}
1351
1352int CControlSocket::Chmod(const CChmodCommand&)
1353{
1354        return FZ_REPLY_NOTSUPPORTED;
1355}
1356
1357void CControlSocket::operator()(fz::event_base const& ev)
1358{
1359        fz::dispatch<fz::timer_event, CObtainLockEvent>(ev, this,
1360                &CControlSocket::OnTimer,
1361                &CControlSocket::OnObtainLock);
1362}
1363
1364void CControlSocket::SetActive(CFileZillaEngine::_direction direction)
1365{
1366        SetAlive();
1367        engine_.SetActive(direction);
1368}
Note: See TracBrowser for help on using the repository browser.