]> git.saurik.com Git - wxWidgets.git/blob - src/common/ftp.cpp
77c72ece33508e609653d1d78aad6487f7434942
[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_lastError = wxPROTO_NOERR;
80 m_streaming = false;
81 m_currentTransfermode = NONE;
82
83 m_user = wxT("anonymous");
84 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
85
86 SetNotify(0);
87 SetFlags(wxSOCKET_NOWAIT);
88 m_bPassive = true;
89 SetDefaultTimeout(60); // Default is Sixty Seconds
90 m_bEncounteredError = false;
91 }
92
93 wxFTP::~wxFTP()
94 {
95 if ( m_streaming )
96 {
97 // if we are streaming, this will issue
98 // an FTP ABORT command, to tell the server we are aborting
99 (void)Abort();
100 }
101
102 // now this issues a "QUIT" command to tell the server we are
103 Close();
104 }
105
106 // ----------------------------------------------------------------------------
107 // wxFTP connect and login methods
108 // ----------------------------------------------------------------------------
109
110 bool wxFTP::Connect(const wxSockAddress& addr, bool WXUNUSED(wait))
111 {
112 if ( !wxProtocol::Connect(addr) )
113 {
114 m_lastError = wxPROTO_NETERR;
115 return false;
116 }
117
118 if ( !m_user )
119 {
120 m_lastError = wxPROTO_CONNERR;
121 return false;
122 }
123
124 // we should have 220 welcome message
125 if ( !CheckResult('2') )
126 {
127 Close();
128 return false;
129 }
130
131 wxString command;
132 command.Printf(wxT("USER %s"), m_user.c_str());
133 char rc = SendCommand(command);
134 if ( rc == '2' )
135 {
136 // 230 return: user accepted without password
137 return true;
138 }
139
140 if ( rc != '3' )
141 {
142 Close();
143 return false;
144 }
145
146 command.Printf(wxT("PASS %s"), m_passwd.c_str());
147 if ( !CheckCommand(command, '2') )
148 {
149 Close();
150 return false;
151 }
152
153 return true;
154 }
155
156 bool wxFTP::Connect(const wxString& host)
157 {
158 wxIPV4address addr;
159 addr.Hostname(host);
160 addr.Service(wxT("ftp"));
161
162 return Connect(addr);
163 }
164
165 bool wxFTP::Close()
166 {
167 if ( m_streaming )
168 {
169 m_lastError = wxPROTO_STREAMING;
170 return false;
171 }
172
173 if ( IsConnected() )
174 {
175 if ( !CheckCommand(wxT("QUIT"), '2') )
176 {
177 wxLogDebug(_T("Failed to close connection gracefully."));
178 }
179 }
180
181 return wxSocketClient::Close();
182 }
183
184 // ============================================================================
185 // low level methods
186 // ============================================================================
187
188 wxSocketBase *wxFTP::AcceptIfActive(wxSocketBase *sock)
189 {
190 if ( m_bPassive )
191 return sock;
192
193 // now wait for a connection from server
194 wxSocketServer *sockSrv = (wxSocketServer *)sock;
195 if ( !sockSrv->WaitForAccept() )
196 {
197 m_lastError = wxPROTO_CONNERR;
198 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
199 delete sock;
200 sock = NULL;
201 }
202 else
203 {
204 sock = sockSrv->Accept(true);
205 delete sockSrv;
206 }
207
208 return sock;
209 }
210
211 bool wxFTP::Abort()
212 {
213 if ( !m_streaming )
214 return true;
215
216 m_streaming = false;
217 if ( !CheckCommand(wxT("ABOR"), '4') )
218 return false;
219
220 return CheckResult('2');
221 }
222
223 void wxFTP::SetDefaultTimeout(wxUint32 Value)
224 {
225 m_uiDefaultTimeout = Value;
226 SetTimeout(Value); // sets it for this socket
227 }
228
229 // ----------------------------------------------------------------------------
230 // Send command to FTP server
231 // ----------------------------------------------------------------------------
232
233 char wxFTP::SendCommand(const wxString& command)
234 {
235 if ( m_streaming )
236 {
237 m_lastError = wxPROTO_STREAMING;
238 return 0;
239 }
240
241 wxString tmp_str = command + wxT("\r\n");
242 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
243 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
244 {
245 m_lastError = wxPROTO_NETERR;
246 return 0;
247 }
248
249 #ifdef __WXDEBUG__
250 // don't show the passwords in the logs (even in debug ones)
251 wxString cmd, password;
252 if ( command.Upper().StartsWith(_T("PASS "), &password) )
253 {
254 cmd << _T("PASS ") << wxString(_T('*'), password.length());
255 }
256 else
257 {
258 cmd = command;
259 }
260
261 wxLogTrace(FTP_TRACE_MASK, _T("==> %s"), cmd.c_str());
262 #endif // __WXDEBUG__
263
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
384 // if we got here we must have a non empty code string
385 return (char)code[0u];
386 }
387
388 // ----------------------------------------------------------------------------
389 // wxFTP simple commands
390 // ----------------------------------------------------------------------------
391
392 bool wxFTP::SetTransferMode(TransferMode transferMode)
393 {
394 if ( transferMode == m_currentTransfermode )
395 {
396 // nothing to do
397 return true;
398 }
399
400 wxString mode;
401 switch ( transferMode )
402 {
403 default:
404 wxFAIL_MSG(_T("unknown FTP transfer mode"));
405 // fall through
406
407 case BINARY:
408 mode = _T('I');
409 break;
410
411 case ASCII:
412 mode = _T('A');
413 break;
414 }
415
416 if ( !DoSimpleCommand(_T("TYPE"), mode) )
417 {
418 wxLogError(_("Failed to set FTP transfer mode to %s."),
419 (transferMode == ASCII ? _("ASCII") : _("binary")));
420
421 return false;
422 }
423
424 // If we get here the operation has been successfully completed
425 // Set the status-member
426 m_currentTransfermode = transferMode;
427
428 return true;
429 }
430
431 bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
432 {
433 wxString fullcmd = command;
434 if ( !arg.empty() )
435 {
436 fullcmd << _T(' ') << arg;
437 }
438
439 if ( !CheckCommand(fullcmd, '2') )
440 {
441 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
442
443 return false;
444 }
445
446 return true;
447 }
448
449 bool wxFTP::ChDir(const wxString& dir)
450 {
451 // some servers might not understand ".." if they use different directory
452 // tree conventions, but they always understand CDUP - should we use it if
453 // dir == ".."? OTOH, do such servers (still) exist?
454
455 return DoSimpleCommand(_T("CWD"), dir);
456 }
457
458 bool wxFTP::MkDir(const wxString& dir)
459 {
460 return DoSimpleCommand(_T("MKD"), dir);
461 }
462
463 bool wxFTP::RmDir(const wxString& dir)
464 {
465 return DoSimpleCommand(_T("RMD"), dir);
466 }
467
468 wxString wxFTP::Pwd()
469 {
470 wxString path;
471
472 if ( CheckCommand(wxT("PWD"), '2') )
473 {
474 // the result is at least that long if CheckCommand() succeeded
475 wxString::const_iterator p = m_lastResult.begin() + LEN_CODE + 1;
476 if ( *p != _T('"') )
477 {
478 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
479 wxString(p, m_lastResult.end()));
480 }
481 else
482 {
483 for ( ++p; (bool)*p; ++p ) // FIXME-DMARS
484 {
485 if ( *p == _T('"') )
486 {
487 // check if the quote is doubled
488 ++p;
489 if ( !*p || *p != _T('"') )
490 {
491 // no, this is the end
492 break;
493 }
494 //else: yes, it is: this is an embedded quote in the
495 // filename, treat as normal char
496 }
497
498 path += *p;
499 }
500
501 if ( !*p )
502 {
503 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
504 m_lastResult.c_str() + LEN_CODE + 1);
505 }
506 }
507 }
508 else
509 {
510 wxLogDebug(_T("FTP PWD command failed."));
511 }
512
513 return path;
514 }
515
516 bool wxFTP::Rename(const wxString& src, const wxString& dst)
517 {
518 wxString str;
519
520 str = wxT("RNFR ") + src;
521 if ( !CheckCommand(str, '3') )
522 return false;
523
524 str = wxT("RNTO ") + dst;
525
526 return CheckCommand(str, '2');
527 }
528
529 bool wxFTP::RmFile(const wxString& path)
530 {
531 wxString str;
532 str = wxT("DELE ") + path;
533
534 return CheckCommand(str, '2');
535 }
536
537 // ----------------------------------------------------------------------------
538 // wxFTP port methods
539 // ----------------------------------------------------------------------------
540
541 wxSocketBase *wxFTP::GetPort()
542 {
543 /*
544 PASSIVE: Client sends a "PASV" to the server. The server responds with
545 an address and port number which it will be listening on. Then
546 the client connects to the server at the specified address and
547 port.
548
549 ACTIVE: Client sends the server a PORT command which includes an
550 address and port number which the client will be listening on.
551 The server then connects to the client at that address and
552 port.
553 */
554
555 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
556 if ( !socket )
557 {
558 m_bEncounteredError = true;
559 return NULL;
560 }
561
562 // Now set the time for the new socket to the default or user selected
563 // timeout period
564 socket->SetTimeout(m_uiDefaultTimeout);
565
566 return socket;
567 }
568
569 wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
570 const wxIPV4address& addrNew)
571 {
572 // Just fills in the return value with the local IP
573 // address of the current socket. Also it fill in the
574 // PORT which the client will be listening on
575
576 wxString addrIP = addrLocal.IPAddress();
577 int portNew = addrNew.Service();
578
579 // We need to break the PORT number in bytes
580 addrIP.Replace(_T("."), _T(","));
581 addrIP << _T(',')
582 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
583 << wxString::Format(_T("%d"), portNew & 0xff);
584
585 // Now we have a value like "10,0,0,1,5,23"
586 return addrIP;
587 }
588
589 wxSocketBase *wxFTP::GetActivePort()
590 {
591 // we need an address to listen on
592 wxIPV4address addrNew, addrLocal;
593 GetLocal(addrLocal);
594 addrNew.AnyAddress();
595 addrNew.Service(0); // pick an open port number.
596
597 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
598 if (!sockSrv->Ok())
599 {
600 // We use Ok() here to see if everything is ok
601 m_lastError = wxPROTO_PROTERR;
602 delete sockSrv;
603 return NULL;
604 }
605
606 //gets the new address, actually it is just the port number
607 sockSrv->GetLocal(addrNew);
608
609 // Now we create the argument of the PORT command, we send in both
610 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
611 // value in addrLocal
612 wxString port = GetPortCmdArgument(addrLocal, addrNew);
613 if ( !DoSimpleCommand(_T("PORT"), port) )
614 {
615 m_lastError = wxPROTO_PROTERR;
616 delete sockSrv;
617 wxLogError(_("The FTP server doesn't support the PORT command."));
618 return NULL;
619 }
620
621 sockSrv->Notify(false); // Don't send any events
622 return sockSrv;
623 }
624
625 wxSocketBase *wxFTP::GetPassivePort()
626 {
627 if ( !DoSimpleCommand(_T("PASV")) )
628 {
629 wxLogError(_("The FTP server doesn't support passive mode."));
630 return NULL;
631 }
632
633 size_t addrStart = m_lastResult.find(_T('('));
634 size_t addrEnd = (addrStart == wxString::npos)
635 ? wxString::npos
636 : m_lastResult.find(_T(')'), addrStart);
637
638 if ( addrEnd == wxString::npos )
639 {
640 m_lastError = wxPROTO_PROTERR;
641 return NULL;
642 }
643
644 // get the port number and address
645 int a[6];
646 wxString straddr(m_lastResult, addrStart + 1, addrEnd - (addrStart + 1));
647 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
648 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
649
650 wxUint32 hostaddr = (wxUint16)a[2] << 24 |
651 (wxUint16)a[3] << 16 |
652 (wxUint16)a[4] << 8 |
653 a[5];
654 wxUint16 port = (wxUint16)(a[0] << 8 | a[1]);
655
656 wxIPV4address addr;
657 addr.Hostname(hostaddr);
658 addr.Service(port);
659
660 wxSocketClient *client = new wxSocketClient();
661 if ( !client->Connect(addr) )
662 {
663 delete client;
664 return NULL;
665 }
666
667 client->Notify(false);
668
669 return client;
670 }
671
672
673 // ----------------------------------------------------------------------------
674 // wxFTP download and upload
675 // ----------------------------------------------------------------------------
676
677 class wxInputFTPStream : public wxSocketInputStream
678 {
679 public:
680 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
681 : wxSocketInputStream(*sock)
682 {
683 m_ftp = ftp;
684 // socket timeout automatically set in GetPort function
685 }
686
687 virtual ~wxInputFTPStream()
688 {
689 delete m_i_socket; // keep at top
690
691 // when checking the result, the stream will
692 // almost always show an error, even if the file was
693 // properly transfered, thus, lets just grab the result
694
695 // we are looking for "226 transfer completed"
696 char code = m_ftp->GetResult();
697 if ('2' == code)
698 {
699 // it was a good transfer.
700 // we're done!
701 m_ftp->m_streaming = false;
702 return;
703 }
704 // did we timeout?
705 if (0 == code)
706 {
707 // the connection is probably toast. issue an abort, and
708 // then a close. there won't be any more waiting
709 // for this connection
710 m_ftp->Abort();
711 m_ftp->Close();
712 return;
713 }
714 // There was a problem with the transfer and the server
715 // has acknowledged it. If we issue an "ABORT" now, the user
716 // would get the "226" for the abort and think the xfer was
717 // complete, thus, don't do anything here, just return
718 }
719
720 wxFTP *m_ftp;
721
722 DECLARE_NO_COPY_CLASS(wxInputFTPStream)
723 };
724
725 class wxOutputFTPStream : public wxSocketOutputStream
726 {
727 public:
728 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
729 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
730 {
731 }
732
733 virtual ~wxOutputFTPStream(void)
734 {
735 if ( IsOk() )
736 {
737 // close data connection first, this will generate "transfer
738 // completed" reply
739 delete m_o_socket;
740
741 // read this reply
742 m_ftp->GetResult(); // save result so user can get to it
743
744 m_ftp->m_streaming = false;
745 }
746 else
747 {
748 // abort data connection first
749 m_ftp->Abort();
750
751 // and close it after
752 delete m_o_socket;
753 }
754 }
755
756 wxFTP *m_ftp;
757
758 DECLARE_NO_COPY_CLASS(wxOutputFTPStream)
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 nonexistent 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.GetCount(); 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