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