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