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