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