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