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