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