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