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