Use the associated document manager, not the global one
[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 const wxChar *p = m_lastResult.c_str() + LEN_CODE + 1;
438 if ( *p != _T('"') )
439 {
440 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p);
441 }
442 else
443 {
444 for ( p++; *p; p++ )
445 {
446 if ( *p == _T('"') )
447 {
448 // check if the quote is doubled
449 p++;
450 if ( !*p || *p != _T('"') )
451 {
452 // no, this is the end
453 break;
454 }
455 //else: yes, it is: this is an embedded quote in the
456 // filename, treat as normal char
457 }
458
459 path += *p;
460 }
461
462 if ( !*p )
463 {
464 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
465 m_lastResult.c_str() + LEN_CODE + 1);
466 }
467 }
468 }
469 else
470 {
471 wxLogDebug(_T("FTP PWD command failed."));
472 }
473
474 return path;
475 }
476
477 bool wxFTP::Rename(const wxString& src, const wxString& dst)
478 {
479 wxString str;
480
481 str = wxT("RNFR ") + src;
482 if ( !CheckCommand(str, '3') )
483 return false;
484
485 str = wxT("RNTO ") + dst;
486
487 return CheckCommand(str, '2');
488 }
489
490 bool wxFTP::RmFile(const wxString& path)
491 {
492 wxString str;
493 str = wxT("DELE ") + path;
494
495 return CheckCommand(str, '2');
496 }
497
498 // ----------------------------------------------------------------------------
499 // wxFTP download and upload
500 // ----------------------------------------------------------------------------
501
502 class wxInputFTPStream : public wxSocketInputStream
503 {
504 public:
505 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
506 : wxSocketInputStream(*sock)
507 {
508 m_ftp = ftp;
509 // socket timeout automatically set in GetPort function
510 }
511
512 virtual ~wxInputFTPStream()
513 {
514 delete m_i_socket; // keep at top
515
516 // when checking the result, the stream will
517 // almost always show an error, even if the file was
518 // properly transfered, thus, lets just grab the result
519
520 // we are looking for "226 transfer completed"
521 char code = m_ftp->GetResult();
522 if ('2' == code)
523 {
524 // it was a good transfer.
525 // we're done!
526 m_ftp->m_streaming = false;
527 return;
528 }
529 // did we timeout?
530 if (0 == code)
531 {
532 // the connection is probably toast. issue an abort, and
533 // then a close. there won't be any more waiting
534 // for this connection
535 m_ftp->Abort();
536 m_ftp->Close();
537 return;
538 }
539 // There was a problem with the transfer and the server
540 // has acknowledged it. If we issue an "ABORT" now, the user
541 // would get the "226" for the abort and think the xfer was
542 // complete, thus, don't do anything here, just return
543 }
544
545 wxFTP *m_ftp;
546
547 DECLARE_NO_COPY_CLASS(wxInputFTPStream)
548 };
549
550 class wxOutputFTPStream : public wxSocketOutputStream
551 {
552 public:
553 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
554 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
555 {
556 }
557
558 virtual ~wxOutputFTPStream(void)
559 {
560 if ( IsOk() )
561 {
562 // close data connection first, this will generate "transfer
563 // completed" reply
564 delete m_o_socket;
565
566 // read this reply
567 m_ftp->GetResult(); // save result so user can get to it
568
569 m_ftp->m_streaming = false;
570 }
571 else
572 {
573 // abort data connection first
574 m_ftp->Abort();
575
576 // and close it after
577 delete m_o_socket;
578 }
579 }
580
581 wxFTP *m_ftp;
582
583 DECLARE_NO_COPY_CLASS(wxOutputFTPStream)
584 };
585
586 void wxFTP::SetDefaultTimeout(wxUint32 Value)
587 {
588 m_uiDefaultTimeout = Value;
589 SetTimeout(Value); // sets it for this socket
590 }
591
592
593 wxSocketBase *wxFTP::GetPort()
594 {
595 /*
596 PASSIVE: Client sends a "PASV" to the server. The server responds with
597 an address and port number which it will be listening on. Then
598 the client connects to the server at the specified address and
599 port.
600
601 ACTIVE: Client sends the server a PORT command which includes an
602 address and port number which the client will be listening on.
603 The server then connects to the client at that address and
604 port.
605 */
606
607 wxSocketBase *socket = m_bPassive ? GetPassivePort() : GetActivePort();
608 if ( !socket )
609 {
610 m_bEncounteredError = true;
611 return NULL;
612 }
613
614 // Now set the time for the new socket to the default or user selected
615 // timeout period
616 socket->SetTimeout(m_uiDefaultTimeout);
617
618 return socket;
619 }
620
621 wxSocketBase *wxFTP::AcceptIfActive(wxSocketBase *sock)
622 {
623 if ( m_bPassive )
624 return sock;
625
626 // now wait for a connection from server
627 wxSocketServer *sockSrv = (wxSocketServer *)sock;
628 if ( !sockSrv->WaitForAccept() )
629 {
630 m_lastError = wxPROTO_CONNERR;
631 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
632 delete sock;
633 sock = NULL;
634 }
635 else
636 {
637 sock = sockSrv->Accept(true);
638 delete sockSrv;
639 }
640
641 return sock;
642 }
643
644 wxString wxFTP::GetPortCmdArgument(const wxIPV4address& addrLocal,
645 const wxIPV4address& addrNew)
646 {
647 // Just fills in the return value with the local IP
648 // address of the current socket. Also it fill in the
649 // PORT which the client will be listening on
650
651 wxString addrIP = addrLocal.IPAddress();
652 int portNew = addrNew.Service();
653
654 // We need to break the PORT number in bytes
655 addrIP.Replace(_T("."), _T(","));
656 addrIP << _T(',')
657 << wxString::Format(_T("%d"), portNew >> 8) << _T(',')
658 << wxString::Format(_T("%d"), portNew & 0xff);
659
660 // Now we have a value like "10,0,0,1,5,23"
661 return addrIP;
662 }
663
664 wxSocketBase *wxFTP::GetActivePort()
665 {
666 // we need an address to listen on
667 wxIPV4address addrNew, addrLocal;
668 GetLocal(addrLocal);
669 addrNew.AnyAddress();
670 addrNew.Service(0); // pick an open port number.
671
672 wxSocketServer *sockSrv = new wxSocketServer(addrNew);
673 if (!sockSrv->Ok())
674 {
675 // We use Ok() here to see if everything is ok
676 m_lastError = wxPROTO_PROTERR;
677 delete sockSrv;
678 return NULL;
679 }
680
681 //gets the new address, actually it is just the port number
682 sockSrv->GetLocal(addrNew);
683
684 // Now we create the argument of the PORT command, we send in both
685 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
686 // value in addrLocal
687 wxString port = GetPortCmdArgument(addrLocal, addrNew);
688 if ( !DoSimpleCommand(_T("PORT "), port) )
689 {
690 m_lastError = wxPROTO_PROTERR;
691 delete sockSrv;
692 wxLogError(_("The FTP server doesn't support the PORT command."));
693 return NULL;
694 }
695
696 sockSrv->Notify(false); // Don't send any events
697 return sockSrv;
698 }
699
700 wxSocketBase *wxFTP::GetPassivePort()
701 {
702 if ( !DoSimpleCommand(_T("PASV")) )
703 {
704 wxLogError(_("The FTP server doesn't support passive mode."));
705 return NULL;
706 }
707
708 const wxChar *addrStart = wxStrchr(m_lastResult, _T('('));
709 const wxChar *addrEnd = addrStart ? wxStrchr(addrStart, _T(')')) : NULL;
710 if ( !addrEnd )
711 {
712 m_lastError = wxPROTO_PROTERR;
713
714 return NULL;
715 }
716
717 // get the port number and address
718 int a[6];
719 wxString straddr(addrStart + 1, addrEnd);
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.Count(); 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