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