1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/ftp.cpp
3 // Purpose:     FTP protocol
4 // Author:      Guilhem Lavaux
5 // Modified by: Mark Johnson, wxWindows@mj10777.de
6 //              20000917 : RmDir, GetLastResult, GetList
7 //              Vadim Zeitlin (numerous fixes and rewrites to all part of the
8 //              code, support ASCII/Binary modes, better error reporting, more
9 //              robust Abort(), support for arbitrary FTP commands, ...)
10 //              Randall Fox (support for active mode)
11 // Created:     07/07/1997
12 // Copyright:   (c) 1997, 1998 Guilhem Lavaux
13 //              (c) 1998-2004 wxWidgets team
14 // Licence:     wxWindows licence
15 /////////////////////////////////////////////////////////////////////////////
16 
17 // ============================================================================
18 // declarations
19 // ============================================================================
20 
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24 
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
27 
28 
29 #if wxUSE_PROTOCOL_FTP
30 
31 #ifndef WX_PRECOMP
32     #include <stdlib.h>
33     #include "wx/string.h"
34     #include "wx/utils.h"
35     #include "wx/log.h"
36     #include "wx/intl.h"
37     #include "wx/wxcrtvararg.h"
38 #endif // WX_PRECOMP
39 
40 #include "wx/sckaddr.h"
41 #include "wx/socket.h"
42 #include "wx/url.h"
43 #include "wx/sckstrm.h"
44 #include "wx/protocol/protocol.h"
45 #include "wx/protocol/ftp.h"
46 
47 #include <memory.h>
48 
49 // ----------------------------------------------------------------------------
50 // constants
51 // ----------------------------------------------------------------------------
52 
53 // the length of FTP status code (3 digits)
54 static const size_t LEN_CODE = 3;
55 
56 // ----------------------------------------------------------------------------
57 // macros
58 // ----------------------------------------------------------------------------
59 
60 wxIMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol);
61 IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), true)
62 
63 // ============================================================================
64 // implementation
65 // ============================================================================
66 
67 // ----------------------------------------------------------------------------
68 // wxFTP constructor and destructor
69 // ----------------------------------------------------------------------------
70 
wxFTP()71 wxFTP::wxFTP()
72 {
73     m_streaming = false;
74     m_currentTransfermode = NONE;
75 
76     m_username = wxT("anonymous");
77     m_password << wxGetUserId() << wxT('@') << wxGetFullHostName();
78 
79     m_bPassive = true;
80     m_bEncounteredError = false;
81 }
82 
~wxFTP()83 wxFTP::~wxFTP()
84 {
85     if ( m_streaming )
86     {
87         // if we are streaming, this will issue
88         // an FTP ABORT command, to tell the server we are aborting
89         (void)Abort();
90     }
91 
92     // now this issues a "QUIT" command to tell the server we are
93     Close();
94 }
95 
96 // ----------------------------------------------------------------------------
97 // wxFTP connect and login methods
98 // ----------------------------------------------------------------------------
99 
Connect(const wxSockAddress & addr,bool WXUNUSED (wait))100 bool wxFTP::Connect(const wxSockAddress& addr, bool WXUNUSED(wait))
101 {
102     if ( !wxProtocol::Connect(addr) )
103     {
104         m_lastError = wxPROTO_NETERR;
105         return false;
106     }
107 
108     if ( !m_username )
109     {
110         m_lastError = wxPROTO_CONNERR;
111         return false;
112     }
113 
114     // we should have 220 welcome message
115     if ( !CheckResult('2') )
116     {
117         Close();
118         return false;
119     }
120 
121     wxString command;
122     command.Printf(wxT("USER %s"), m_username.c_str());
123     char rc = SendCommand(command);
124     if ( rc == '2' )
125     {
126         // 230 return: user accepted without password
127         m_lastError = wxPROTO_NOERR;
128         return true;
129     }
130 
131     if ( rc != '3' )
132     {
133         m_lastError = wxPROTO_CONNERR;
134         Close();
135         return false;
136     }
137 
138     command.Printf(wxT("PASS %s"), m_password.c_str());
139     if ( !CheckCommand(command, '2') )
140     {
141         m_lastError = wxPROTO_CONNERR;
142         Close();
143         return false;
144     }
145 
146     m_lastError = wxPROTO_NOERR;
147     return true;
148 }
149 
Connect(const wxString & host,unsigned short port)150 bool wxFTP::Connect(const wxString& host, unsigned short port)
151 {
152     wxIPV4address addr;
153     addr.Hostname(host);
154 
155     if ( port )
156         addr.Service(port);
157     else if (!addr.Service(wxT("ftp")))
158         addr.Service(21);
159 
160     return Connect(addr);
161 }
162 
Close()163 bool wxFTP::Close()
164 {
165     if ( m_streaming )
166     {
167         m_lastError = wxPROTO_STREAMING;
168         return false;
169     }
170 
171     if ( IsConnected() )
172     {
173         if ( !CheckCommand(wxT("QUIT"), '2') )
174         {
175             m_lastError = wxPROTO_CONNERR;
176             wxLogDebug(wxT("Failed to close connection gracefully."));
177         }
178     }
179 
180     return wxSocketClient::Close();
181 }
182 
183 // ============================================================================
184 // low level methods
185 // ============================================================================
186 
AcceptIfActive(wxSocketBase * sock)187 wxSocketBase *wxFTP::AcceptIfActive(wxSocketBase *sock)
188 {
189     if ( m_bPassive )
190         return sock;
191 
192     // now wait for a connection from server
193     wxSocketServer *sockSrv = (wxSocketServer *)sock;
194     if ( !sockSrv->WaitForAccept() )
195     {
196         m_lastError = wxPROTO_CONNERR;
197         wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
198         wxDELETE(sock);
199     }
200     else
201     {
202         m_lastError = wxPROTO_NOERR;
203         sock = sockSrv->Accept(true);
204         delete sockSrv;
205     }
206 
207     return sock;
208 }
209 
Abort()210 bool wxFTP::Abort()
211 {
212     if ( !m_streaming )
213         return true;
214 
215     m_streaming = false;
216     if ( !CheckCommand(wxT("ABOR"), '4') )
217         return false;
218 
219     return CheckResult('2');
220 }
221 
222 
223 // ----------------------------------------------------------------------------
224 // Send command to FTP server
225 // ----------------------------------------------------------------------------
226 
SendCommand(const wxString & command)227 char wxFTP::SendCommand(const wxString& command)
228 {
229     if ( m_streaming )
230     {
231         m_lastError = wxPROTO_STREAMING;
232         return 0;
233     }
234 
235     wxString tmp_str = command + wxT("\r\n");
236     const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
237     if ( Write(static_cast<const char *>(tmp_buf), strlen(tmp_buf)).Error())
238     {
239         m_lastError = wxPROTO_NETERR;
240         return 0;
241     }
242 
243     // don't show the passwords in the logs (even in debug ones)
244     wxString cmd, password;
245     if ( command.Upper().StartsWith(wxT("PASS "), &password) )
246     {
247         cmd << wxT("PASS ") << wxString(wxT('*'), password.length());
248     }
249     else
250     {
251         cmd = command;
252     }
253 
254     LogRequest(cmd);
255 
256     m_lastError = wxPROTO_NOERR;
257     return GetResult();
258 }
259 
260 // ----------------------------------------------------------------------------
261 // Receive servers reply
262 // ----------------------------------------------------------------------------
263 
GetResult()264 char wxFTP::GetResult()
265 {
266     // if we've already had a read or write timeout error, the connection is
267     // probably toast, so don't bother, it just wastes the users time
268     if ( m_bEncounteredError )
269         return 0;
270 
271     wxString code;
272 
273     // m_lastResult will contain the entire server response, possibly on
274     // multiple lines
275     m_lastResult.clear();
276 
277     // we handle multiline replies here according to RFC 959: it says that a
278     // reply may either be on 1 line of the form "xyz ..." or on several lines
279     // in whuch case it looks like
280     //      xyz-...
281     //      ...
282     //      xyz ...
283     // and the intermeidate lines may start with xyz or not
284     bool badReply = false;
285     bool firstLine = true;
286     bool endOfReply = false;
287     while ( !endOfReply && !badReply )
288     {
289         wxString line;
290         m_lastError = ReadLine(this,line);
291         if ( m_lastError )
292         {
293             m_bEncounteredError = true;
294             return 0;
295         }
296 
297         LogResponse(line);
298 
299         if ( !m_lastResult.empty() )
300         {
301             // separate from last line
302             m_lastResult += wxT('\n');
303         }
304 
305         m_lastResult += line;
306 
307         // unless this is an intermediate line of a multiline reply, it must
308         // contain the code in the beginning and '-' or ' ' following it
309         if ( line.Len() < LEN_CODE + 1 )
310         {
311             if ( firstLine )
312             {
313                 badReply = true;
314             }
315         }
316         else // line has at least 4 chars
317         {
318             // this is the char which tells us what we're dealing with
319             wxChar chMarker = line.GetChar(LEN_CODE);
320 
321             if ( firstLine )
322             {
323                 code = wxString(line, LEN_CODE);
324 
325                 switch ( chMarker )
326                 {
327                     case wxT(' '):
328                         endOfReply = true;
329                         break;
330 
331                     case wxT('-'):
332                         firstLine = false;
333                         break;
334 
335                     default:
336                         // unexpected
337                         badReply = true;
338                 }
339             }
340             else // subsequent line of multiline reply
341             {
342                 if ( line.compare(0, LEN_CODE, code) == 0 )
343                 {
344                     if ( chMarker == wxT(' ') )
345                     {
346                         endOfReply = true;
347                     }
348                 }
349             }
350         }
351     }
352 
353     if ( badReply )
354     {
355         wxLogDebug(wxT("Broken FTP server: '%s' is not a valid reply."),
356                    m_lastResult.c_str());
357 
358         m_lastError = wxPROTO_PROTERR;
359 
360         return 0;
361     }
362     else
363         m_lastError = wxPROTO_NOERR;
364 
365     // if we got here we must have a non empty code string
366     return (char)code[0u];
367 }
368 
369 // ----------------------------------------------------------------------------
370 // wxFTP simple commands
371 // ----------------------------------------------------------------------------
372 
SetTransferMode(TransferMode transferMode)373 bool wxFTP::SetTransferMode(TransferMode transferMode)
374 {
375     if ( transferMode == m_currentTransfermode )
376     {
377         // nothing to do
378         return true;
379     }
380 
381     wxString mode;
382     switch ( transferMode )
383     {
384         default:
385             wxFAIL_MSG(wxT("unknown FTP transfer mode"));
386             wxFALLTHROUGH;
387 
388         case BINARY:
389             mode = wxT('I');
390             break;
391 
392         case ASCII:
393             mode = wxT('A');
394             break;
395     }
396 
397     if ( !DoSimpleCommand(wxT("TYPE"), mode) )
398     {
399         wxLogError(_("Failed to set FTP transfer mode to %s."),
400                    (transferMode == ASCII ? _("ASCII") : _("binary")));
401 
402         return false;
403     }
404 
405     // If we get here the operation has been successfully completed
406     // Set the status-member
407     m_currentTransfermode = transferMode;
408 
409     return true;
410 }
411 
DoSimpleCommand(const wxChar * command,const wxString & arg)412 bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
413 {
414     wxString fullcmd = command;
415     if ( !arg.empty() )
416     {
417         fullcmd << wxT(' ') << arg;
418     }
419 
420     if ( !CheckCommand(fullcmd, '2') )
421     {
422         wxLogDebug(wxT("FTP command '%s' failed."), fullcmd.c_str());
423         m_lastError = wxPROTO_NETERR;
424 
425         return false;
426     }
427 
428     m_lastError = wxPROTO_NOERR;
429     return true;
430 }
431 
ChDir(const wxString & dir)432 bool wxFTP::ChDir(const wxString& dir)
433 {
434     // some servers might not understand ".." if they use different directory
435     // tree conventions, but they always understand CDUP - should we use it if
436     // dir == ".."? OTOH, do such servers (still) exist?
437 
438     return DoSimpleCommand(wxT("CWD"), dir);
439 }
440 
MkDir(const wxString & dir)441 bool wxFTP::MkDir(const wxString& dir)
442 {
443     return DoSimpleCommand(wxT("MKD"), dir);
444 }
445 
RmDir(const wxString & dir)446 bool wxFTP::RmDir(const wxString& dir)
447 {
448     return DoSimpleCommand(wxT("RMD"), dir);
449 }
450 
Pwd()451 wxString wxFTP::Pwd()
452 {
453     wxString path;
454 
455     if ( CheckCommand(wxT("PWD"), '2') )
456     {
457         // the result is at least that long if CheckCommand() succeeded
458         wxString::const_iterator p = m_lastResult.begin() + LEN_CODE + 1;
459         const wxString::const_iterator end = m_lastResult.end();
460         if ( p == end || *p != wxT('"') )
461         {
462             wxLogDebug(wxT("Missing starting quote in reply for PWD: %s"),
463                        wxString(p, end));
464         }
465         else
466         {
467             for ( ++p; p != end; ++p )
468             {
469                 if ( *p == wxT('"') )
470                 {
471                     // check if the quote is doubled
472                     ++p;
473                     if ( p == end || *p != wxT('"') )
474                     {
475                         // no, this is the end
476                         break;
477                     }
478                     //else: yes, it is: this is an embedded quote in the
479                     //      filename, treat as normal char
480                 }
481 
482                 path += *p;
483             }
484 
485             if ( p != end )
486             {
487                 wxLogDebug(wxT("Missing ending quote in reply for PWD: %s"),
488                            m_lastResult.c_str() + LEN_CODE + 1);
489             }
490         }
491     }
492     else
493     {
494         m_lastError = wxPROTO_PROTERR;
495         wxLogDebug(wxT("FTP PWD command failed."));
496     }
497 
498     return path;
499 }
500 
Rename(const wxString & src,const wxString & dst)501 bool wxFTP::Rename(const wxString& src, const wxString& dst)
502 {
503     wxString str;
504 
505     str = wxT("RNFR ") + src;
506     if ( !CheckCommand(str, '3') )
507         return false;
508 
509     str = wxT("RNTO ") + dst;
510 
511     return CheckCommand(str, '2');
512 }
513 
RmFile(const wxString & path)514 bool wxFTP::RmFile(const wxString& path)
515 {
516     wxString str;
517     str = wxT("DELE ") + path;
518 
519     return CheckCommand(str, '2');
520 }
521 
522 // ----------------------------------------------------------------------------
523 // wxFTP port methods
524 // ----------------------------------------------------------------------------
525 
GetPort()526 wxSocketBase *wxFTP::GetPort()
527 {
528     /*
529     PASSIVE:    Client sends a "PASV" to the server.  The server responds with
530                 an address and port number which it will be listening on. Then
531                 the client connects to the server at the specified address and
532                 port.
533 
534     ACTIVE:     Client sends the server a PORT command which includes an
535                 address and port number which the client will be listening on.
536                 The server then connects to the client at that address and
537                 port.
538     */
539 
540     wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
541     if ( !socket )
542     {
543         m_bEncounteredError = true;
544         return NULL;
545     }
546 
547     // Now set the time for the new socket to the default or user selected
548     // timeout period
549     socket->SetTimeout(m_uiDefaultTimeout);
550 
551     return socket;
552 }
553 
GetPortCmdArgument(const wxIPV4address & addrLocal,const wxIPV4address & addrNew)554 wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
555                                    const wxIPV4address& addrNew)
556 {
557     // Just fills in the return value with the local IP
558     // address of the current socket.  Also it fill in the
559     // PORT which the client will be listening on
560 
561     wxString addrIP = addrLocal.IPAddress();
562     int portNew = addrNew.Service();
563 
564     // We need to break the PORT number in bytes
565     addrIP.Replace(wxT("."), wxT(","));
566     addrIP << wxT(',')
567            << wxString::Format(wxT("%d"), portNew >> 8) << wxT(',')
568            << wxString::Format(wxT("%d"), portNew & 0xff);
569 
570     // Now we have a value like "10,0,0,1,5,23"
571     return addrIP;
572 }
573 
GetActivePort()574 wxSocketBase *wxFTP::GetActivePort()
575 {
576     // we need an address to listen on
577     wxIPV4address addrNew, addrLocal;
578     GetLocal(addrLocal);
579     addrNew.AnyAddress();
580     addrNew.Service(0); // pick an open port number.
581 
582     wxSocketServer* const
583         sockSrv = new wxSocketServer
584                       (
585                         addrNew,
586                         wxSocketServer::GetBlockingFlagIfNeeded()
587                       );
588 
589     if (!sockSrv->IsOk())
590     {
591         // We use IsOk() here to see if everything is ok
592         m_lastError = wxPROTO_PROTERR;
593         delete sockSrv;
594         return NULL;
595     }
596 
597     //gets the new address, actually it is just the port number
598     sockSrv->GetLocal(addrNew);
599 
600     // Now we create the argument of the PORT command, we send in both
601     // addresses because the addrNew has an IP of "0.0.0.0", so we need the
602     // value in addrLocal
603     wxString port = GetPortCmdArgument(addrLocal, addrNew);
604     if ( !DoSimpleCommand(wxT("PORT"), port) )
605     {
606         m_lastError = wxPROTO_PROTERR;
607         delete sockSrv;
608         wxLogError(_("The FTP server doesn't support the PORT command."));
609         return NULL;
610     }
611 
612     m_lastError = wxPROTO_NOERR;
613     sockSrv->Notify(false); // Don't send any events
614     return sockSrv;
615 }
616 
GetPassivePort()617 wxSocketBase *wxFTP::GetPassivePort()
618 {
619     if ( !DoSimpleCommand(wxT("PASV")) )
620     {
621         m_lastError = wxPROTO_PROTERR;
622         wxLogError(_("The FTP server doesn't support passive mode."));
623         return NULL;
624     }
625 
626     size_t addrStart = m_lastResult.find(wxT('('));
627     size_t addrEnd = (addrStart == wxString::npos)
628                      ? wxString::npos
629                      : m_lastResult.find(wxT(')'), addrStart);
630 
631     if ( addrEnd == wxString::npos )
632     {
633         m_lastError = wxPROTO_PROTERR;
634         return NULL;
635     }
636 
637     // get the port number and address
638     int a[6];
639     wxString straddr(m_lastResult, addrStart + 1, addrEnd - (addrStart + 1));
640     wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
641              &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
642 
643     wxUint32 hostaddr = (wxUint16)a[2] << 24 |
644                         (wxUint16)a[3] << 16 |
645                         (wxUint16)a[4] << 8 |
646                         a[5];
647     wxUint16 port = (wxUint16)(a[0] << 8 | a[1]);
648 
649     wxIPV4address addr;
650     addr.Hostname(hostaddr);
651     addr.Service(port);
652 
653     // If we're used from a worker thread or can't dispatch events even though
654     // we're in the main one, we can't use non-blocking sockets.
655     wxSocketClient* const
656         client = new wxSocketClient(wxSocketClient::GetBlockingFlagIfNeeded());
657 
658     if ( !client->Connect(addr) )
659     {
660         m_lastError = wxPROTO_CONNERR;
661         delete client;
662         return NULL;
663     }
664 
665     client->Notify(false);
666 
667     m_lastError = wxPROTO_NOERR;
668     return client;
669 }
670 
671 
672 // ----------------------------------------------------------------------------
673 // wxFTP download and upload
674 // ----------------------------------------------------------------------------
675 
676 class wxInputFTPStream : public wxSocketInputStream
677 {
678 public:
wxInputFTPStream(wxFTP * ftp,wxSocketBase * sock)679     wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
680         : wxSocketInputStream(*sock)
681     {
682         m_ftp = ftp;
683         // socket timeout automatically set in GetPort function
684     }
685 
~wxInputFTPStream()686     virtual ~wxInputFTPStream()
687     {
688         delete m_i_socket;   // keep at top
689 
690         // when checking the result, the stream will
691         // almost always show an error, even if the file was
692         // properly transferred, thus, let's just grab the result
693 
694         // we are looking for "226 transfer completed"
695         char code = m_ftp->GetResult();
696         if ('2' == code)
697         {
698             // it was a good transfer.
699             // we're done!
700              m_ftp->m_streaming = false;
701             return;
702         }
703         // did we timeout?
704         if (0 == code)
705         {
706             // the connection is probably toast. issue an abort, and
707             // then a close. there won't be any more waiting
708             // for this connection
709             m_ftp->Abort();
710             m_ftp->Close();
711             return;
712         }
713         // There was a problem with the transfer and the server
714         // has acknowledged it.  If we issue an "ABORT" now, the user
715         // would get the "226" for the abort and think the xfer was
716         // complete, thus, don't do anything here, just return
717     }
718 
719     wxFTP *m_ftp;
720 
721     wxDECLARE_NO_COPY_CLASS(wxInputFTPStream);
722 };
723 
724 class wxOutputFTPStream : public wxSocketOutputStream
725 {
726 public:
wxOutputFTPStream(wxFTP * ftp_clt,wxSocketBase * sock)727     wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
728         : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
729     {
730     }
731 
~wxOutputFTPStream()732     virtual ~wxOutputFTPStream()
733     {
734         if ( IsOk() )
735         {
736             // close data connection first, this will generate "transfer
737             // completed" reply
738             delete m_o_socket;
739 
740             // read this reply
741             m_ftp->GetResult(); // save result so user can get to it
742 
743             m_ftp->m_streaming = false;
744         }
745         else
746         {
747             // abort data connection first
748             m_ftp->Abort();
749 
750             // and close it after
751             delete m_o_socket;
752         }
753     }
754 
755     wxFTP *m_ftp;
756 
757     wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream);
758 };
759 
GetInputStream(const wxString & path)760 wxInputStream *wxFTP::GetInputStream(const wxString& path)
761 {
762     if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
763     {
764         m_lastError = wxPROTO_CONNERR;
765         return NULL;
766     }
767 
768     wxSocketBase *sock = GetPort();
769 
770     if ( !sock )
771     {
772         m_lastError = wxPROTO_NETERR;
773         return NULL;
774     }
775 
776     wxString tmp_str = wxT("RETR ") + wxURI::Unescape(path);
777     if ( !CheckCommand(tmp_str, '1') )
778     {
779         delete sock;
780 
781         return NULL;
782     }
783 
784     sock = AcceptIfActive(sock);
785     if ( !sock )
786     {
787         m_lastError = wxPROTO_CONNERR;
788         return NULL;
789     }
790 
791     m_streaming = true;
792 
793     wxInputFTPStream *in_stream = new wxInputFTPStream(this, sock);
794 
795     m_lastError = wxPROTO_NOERR;
796     return in_stream;
797 }
798 
GetOutputStream(const wxString & path)799 wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
800 {
801     if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
802     {
803         m_lastError = wxPROTO_CONNERR;
804         return NULL;
805     }
806 
807     wxSocketBase *sock = GetPort();
808 
809     wxString tmp_str = wxT("STOR ") + path;
810     if ( !CheckCommand(tmp_str, '1') )
811     {
812         delete sock;
813 
814         return NULL;
815     }
816 
817     sock = AcceptIfActive(sock);
818 
819     m_streaming = true;
820 
821     m_lastError = wxPROTO_NOERR;
822     return new wxOutputFTPStream(this, sock);
823 }
824 
825 // ----------------------------------------------------------------------------
826 // FTP directory listing
827 // ----------------------------------------------------------------------------
828 
GetList(wxArrayString & files,const wxString & wildcard,bool details)829 bool wxFTP::GetList(wxArrayString& files,
830                     const wxString& wildcard,
831                     bool details)
832 {
833     wxSocketBase *sock = GetPort();
834     if (!sock) {
835         m_lastError = wxPROTO_NETERR;
836         return false;
837     }
838 
839     // NLST : List of Filenames (including Directory's !)
840     // LIST : depending on BS of FTP-Server
841     //        - Unix    : result like "ls" command
842     //        - Windows : like "dir" command
843     //        - others  : ?
844     wxString line(details ? wxT("LIST") : wxT("NLST"));
845     if ( !wildcard.empty() )
846     {
847         line << wxT(' ') << wildcard;
848     }
849 
850     if ( !CheckCommand(line, '1') )
851     {
852         m_lastError = wxPROTO_PROTERR;
853         wxLogDebug(wxT("FTP 'LIST' command returned unexpected result from server"));
854         delete sock;
855         return false;
856     }
857 
858     sock = AcceptIfActive(sock);
859     if ( !sock ) {
860         m_lastError = wxPROTO_CONNERR;
861         return false;
862     }
863 
864     files.Empty();
865     while (ReadLine(sock, line) == wxPROTO_NOERR )
866     {
867         files.Add(line);
868     }
869 
870     delete sock;
871 
872     // the file list should be terminated by "226 Transfer complete""
873     m_lastError = wxPROTO_NOERR;
874     return CheckResult('2');
875 }
876 
FileExists(const wxString & fileName)877 bool wxFTP::FileExists(const wxString& fileName)
878 {
879     // This function checks if the file specified in fileName exists in the
880     // current dir. It does so by simply doing an NLST (via GetList).
881     // If this succeeds (and the list is not empty) the file exists.
882 
883     bool retval = false;
884     wxArrayString fileList;
885 
886     if ( GetList(fileList, fileName, false) )
887     {
888         // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
889         // displays this behaviour when queried on a nonexistent file:
890         // NLST this_file_does_not_exist
891         // 150 Opening ASCII data connection for directory listing
892         // (no data transferred)
893         // 226 Transfer complete
894         // Here wxFTP::GetList(...) will succeed but it will return an empty
895         // list.
896         retval = !fileList.IsEmpty();
897     }
898 
899     return retval;
900 }
901 
902 // ----------------------------------------------------------------------------
903 // FTP GetSize
904 // ----------------------------------------------------------------------------
905 
GetFileSize(const wxString & fileName)906 int wxFTP::GetFileSize(const wxString& fileName)
907 {
908     // return the filesize of the given file if possible
909     // return -1 otherwise (predominantly if file doesn't exist
910     // in current dir)
911 
912     int filesize = -1;
913 
914     // Check for existence of file via wxFTP::FileExists(...)
915     if ( FileExists(fileName) )
916     {
917         wxString command;
918 
919         // First try "SIZE" command using BINARY(IMAGE) transfermode
920         // Especially UNIX ftp-servers distinguish between the different
921         // transfermodes and reports different filesizes accordingly.
922         // The BINARY size is the interesting one: How much memory
923         // will we need to hold this file?
924         TransferMode oldTransfermode = m_currentTransfermode;
925         SetTransferMode(BINARY);
926         command << wxT("SIZE ") << fileName;
927 
928         bool ok = CheckCommand(command, '2');
929 
930         if ( ok )
931         {
932             // The answer should be one line: "213 <filesize>\n"
933             // 213 is File Status (STD9)
934             // "SIZE" is not described anywhere..? It works on most servers
935             int statuscode;
936             if ( wxSscanf(GetLastResult().c_str(), wxT("%i %i"),
937                           &statuscode, &filesize) == 2 )
938             {
939                 // We've gotten a good reply.
940                 ok = true;
941             }
942             else
943             {
944                 // Something bad happened.. A "2yz" reply with no size
945                 // Fallback
946                 ok = false;
947             }
948         }
949 
950         // Set transfermode back to the original. Only the "SIZE"-command
951         // is dependent on transfermode
952         if ( oldTransfermode != NONE )
953         {
954             SetTransferMode(oldTransfermode);
955         }
956 
957         // this is not a direct else clause.. The size command might return an
958         // invalid "2yz" reply
959         if ( !ok )
960         {
961             // The server didn't understand the "SIZE"-command or it
962             // returned an invalid reply.
963             // We now try to get details for the file with a "LIST"-command
964             // and then parse the output from there..
965             wxArrayString fileList;
966             if ( GetList(fileList, fileName, true) )
967             {
968                 if ( !fileList.IsEmpty() )
969                 {
970                     // We _should_ only get one line in return, but just to be
971                     // safe we run through the line(s) returned and look for a
972                     // substring containing the name we are looking for. We
973                     // stop the iteration at the first occurrence of the
974                     // filename. The search is not case-sensitive.
975                     const size_t numFiles = fileList.size();
976                     size_t i;
977                     for ( i = 0; i < fileList.GetCount(); i++ )
978                     {
979                         if ( fileList[i].Upper().Contains(fileName.Upper()) )
980                             break;
981                     }
982 
983                     if ( i != numFiles )
984                     {
985                         // The index i points to the first occurrence of
986                         // fileName in the array Now we have to find out what
987                         // format the LIST has returned. There are two
988                         // "schools": Unix-like
989                         //
990                         // '-rw-rw-rw- owner group size month day time filename'
991                         //
992                         // or Windows-like
993                         //
994                         // 'date size filename'
995 
996                         // check if the first character is '-'. This would
997                         // indicate Unix-style (this also limits this function
998                         // to searching for files, not directories)
999                         if ( fileList[i].Mid(0, 1) == wxT("-") )
1000                         {
1001 
1002                             if ( wxSscanf(fileList[i].c_str(),
1003                                           wxT("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
1004                                           &filesize) != 9 )
1005                             {
1006                                 // Hmm... Invalid response
1007                                 wxLogDebug(wxT("Invalid LIST response"));
1008                             }
1009                         }
1010                         else // Windows-style response (?)
1011                         {
1012                             if ( wxSscanf(fileList[i].c_str(),
1013                                           wxT("%*s %*s %i %*s"),
1014                                           &filesize) != 4 )
1015                             {
1016                                 // something bad happened..?
1017                                 wxLogDebug(wxT("Invalid or unknown LIST response"));
1018                             }
1019                         }
1020                     }
1021                 }
1022             }
1023         }
1024     }
1025 
1026     // filesize might still be -1 when exiting
1027     return filesize;
1028 }
1029 
1030 #endif // wxUSE_PROTOCOL_FTP
1031 
1032