]> git.saurik.com Git - wxWidgets.git/blame - src/common/ftp.cpp
Try this again
[wxWidgets.git] / src / common / ftp.cpp
CommitLineData
f4ada568
GL
1/////////////////////////////////////////////////////////////////////////////
2// Name: ftp.cpp
3// Purpose: FTP protocol
4// Author: Guilhem Lavaux
2e907fab
VZ
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, ...)
f4ada568
GL
10// Created: 07/07/1997
11// RCS-ID: $Id$
12// Copyright: (c) 1997, 1998 Guilhem Lavaux
13// Licence: wxWindows license
14/////////////////////////////////////////////////////////////////////////////
15
2e907fab
VZ
16// ============================================================================
17// declarations
18// ============================================================================
19
f4ada568 20#ifdef __GNUG__
2df7be7f 21 #pragma implementation "ftp.h"
f4ada568 22#endif
ec45f8ee 23
2e907fab
VZ
24// ----------------------------------------------------------------------------
25// headers
26// ----------------------------------------------------------------------------
27
ec45f8ee
UU
28// For compilers that support precompilation, includes "wx.h".
29#include "wx/wxprec.h"
30
19ae5cf0 31#ifdef __BORLANDC__
2df7be7f 32 #pragma hdrstop
19ae5cf0
UU
33#endif
34
a5d46b73 35#if wxUSE_PROTOCOL_FTP
35a4dab7 36
2e907fab
VZ
37#ifndef WX_PRECOMP
38 #include <stdlib.h>
39 #include "wx/string.h"
40 #include "wx/utils.h"
41 #include "wx/log.h"
42 #include "wx/intl.h"
43#endif // WX_PRECOMP
17dff81c 44
f4ada568 45#include "wx/sckaddr.h"
f4ada568
GL
46#include "wx/socket.h"
47#include "wx/url.h"
48#include "wx/sckstrm.h"
49#include "wx/protocol/protocol.h"
50#include "wx/protocol/ftp.h"
51
2e907fab 52#if defined(__WXMAC__)
03e11df5 53 #include "wx/mac/macsock.h"
2e907fab
VZ
54#endif
55
56#ifndef __MWERKS__
57 #include <memory.h>
f4ada568
GL
58#endif
59
2e907fab
VZ
60// ----------------------------------------------------------------------------
61// constants
62// ----------------------------------------------------------------------------
63
8e907a13
VZ
64// the length of FTP status code (3 digits)
65static const size_t LEN_CODE = 3;
66
2e907fab
VZ
67// ----------------------------------------------------------------------------
68// macros
69// ----------------------------------------------------------------------------
f4ada568 70
f4ada568 71IMPLEMENT_DYNAMIC_CLASS(wxFTP, wxProtocol)
223d09f6 72IMPLEMENT_PROTOCOL(wxFTP, wxT("ftp"), wxT("ftp"), TRUE)
f4ada568 73
2e907fab
VZ
74// ============================================================================
75// implementation
76// ============================================================================
77
78// ----------------------------------------------------------------------------
79// wxFTP constructor and destructor
80// ----------------------------------------------------------------------------
f4ada568
GL
81
82wxFTP::wxFTP()
f4ada568 83{
8e907a13
VZ
84 m_lastError = wxPROTO_NOERR;
85 m_streaming = FALSE;
3f2bcf34 86 m_currentTransfermode = NONE;
f4ada568 87
8e907a13
VZ
88 m_user = wxT("anonymous");
89 m_passwd << wxGetUserId() << wxT('@') << wxGetFullHostName();
f4ada568 90
8e907a13
VZ
91 SetNotify(0);
92 SetFlags(wxSOCKET_NONE);
f4ada568
GL
93}
94
95wxFTP::~wxFTP()
96{
7ff26ec2
VZ
97 if ( m_streaming )
98 {
99 (void)Abort();
100 }
101
8e907a13 102 Close();
f4ada568
GL
103}
104
2e907fab
VZ
105// ----------------------------------------------------------------------------
106// wxFTP connect and login methods
107// ----------------------------------------------------------------------------
108
8a2c6ef8 109bool wxFTP::Connect(wxSockAddress& addr, bool WXUNUSED(wait))
f4ada568 110{
2e907fab
VZ
111 if ( !wxProtocol::Connect(addr) )
112 {
113 m_lastError = wxPROTO_NETERR;
114 return FALSE;
115 }
f4ada568 116
2e907fab
VZ
117 if ( !m_user )
118 {
119 m_lastError = wxPROTO_CONNERR;
120 return FALSE;
121 }
f4ada568 122
2e907fab
VZ
123 // we should have 220 welcome message
124 if ( !CheckResult('2') )
125 {
126 Close();
127 return FALSE;
128 }
f4ada568 129
2e907fab
VZ
130 wxString command;
131 command.Printf(wxT("USER %s"), m_user.c_str());
132 char rc = SendCommand(command);
133 if ( rc == '2' )
134 {
135 // 230 return: user accepted without password
136 return TRUE;
137 }
f4ada568 138
2e907fab
VZ
139 if ( rc != '3' )
140 {
141 Close();
142 return FALSE;
143 }
f4ada568 144
2e907fab
VZ
145 command.Printf(wxT("PASS %s"), m_passwd.c_str());
146 if ( !CheckCommand(command, '2') )
147 {
148 Close();
149 return FALSE;
150 }
f4ada568 151
2e907fab 152 return TRUE;
f4ada568
GL
153}
154
155bool wxFTP::Connect(const wxString& host)
156{
2e907fab
VZ
157 wxIPV4address addr;
158 addr.Hostname(host);
159 addr.Service(wxT("ftp"));
f4ada568 160
2e907fab 161 return Connect(addr);
f4ada568
GL
162}
163
7ff26ec2 164bool wxFTP::Close()
f4ada568 165{
8e907a13
VZ
166 if ( m_streaming )
167 {
7ff26ec2
VZ
168 m_lastError = wxPROTO_STREAMING;
169 return FALSE;
8e907a13
VZ
170 }
171
172 if ( IsConnected() )
2e907fab
VZ
173 {
174 if ( !CheckCommand(wxT("QUIT"), '2') )
175 {
176 wxLogDebug(_T("Failed to close connection gracefully."));
177 }
178 }
e8773bdf 179
8e907a13 180 return wxSocketClient::Close();
f4ada568
GL
181}
182
8e907a13
VZ
183// ============================================================================
184// low level methods
185// ============================================================================
186
187// ----------------------------------------------------------------------------
188// Send command to FTP server
189// ----------------------------------------------------------------------------
190
2e907fab 191char wxFTP::SendCommand(const wxString& command)
f4ada568 192{
2e907fab 193 if ( m_streaming )
8e907a13
VZ
194 {
195 m_lastError = wxPROTO_STREAMING;
2e907fab 196 return 0;
8e907a13
VZ
197 }
198
2e907fab 199 wxString tmp_str = command + wxT("\r\n");
8e907a13
VZ
200 const wxWX2MBbuf tmp_buf = tmp_str.mb_str();
201 if ( Write(wxMBSTRINGCAST tmp_buf, strlen(tmp_buf)).Error())
202 {
203 m_lastError = wxPROTO_NETERR;
2e907fab 204 return 0;
8e907a13
VZ
205 }
206
b92fd37c
VZ
207#ifdef __WXDEBUG__
208 // don't show the passwords in the logs (even in debug ones)
209 wxString cmd, password;
210 if ( command.Upper().StartsWith(_T("PASS "), &password) )
211 {
212 cmd << _T("PASS ") << wxString(_T('*'), password.length());
213 }
214 else
215 {
216 cmd = command;
217 }
218
219 wxLogTrace(FTP_TRACE_MASK, _T("==> %s"), cmd.c_str());
220#endif // __WXDEBUG__
8e907a13 221
2e907fab 222 return GetResult();
f4ada568
GL
223}
224
8e907a13
VZ
225// ----------------------------------------------------------------------------
226// Recieve servers reply
227// ----------------------------------------------------------------------------
228
2e907fab 229char wxFTP::GetResult()
f4ada568 230{
8e907a13
VZ
231 wxString code;
232
2e907fab
VZ
233 // m_lastResult will contain the entire server response, possibly on
234 // multiple lines
235 m_lastResult.clear();
236
8e907a13
VZ
237 // we handle multiline replies here according to RFC 959: it says that a
238 // reply may either be on 1 line of the form "xyz ..." or on several lines
239 // in whuch case it looks like
240 // xyz-...
241 // ...
242 // xyz ...
243 // and the intermeidate lines may start with xyz or not
244 bool badReply = FALSE;
245 bool firstLine = TRUE;
246 bool endOfReply = FALSE;
247 while ( !endOfReply && !badReply )
248 {
2e907fab
VZ
249 wxString line;
250 m_lastError = ReadLine(line);
8e907a13 251 if ( m_lastError )
2e907fab
VZ
252 return 0;
253
254 if ( !m_lastResult.empty() )
255 {
256 // separate from last line
257 m_lastResult += _T('\n');
258 }
259
260 m_lastResult += line;
8e907a13
VZ
261
262 // unless this is an intermediate line of a multiline reply, it must
263 // contain the code in the beginning and '-' or ' ' following it
2e907fab 264 if ( line.Len() < LEN_CODE + 1 )
8e907a13
VZ
265 {
266 if ( firstLine )
267 {
268 badReply = TRUE;
269 }
270 else
271 {
b92fd37c 272 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 273 code.c_str(), line.c_str());
8e907a13
VZ
274 }
275 }
276 else // line has at least 4 chars
277 {
278 // this is the char which tells us what we're dealing with
2e907fab 279 wxChar chMarker = line.GetChar(LEN_CODE);
8e907a13
VZ
280
281 if ( firstLine )
282 {
2e907fab 283 code = wxString(line, LEN_CODE);
b92fd37c 284 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 285 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
286
287 switch ( chMarker )
288 {
289 case _T(' '):
290 endOfReply = TRUE;
291 break;
292
293 case _T('-'):
294 firstLine = FALSE;
295 break;
296
297 default:
298 // unexpected
299 badReply = TRUE;
300 }
301 }
302 else // subsequent line of multiline reply
303 {
2e907fab 304 if ( wxStrncmp(line, code, LEN_CODE) == 0 )
8e907a13
VZ
305 {
306 if ( chMarker == _T(' ') )
307 {
308 endOfReply = TRUE;
309 }
310
b92fd37c 311 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 312 code.c_str(), line.c_str() + LEN_CODE + 1);
8e907a13
VZ
313 }
314 else
315 {
316 // just part of reply
b92fd37c 317 wxLogTrace(FTP_TRACE_MASK, _T("<== %s %s"),
2e907fab 318 code.c_str(), line.c_str());
8e907a13
VZ
319 }
320 }
321 }
322 }
f4ada568 323
8e907a13
VZ
324 if ( badReply )
325 {
326 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
327 m_lastResult.c_str());
f4ada568 328
8e907a13 329 m_lastError = wxPROTO_PROTERR;
f4ada568 330
2e907fab
VZ
331 return 0;
332 }
333
334 // if we got here we must have a non empty code string
335 return code[0u];
336}
337
338// ----------------------------------------------------------------------------
339// wxFTP simple commands
340// ----------------------------------------------------------------------------
341
342bool wxFTP::SetTransferMode(TransferMode transferMode)
343{
b92fd37c
VZ
344 if ( transferMode == m_currentTransfermode )
345 {
346 // nothing to do
347 return TRUE;
348 }
349
2e907fab
VZ
350 wxString mode;
351 switch ( transferMode )
352 {
353 default:
354 wxFAIL_MSG(_T("unknown FTP transfer mode"));
355 // fall through
356
357 case BINARY:
358 mode = _T('I');
359 break;
360
361 case ASCII:
362 mode = _T('A');
363 break;
364 }
365
366 if ( !DoSimpleCommand(_T("TYPE"), mode) )
367 {
86435b1a
OK
368 wxLogError(_("Failed to set FTP transfer mode to %s."), (const wxChar*)
369 (transferMode == ASCII ? _("ASCII") : _("binary")));
2e907fab 370
f4ada568
GL
371 return FALSE;
372 }
8e907a13 373
3f2bcf34
VZ
374 // If we get here the operation has been succesfully completed
375 // Set the status-member
376 m_currentTransfermode = transferMode;
2e907fab
VZ
377
378 return TRUE;
379}
380
381bool wxFTP::DoSimpleCommand(const wxChar *command, const wxString& arg)
382{
383 wxString fullcmd = command;
384 if ( !arg.empty() )
8e907a13 385 {
2e907fab
VZ
386 fullcmd << _T(' ') << arg;
387 }
388
389 if ( !CheckCommand(fullcmd, '2') )
390 {
391 wxLogDebug(_T("FTP command '%s' failed."), fullcmd.c_str());
8e907a13
VZ
392
393 return FALSE;
394 }
395
396 return TRUE;
f4ada568
GL
397}
398
f4ada568
GL
399bool wxFTP::ChDir(const wxString& dir)
400{
2e907fab
VZ
401 // some servers might not understand ".." if they use different directory
402 // tree conventions, but they always understand CDUP - should we use it if
403 // dir == ".."? OTOH, do such servers (still) exist?
f4ada568 404
2e907fab 405 return DoSimpleCommand(_T("CWD"), dir);
f4ada568
GL
406}
407
408bool wxFTP::MkDir(const wxString& dir)
409{
2e907fab 410 return DoSimpleCommand(_T("MKD"), dir);
f4ada568
GL
411}
412
413bool wxFTP::RmDir(const wxString& dir)
414{
2e907fab 415 return DoSimpleCommand(_T("RMD"), dir);
f4ada568
GL
416}
417
418wxString wxFTP::Pwd()
419{
8e907a13
VZ
420 wxString path;
421
2e907fab 422 if ( CheckCommand(wxT("PWD"), '2') )
8e907a13 423 {
2e907fab 424 // the result is at least that long if CheckCommand() succeeded
8e907a13
VZ
425 const wxChar *p = m_lastResult.c_str() + LEN_CODE + 1;
426 if ( *p != _T('"') )
427 {
428 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p);
429 }
430 else
431 {
432 for ( p++; *p; p++ )
433 {
434 if ( *p == _T('"') )
435 {
436 // check if the quote is doubled
437 p++;
438 if ( !*p || *p != _T('"') )
439 {
440 // no, this is the end
441 break;
442 }
443 //else: yes, it is: this is an embedded quote in the
444 // filename, treat as normal char
445 }
446
447 path += *p;
448 }
449
450 if ( !*p )
451 {
452 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
453 m_lastResult.c_str() + LEN_CODE + 1);
454 }
455 }
456 }
2e907fab
VZ
457 else
458 {
459 wxLogDebug(_T("FTP PWD command failed."));
460 }
f4ada568 461
8e907a13 462 return path;
f4ada568
GL
463}
464
465bool wxFTP::Rename(const wxString& src, const wxString& dst)
466{
2e907fab
VZ
467 wxString str;
468
469 str = wxT("RNFR ") + src;
470 if ( !CheckCommand(str, '3') )
471 return FALSE;
f4ada568 472
2e907fab 473 str = wxT("RNTO ") + dst;
f4ada568 474
2e907fab 475 return CheckCommand(str, '2');
f4ada568
GL
476}
477
478bool wxFTP::RmFile(const wxString& path)
479{
2e907fab
VZ
480 wxString str;
481 str = wxT("DELE ") + path;
f4ada568 482
2e907fab 483 return CheckCommand(str, '2');
f4ada568
GL
484}
485
2e907fab
VZ
486// ----------------------------------------------------------------------------
487// wxFTP download and upload
488// ----------------------------------------------------------------------------
f4ada568 489
2e907fab
VZ
490class wxInputFTPStream : public wxSocketInputStream
491{
f4ada568 492public:
445783de
VZ
493 wxInputFTPStream(wxFTP *ftp, wxSocketBase *sock)
494 : wxSocketInputStream(*sock)
2e907fab 495 {
445783de
VZ
496 m_ftp = ftp;
497
498 // FIXME make the timeout configurable
499
500 // set a shorter than default timeout
501 m_i_socket->SetTimeout(60); // 1 minute
2e907fab
VZ
502 }
503
504 size_t GetSize() const { return m_ftpsize; }
505
506 virtual ~wxInputFTPStream()
507 {
b0ad2006
VZ
508 delete m_i_socket;
509
2b5f62a0 510 if ( IsOk() )
2e907fab
VZ
511 {
512 // wait for "226 transfer completed"
513 m_ftp->CheckResult('2');
514
515 m_ftp->m_streaming = FALSE;
516 }
517 else
518 {
519 m_ftp->Abort();
520 }
b92fd37c
VZ
521
522 // delete m_i_socket; // moved to top of destructor to accomodate wu-FTPd >= 2.6.0
2e907fab
VZ
523 }
524
525 wxFTP *m_ftp;
526 size_t m_ftpsize;
22f3361e
VZ
527
528 DECLARE_NO_COPY_CLASS(wxInputFTPStream)
f4ada568
GL
529};
530
2e907fab
VZ
531class wxOutputFTPStream : public wxSocketOutputStream
532{
f4ada568 533public:
2e907fab
VZ
534 wxOutputFTPStream(wxFTP *ftp_clt, wxSocketBase *sock)
535 : wxSocketOutputStream(*sock), m_ftp(ftp_clt)
536 {
537 }
538
539 virtual ~wxOutputFTPStream(void)
540 {
541 if ( IsOk() )
542 {
543 // close data connection first, this will generate "transfer
544 // completed" reply
545 delete m_o_socket;
546
547 // read this reply
548 m_ftp->CheckResult('2');
549
550 m_ftp->m_streaming = FALSE;
551 }
552 else
553 {
554 // abort data connection first
555 m_ftp->Abort();
556
557 // and close it after
558 delete m_o_socket;
559 }
560 }
561
562 wxFTP *m_ftp;
22f3361e
VZ
563
564 DECLARE_NO_COPY_CLASS(wxOutputFTPStream)
f4ada568
GL
565};
566
567wxSocketClient *wxFTP::GetPort()
568{
2e907fab
VZ
569 int a[6];
570
571 if ( !DoSimpleCommand(_T("PASV")) )
572 {
573 wxLogError(_("The FTP server doesn't support passive mode."));
574
575 return NULL;
576 }
577
86435b1a 578 const wxChar *addrStart = wxStrchr(m_lastResult, _T('('));
2e907fab
VZ
579 if ( !addrStart )
580 {
581 m_lastError = wxPROTO_PROTERR;
582
583 return NULL;
584 }
585
86435b1a 586 const wxChar *addrEnd = wxStrchr(addrStart, _T(')'));
2e907fab
VZ
587 if ( !addrEnd )
588 {
589 m_lastError = wxPROTO_PROTERR;
590
591 return NULL;
592 }
593
594 wxString straddr(addrStart + 1, addrEnd);
595
596 wxSscanf(straddr, wxT("%d,%d,%d,%d,%d,%d"),
597 &a[2],&a[3],&a[4],&a[5],&a[0],&a[1]);
598
599 wxUint32 hostaddr = (wxUint16)a[5] << 24 |
600 (wxUint16)a[4] << 16 |
601 (wxUint16)a[3] << 8 |
602 a[2];
603 wxUint16 port = (wxUint16)a[0] << 8 | a[1];
604
605 wxIPV4address addr;
606 addr.Hostname(hostaddr);
607 addr.Service(port);
608
609 wxSocketClient *client = new wxSocketClient();
610 if ( !client->Connect(addr) )
611 {
612 delete client;
613 return NULL;
614 }
615
616 client->Notify(FALSE);
617
618 return client;
f4ada568
GL
619}
620
8e907a13 621bool wxFTP::Abort()
f4ada568 622{
8e907a13
VZ
623 if ( !m_streaming )
624 return TRUE;
2e907fab 625
8e907a13 626 m_streaming = FALSE;
2e907fab 627 if ( !CheckCommand(wxT("ABOR"), '4') )
8e907a13
VZ
628 return FALSE;
629
2e907fab 630 return CheckResult('2');
f4ada568
GL
631}
632
633wxInputStream *wxFTP::GetInputStream(const wxString& path)
634{
2e907fab
VZ
635 int pos_size;
636 wxInputFTPStream *in_stream;
637
3f2bcf34 638 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
2e907fab
VZ
639 return NULL;
640
641 wxSocketClient *sock = GetPort();
f4ada568 642
2e907fab
VZ
643 if ( !sock )
644 {
645 m_lastError = wxPROTO_NETERR;
646 return NULL;
647 }
f4ada568 648
2e907fab
VZ
649 wxString tmp_str = wxT("RETR ") + wxURL::ConvertFromURI(path);
650 if ( !CheckCommand(tmp_str, '1') )
651 return NULL;
f4ada568 652
2e907fab 653 m_streaming = TRUE;
f4ada568 654
2e907fab 655 in_stream = new wxInputFTPStream(this, sock);
f4ada568 656
2e907fab
VZ
657 pos_size = m_lastResult.Index(wxT('('));
658 if ( pos_size != wxNOT_FOUND )
659 {
660 wxString str_size = m_lastResult(pos_size+1, m_lastResult.Index(wxT(')'))-1);
9a1b2c28 661
2e907fab
VZ
662 in_stream->m_ftpsize = wxAtoi(WXSTRINGCAST str_size);
663 }
9a1b2c28 664
2e907fab 665 sock->SetFlags(wxSOCKET_WAITALL);
9a1b2c28 666
2e907fab 667 return in_stream;
f4ada568
GL
668}
669
670wxOutputStream *wxFTP::GetOutputStream(const wxString& path)
671{
3f2bcf34 672 if ( ( m_currentTransfermode == NONE ) && !SetTransferMode(BINARY) )
2e907fab 673 return NULL;
f4ada568 674
2e907fab 675 wxSocketClient *sock = GetPort();
f4ada568 676
2e907fab
VZ
677 wxString tmp_str = wxT("STOR ") + path;
678 if ( !CheckCommand(tmp_str, '1') )
8f901032 679 return NULL;
f4ada568 680
2e907fab 681 m_streaming = TRUE;
f4ada568 682
2e907fab 683 return new wxOutputFTPStream(this, sock);
f4ada568
GL
684}
685
2e907fab
VZ
686// ----------------------------------------------------------------------------
687// FTP directory listing
688// ----------------------------------------------------------------------------
689
690bool wxFTP::GetList(wxArrayString& files,
691 const wxString& wildcard,
692 bool details)
8e907a13
VZ
693{
694 wxSocketBase *sock = GetPort();
2e907fab 695 if (!sock)
8e907a13 696 return FALSE;
8e907a13 697
2e907fab
VZ
698 // NLST : List of Filenames (including Directory's !)
699 // LIST : depending on BS of FTP-Server
700 // - Unix : result like "ls" command
701 // - Windows : like "dir" command
702 // - others : ?
703 wxString line(details ? _T("LIST") : _T("NLST"));
8e907a13
VZ
704 if ( !!wildcard )
705 {
2e907fab 706 line << _T(' ') << wildcard;
8e907a13
VZ
707 }
708
2e907fab 709 if (!CheckCommand(line, '1'))
8e907a13
VZ
710 {
711 return FALSE;
712 }
8e907a13 713 files.Empty();
8e907a13
VZ
714 while ( ReadLine(sock, line) == wxPROTO_NOERR )
715 {
716 files.Add(line);
717 }
8e907a13
VZ
718 delete sock;
719
720 // the file list should be terminated by "226 Transfer complete""
2e907fab 721 if ( !CheckResult('2') )
8e907a13
VZ
722 return FALSE;
723
724 return TRUE;
725}
726
b92fd37c
VZ
727bool wxFTP::FileExists(const wxString& fileName)
728{
3f2bcf34
VZ
729 // This function checks if the file specified in fileName exists in the
730 // current dir. It does so by simply doing an NLST (via GetList).
731 // If this succeeds (and the list is not empty) the file exists.
732
733 bool retval = FALSE;
734 wxArrayString fileList;
735
736 if ( GetList(fileList, fileName, FALSE) )
737 {
738 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
739 // displays this behaviour when queried on a non-existing file:
740 // NLST this_file_does_not_exist
741 // 150 Opening ASCII data connection for directory listing
742 // (no data transferred)
743 // 226 Transfer complete
b92fd37c
VZ
744 // Here wxFTP::GetList(...) will succeed but it will return an empty
745 // list.
746 retval = !fileList.IsEmpty();
3f2bcf34 747 }
b92fd37c 748
3f2bcf34 749 return retval;
b92fd37c
VZ
750}
751
752// ----------------------------------------------------------------------------
753// FTP GetSize
754// ----------------------------------------------------------------------------
755
756int wxFTP::GetFileSize(const wxString& fileName)
757{
3f2bcf34
VZ
758 // return the filesize of the given file if possible
759 // return -1 otherwise (predominantly if file doesn't exist
760 // in current dir)
761
762 int filesize = -1;
b92fd37c 763
3f2bcf34 764 // Check for existance of file via wxFTP::FileExists(...)
b92fd37c
VZ
765 if ( FileExists(fileName) )
766 {
767 wxString command;
768
769 // First try "SIZE" command using BINARY(IMAGE) transfermode
770 // Especially UNIX ftp-servers distinguish between the different
771 // transfermodes and reports different filesizes accordingly.
772 // The BINARY size is the interesting one: How much memory
773 // will we need to hold this file?
3f2bcf34 774 TransferMode oldTransfermode = m_currentTransfermode;
b92fd37c
VZ
775 SetTransferMode(BINARY);
776 command << _T("SIZE ") << fileName;
777
778 bool ok = CheckCommand(command, '2');
779
780 if ( ok )
781 {
782 // The answer should be one line: "213 <filesize>\n"
783 // 213 is File Status (STD9)
3f2bcf34
VZ
784 // "SIZE" is not described anywhere..? It works on most servers
785 int statuscode;
786 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
b92fd37c 787 &statuscode, &filesize) == 2 )
3f2bcf34
VZ
788 {
789 // We've gotten a good reply.
790 ok = TRUE;
791 }
792 else
793 {
794 // Something bad happened.. A "2yz" reply with no size
795 // Fallback
796 ok = FALSE;
797 }
b92fd37c 798 }
3f2bcf34
VZ
799
800 // Set transfermode back to the original. Only the "SIZE"-command
801 // is dependant on transfermode
b92fd37c
VZ
802 if ( oldTransfermode != NONE )
803 {
804 SetTransferMode(oldTransfermode);
805 }
3f2bcf34
VZ
806
807 if ( !ok ) // this is not a direct else clause.. The size command might return an invalid "2yz" reply
b92fd37c 808 {
3f2bcf34
VZ
809 // The server didn't understand the "SIZE"-command or it
810 // returned an invalid reply.
811 // We now try to get details for the file with a "LIST"-command
812 // and then parse the output from there..
813 wxArrayString fileList;
814 if ( GetList(fileList, fileName, TRUE) )
815 {
816 if ( !fileList.IsEmpty() )
817 {
b92fd37c
VZ
818 // We _should_ only get one line in return, but just to be
819 // safe we run through the line(s) returned and look for a
820 // substring containing the name we are looking for. We
821 // stop the iteration at the first occurrence of the
822 // filename. The search is not case-sensitive.
3f2bcf34 823 bool foundIt = FALSE;
b92fd37c
VZ
824
825 size_t i;
3f2bcf34
VZ
826 for ( i = 0; !foundIt && i < fileList.Count(); i++ )
827 {
828 foundIt = fileList[i].Upper().Contains(fileName.Upper());
829 }
b92fd37c 830
3f2bcf34
VZ
831 if ( foundIt )
832 {
b92fd37c
VZ
833 // The index i points to the first occurrence of
834 // fileName in the array Now we have to find out what
835 // format the LIST has returned. There are two
836 // "schools": Unix-like
837 //
838 // '-rw-rw-rw- owner group size month day time filename'
839 //
840 // or Windows-like
841 //
842 // 'date size filename'
843
844 // check if the first character is '-'. This would
845 // indicate Unix-style (this also limits this function
846 // to searching for files, not directories)
847 if ( fileList[i].Mid(0, 1) == _T("-") )
3f2bcf34 848 {
b92fd37c 849
3f2bcf34 850 if ( wxSscanf(fileList[i].c_str(),
1489a2c0 851 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
b92fd37c 852 &filesize) == 9 )
3f2bcf34
VZ
853 {
854 // We've gotten a good response
855 ok = TRUE;
856 }
857 else
858 {
859 // Hmm... Invalid response
860 wxLogTrace(FTP_TRACE_MASK,
b92fd37c 861 _T("Invalid LIST response"));
3f2bcf34
VZ
862 }
863 }
864 else // Windows-style response (?)
865 {
866 if ( wxSscanf(fileList[i].c_str(),
b92fd37c
VZ
867 _T("%*s %*s %i %*s"),
868 &filesize) == 4 )
3f2bcf34
VZ
869 {
870 // valid response
871 ok = TRUE;
872 }
873 else
874 {
875 // something bad happened..?
876 wxLogTrace(FTP_TRACE_MASK,
b92fd37c 877 _T("Invalid or unknown LIST response"));
3f2bcf34
VZ
878 }
879 }
880 }
881 }
882 }
b92fd37c
VZ
883 }
884 }
885
3f2bcf34
VZ
886 // filesize might still be -1 when exiting
887 return filesize;
b92fd37c
VZ
888}
889
890
1d79bd3e 891#if WXWIN_COMPATIBILITY_2
2e907fab
VZ
892// deprecated
893wxList *wxFTP::GetList(const wxString& wildcard, bool details)
f4ada568 894{
2e907fab
VZ
895 wxSocketBase *sock = GetPort();
896 if (!sock)
e30285ab 897 return NULL;
2e907fab
VZ
898 wxList *file_list = new wxList;
899 wxString line;
900 // NLST : List of Filenames (including Directory's !)
901 // LIST : depending on BS of FTP-Server
902 // - Unix : result like "ls" command
903 // - Windows : like "dir" command
904 // - others : ?
905 if (!details)
906 line = _T("NLST"); // Default
907 else
908 line = _T("LIST");
909 if (!wildcard.IsNull())
910 line += wildcard;
911 if (!CheckCommand(line, '1'))
912 {
913 delete sock;
914 delete file_list;
915 return NULL;
916 }
917 while (GetLine(sock, line) == wxPROTO_NOERR)
918 {
919 file_list->Append((wxObject *)(new wxString(line)));
920 }
921 if (!CheckResult('2'))
922 {
923 delete sock;
924 file_list->DeleteContents(TRUE);
925 delete file_list;
926 return NULL;
927 }
928 return file_list;
f4ada568 929}
2e907fab
VZ
930#endif // WXWIN_COMPATIBILITY_2
931
a5d46b73
VZ
932#endif // wxUSE_PROTOCOL_FTP
933