don't use wxScopedPtr<> in wxDocTemplate::CreateDocument() as the document is implici...
[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 LogRequest(cmd);
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 LogResponse(line);
305
306 if ( !m_lastResult.empty() )
307 {
308 // separate from last line
309 m_lastResult += _T('\n');
310 }
311
312 m_lastResult += line;
313
314 // unless this is an intermediate line of a multiline reply, it must
315 // contain the code in the beginning and '-' or ' ' following it
316 if ( line.Len() < LEN_CODE + 1 )
317 {
318 if ( firstLine )
319 {
320 badReply = true;
321 }
322 }
323 else // line has at least 4 chars
324 {
325 // this is the char which tells us what we're dealing with
326 wxChar chMarker = line.GetChar(LEN_CODE);
327
328 if ( firstLine )
329 {
330 code = wxString(line, LEN_CODE);
331
332 switch ( chMarker )
333 {
334 case _T(' '):
335 endOfReply = true;
336 break;
337
338 case _T('-'):
339 firstLine = false;
340 break;
341
342 default:
343 // unexpected
344 badReply = true;
345 }
346 }
347 else // subsequent line of multiline reply
348 {
349 if ( line.compare(0, LEN_CODE, code) == 0 )
350 {
351 if ( chMarker == _T(' ') )
352 {
353 endOfReply = true;
354 }
355 }
356 }
357 }
358 }
359
360 if ( badReply )
361 {
362 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
363 m_lastResult.c_str());
364
365 m_lastError = wxPROTO_PROTERR;
366
367 return 0;
368 }
369 else
370 m_lastError = wxPROTO_NOERR;
371
372 // if we got here we must have a non empty code string
373 return (char)code[0u];
374 }
375
376 // ----------------------------------------------------------------------------
377 // wxFTP simple commands
378 // ----------------------------------------------------------------------------
379
380 bool wxFTP::SetTransferMode(TransferMode transferMode)
381 {
382 if ( transferMode == m_currentTransfermode )
383 {
384 // nothing to do
385 return true;
386 }
387
388 wxString mode;
389 switch ( transferMode )
390 {
391 default:
392 wxFAIL_MSG(_T("unknown FTP transfer mode"));
393 // fall through
394
395 case BINARY:
396 mode = _T('I');
397 break;
398
399 case ASCII:
400 mode = _T('A');
401 break;
402 }
403
404 if ( !DoSimpleCommand(_T("TYPE"), mode) )
405 {
406 wxLogError(_("Failed to set FTP transfer mode to %s."),
407 (transferMode == ASCII ? _("ASCII") : _("binary")));
408
409 return false;
410 }
411
412 // If we get here the operation has been successfully completed
413 // Set the status-member
414 m_currentTransfermode = transferMode;
415
416 return true;
417 }
418
419 bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
420 {
421 wxString fullcmd = command;
422 if ( !arg.empty() )
423 {
424 fullcmd << _T(' ') << arg;
425 }
426
427 if ( !CheckCommand(fullcmd, '2') )
428 {
429 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
430 m_lastError = wxPROTO_NETERR;
431
432 return false;
433 }
434
435 m_lastError = wxPROTO_NOERR;
436 return true;
437 }
438
439 bool wxFTP::ChDir(const wxString& dir)
440 {
441 // some servers might not understand ".." if they use different directory
442 // tree conventions, but they always understand CDUP - should we use it if
443 // dir == ".."? OTOH, do such servers (still) exist?
444
445 return DoSimpleCommand(_T("CWD"), dir);
446 }
447
448 bool wxFTP::MkDir(const wxString& dir)
449 {
450 return DoSimpleCommand(_T("MKD"), dir);
451 }
452
453 bool wxFTP::RmDir(const wxString& dir)
454 {
455 return DoSimpleCommand(_T("RMD"), dir);
456 }
457
458 wxString wxFTP::Pwd()
459 {
460 wxString path;
461
462 if ( CheckCommand(wxT("PWD"), '2') )
463 {
464 // the result is at least that long if CheckCommand() succeeded
465 wxString::const_iterator p = m_lastResult.begin() + LEN_CODE + 1;
466 if ( *p != _T('"') )
467 {
468 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
469 wxString(p, m_lastResult.end()));
470 }
471 else
472 {
473 for ( ++p; (bool)*p; ++p ) // FIXME-DMARS
474 {
475 if ( *p == _T('"') )
476 {
477 // check if the quote is doubled
478 ++p;
479 if ( !*p || *p != _T('"') )
480 {
481 // no, this is the end
482 break;
483 }
484 //else: yes, it is: this is an embedded quote in the
485 // filename, treat as normal char
486 }
487
488 path += *p;
489 }
490
491 if ( !*p )
492 {
493 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
494 m_lastResult.c_str() + LEN_CODE + 1);
495 }
496 }
497 }
498 else
499 {
500 m_lastError = wxPROTO_PROTERR;
501 wxLogDebug(_T("FTP PWD command failed."));
502 }
503
504 return path;
505 }
506
507 bool wxFTP::Rename(const wxString& src, const wxString& dst)
508 {
509 wxString str;
510
511 str = wxT("RNFR ") + src;
512 if ( !CheckCommand(str, '3') )
513 return false;
514
515 str = wxT("RNTO ") + dst;
516
517 return CheckCommand(str, '2');
518 }
519
520 bool wxFTP::RmFile(const wxString& path)
521 {
522 wxString str;
523 str = wxT("DELE ") + path;
524
525 return CheckCommand(str, '2');
526 }
527
528 // ----------------------------------------------------------------------------
529 // wxFTP port methods
530 // ----------------------------------------------------------------------------
531
532 wxSocketBase *wxFTP::GetPort()
533 {
534 /*
535 PASSIVE: Client sends a "PASV" to the server. The server responds with
536 an address and port number which it will be listening on. Then
537 the client connects to the server at the specified address and
538 port.
539
540 ACTIVE: Client sends the server a PORT command which includes an
541 address and port number which the client will be listening on.
542 The server then connects to the client at that address and
543 port.
544 */
545
546 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
547 if ( !socket )
548 {
549 m_bEncounteredError = true;
550 return NULL;
551 }
552
553 // Now set the time for the new socket to the default or user selected
554 // timeout period
555 socket->SetTimeout(m_uiDefaultTimeout);
556
557 return socket;
558 }
559
560 wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
561 const wxIPV4address& addrNew)
562 {
563 // Just fills in the return value with the local IP
564 // address of the current socket. Also it fill in the
565 // PORT which the client will be listening on
566
567 wxString addrIP = addrLocal.IPAddress();
568 int portNew = addrNew.Service();
569
570 // We need to break the PORT number in bytes
571 addrIP.Replace(_T("."), _T(","));
572 addrIP << _T(',')
573 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
574 << wxString::Format(_T("%d"), portNew & 0xff);
575
576 // Now we have a value like "10,0,0,1,5,23"
577 return addrIP;
578 }
579
580 wxSocketBase *wxFTP::GetActivePort()
581 {
582 // we need an address to listen on
583 wxIPV4address addrNew, addrLocal;
584 GetLocal(addrLocal);
585 addrNew.AnyAddress();
586 addrNew.Service(0); // pick an open port number.
587
588 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
589 if (!sockSrv->Ok())
590 {
591 // We use Ok() 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(_T("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
617 wxSocketBase *wxFTP::GetPassivePort()
618 {
619 if ( !DoSimpleCommand(_T("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(_T('('));
627 size_t addrEnd = (addrStart == wxString::npos)
628 ? wxString::npos
629 : m_lastResult.find(_T(')'), 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 wxSocketClient *client = new wxSocketClient();
654 if ( !client->Connect(addr) )
655 {
656 m_lastError = wxPROTO_CONNERR;
657 delete client;
658 return NULL;
659 }
660
661 client->Notify(false);
662
663 m_lastError = wxPROTO_NOERR;
664 return client;
665 }
666
667
668 // ----------------------------------------------------------------------------
669 // wxFTP download and upload
670 // ----------------------------------------------------------------------------
671
672 class wxInputFTPStream : public wxSocketInputStream
673 {
674 public:
675 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
676 : wxSocketInputStream(*sock)
677 {
678 m_ftp = ftp;
679 // socket timeout automatically set in GetPort function
680 }
681
682 virtual ~wxInputFTPStream()
683 {
684 delete m_i_socket; // keep at top
685
686 // when checking the result, the stream will
687 // almost always show an error, even if the file was
688 // properly transfered, thus, lets just grab the result
689
690 // we are looking for "226 transfer completed"
691 char code = m_ftp->GetResult();
692 if ('2' == code)
693 {
694 // it was a good transfer.
695 // we're done!
696 m_ftp->m_streaming = false;
697 return;
698 }
699 // did we timeout?
700 if (0 == code)
701 {
702 // the connection is probably toast. issue an abort, and
703 // then a close. there won't be any more waiting
704 // for this connection
705 m_ftp->Abort();
706 m_ftp->Close();
707 return;
708 }
709 // There was a problem with the transfer and the server
710 // has acknowledged it. If we issue an "ABORT" now, the user
711 // would get the "226" for the abort and think the xfer was
712 // complete, thus, don't do anything here, just return
713 }
714
715 wxFTP *m_ftp;
716
717 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream);
718 };
719
720 class wxOutputFTPStream : public wxSocketOutputStream
721 {
722 public:
723 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
724 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
725 {
726 }
727
728 virtual ~wxOutputFTPStream(void)
729 {
730 if ( IsOk() )
731 {
732 // close data connection first, this will generate "transfer
733 // completed" reply
734 delete m_o_socket;
735
736 // read this reply
737 m_ftp->GetResult(); // save result so user can get to it
738
739 m_ftp->m_streaming = false;
740 }
741 else
742 {
743 // abort data connection first
744 m_ftp->Abort();
745
746 // and close it after
747 delete m_o_socket;
748 }
749 }
750
751 wxFTP *m_ftp;
752
753 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream);
754 };
755
756 wxInputStream *wxFTP::GetInputStream(const wxString& path)
757 {
758 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
759 {
760 m_lastError = wxPROTO_CONNERR;
761 return NULL;
762 }
763
764 wxSocketBase *sock = GetPort();
765
766 if ( !sock )
767 {
768 m_lastError = wxPROTO_NETERR;
769 return NULL;
770 }
771
772 wxString tmp_str = wxT("RETR ") + wxURI::Unescape(path);
773 if ( !CheckCommand(tmp_str, '1') )
774 return NULL;
775
776 sock = AcceptIfActive(sock);
777 if ( !sock )
778 {
779 m_lastError = wxPROTO_CONNERR;
780 return NULL;
781 }
782
783 sock->SetFlags(wxSOCKET_WAITALL);
784
785 m_streaming = true;
786
787 wxInputFTPStream *in_stream = new wxInputFTPStream(this, sock);
788
789 m_lastError = wxPROTO_NOERR;
790 return in_stream;
791 }
792
793 wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
794 {
795 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
796 {
797 m_lastError = wxPROTO_CONNERR;
798 return NULL;
799 }
800
801 wxSocketBase *sock = GetPort();
802
803 wxString tmp_str = wxT("STOR ") + path;
804 if ( !CheckCommand(tmp_str, '1') )
805 return NULL;
806
807 sock = AcceptIfActive(sock);
808
809 m_streaming = true;
810
811 m_lastError = wxPROTO_NOERR;
812 return new wxOutputFTPStream(this, sock);
813 }
814
815 // ----------------------------------------------------------------------------
816 // FTP directory listing
817 // ----------------------------------------------------------------------------
818
819 bool wxFTP::GetList(wxArrayString& files,
820 const wxString& wildcard,
821 bool details)
822 {
823 wxSocketBase *sock = GetPort();
824 if (!sock) {
825 m_lastError = wxPROTO_NETERR;
826 return false;
827 }
828
829 // NLST : List of Filenames (including Directory's !)
830 // LIST : depending on BS of FTP-Server
831 // - Unix : result like "ls" command
832 // - Windows : like "dir" command
833 // - others : ?
834 wxString line(details ? _T("LIST") : _T("NLST"));
835 if ( !wildcard.empty() )
836 {
837 line << _T(' ') << wildcard;
838 }
839
840 if ( !CheckCommand(line, '1') )
841 {
842 m_lastError = wxPROTO_PROTERR;
843 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
844 delete sock;
845 return false;
846 }
847
848 sock = AcceptIfActive(sock);
849 if ( !sock ) {
850 m_lastError = wxPROTO_CONNERR;
851 return false;
852 }
853
854 files.Empty();
855 while (ReadLine(sock, line) == wxPROTO_NOERR )
856 {
857 files.Add(line);
858 }
859
860 delete sock;
861
862 // the file list should be terminated by "226 Transfer complete""
863 m_lastError = wxPROTO_NOERR;
864 return CheckResult('2');
865 }
866
867 bool wxFTP::FileExists(const wxString& fileName)
868 {
869 // This function checks if the file specified in fileName exists in the
870 // current dir. It does so by simply doing an NLST (via GetList).
871 // If this succeeds (and the list is not empty) the file exists.
872
873 bool retval = false;
874 wxArrayString fileList;
875
876 if ( GetList(fileList, fileName, false) )
877 {
878 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
879 // displays this behaviour when queried on a nonexistent file:
880 // NLST this_file_does_not_exist
881 // 150 Opening ASCII data connection for directory listing
882 // (no data transferred)
883 // 226 Transfer complete
884 // Here wxFTP::GetList(...) will succeed but it will return an empty
885 // list.
886 retval = !fileList.IsEmpty();
887 }
888
889 return retval;
890 }
891
892 // ----------------------------------------------------------------------------
893 // FTP GetSize
894 // ----------------------------------------------------------------------------
895
896 int wxFTP::GetFileSize(const wxString& fileName)
897 {
898 // return the filesize of the given file if possible
899 // return -1 otherwise (predominantly if file doesn't exist
900 // in current dir)
901
902 int filesize = -1;
903
904 // Check for existance of file via wxFTP::FileExists(...)
905 if ( FileExists(fileName) )
906 {
907 wxString command;
908
909 // First try "SIZE" command using BINARY(IMAGE) transfermode
910 // Especially UNIX ftp-servers distinguish between the different
911 // transfermodes and reports different filesizes accordingly.
912 // The BINARY size is the interesting one: How much memory
913 // will we need to hold this file?
914 TransferMode oldTransfermode = m_currentTransfermode;
915 SetTransferMode(BINARY);
916 command << _T("SIZE ") << fileName;
917
918 bool ok = CheckCommand(command, '2');
919
920 if ( ok )
921 {
922 // The answer should be one line: "213 <filesize>\n"
923 // 213 is File Status (STD9)
924 // "SIZE" is not described anywhere..? It works on most servers
925 int statuscode;
926 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
927 &statuscode, &filesize) == 2 )
928 {
929 // We've gotten a good reply.
930 ok = true;
931 }
932 else
933 {
934 // Something bad happened.. A "2yz" reply with no size
935 // Fallback
936 ok = false;
937 }
938 }
939
940 // Set transfermode back to the original. Only the "SIZE"-command
941 // is dependant on transfermode
942 if ( oldTransfermode != NONE )
943 {
944 SetTransferMode(oldTransfermode);
945 }
946
947 // this is not a direct else clause.. The size command might return an
948 // invalid "2yz" reply
949 if ( !ok )
950 {
951 // The server didn't understand the "SIZE"-command or it
952 // returned an invalid reply.
953 // We now try to get details for the file with a "LIST"-command
954 // and then parse the output from there..
955 wxArrayString fileList;
956 if ( GetList(fileList, fileName, true) )
957 {
958 if ( !fileList.IsEmpty() )
959 {
960 // We _should_ only get one line in return, but just to be
961 // safe we run through the line(s) returned and look for a
962 // substring containing the name we are looking for. We
963 // stop the iteration at the first occurrence of the
964 // filename. The search is not case-sensitive.
965 bool foundIt = false;
966
967 size_t i;
968 for ( i = 0; !foundIt && i < fileList.GetCount(); i++ )
969 {
970 foundIt = fileList[i].Upper().Contains(fileName.Upper());
971 }
972
973 if ( foundIt )
974 {
975 // The index i points to the first occurrence of
976 // fileName in the array Now we have to find out what
977 // format the LIST has returned. There are two
978 // "schools": Unix-like
979 //
980 // '-rw-rw-rw- owner group size month day time filename'
981 //
982 // or Windows-like
983 //
984 // 'date size filename'
985
986 // check if the first character is '-'. This would
987 // indicate Unix-style (this also limits this function
988 // to searching for files, not directories)
989 if ( fileList[i].Mid(0, 1) == _T("-") )
990 {
991
992 if ( wxSscanf(fileList[i].c_str(),
993 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
994 &filesize) != 9 )
995 {
996 // Hmm... Invalid response
997 wxLogDebug(wxT("Invalid LIST response"));
998 }
999 }
1000 else // Windows-style response (?)
1001 {
1002 if ( wxSscanf(fileList[i].c_str(),
1003 _T("%*s %*s %i %*s"),
1004 &filesize) != 4 )
1005 {
1006 // something bad happened..?
1007 wxLogDebug(wxT("Invalid or unknown LIST response"));
1008 }
1009 }
1010 }
1011 }
1012 }
1013 }
1014 }
1015
1016 // filesize might still be -1 when exiting
1017 return filesize;
1018 }
1019
1020 #endif // wxUSE_PROTOCOL_FTP
1021