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 
  13 // Copyright:   (c) 1997, 1998 Guilhem Lavaux 
  14 //              (c) 1998-2004 wxWidgets team 
  15 // Licence:     wxWindows licence 
  16 ///////////////////////////////////////////////////////////////////////////// 
  18 // ============================================================================ 
  20 // ============================================================================ 
  22 // ---------------------------------------------------------------------------- 
  24 // ---------------------------------------------------------------------------- 
  26 // For compilers that support precompilation, includes "wx.h". 
  27 #include "wx/wxprec.h" 
  33 #if wxUSE_PROTOCOL_FTP 
  37     #include "wx/string.h" 
  41     #include "wx/wxcrtvararg.h" 
  44 #include "wx/sckaddr.h" 
  45 #include "wx/socket.h" 
  47 #include "wx/sckstrm.h" 
  48 #include "wx/protocol/protocol.h" 
  49 #include "wx/protocol/ftp.h" 
  55 // ---------------------------------------------------------------------------- 
  57 // ---------------------------------------------------------------------------- 
  59 // the length of FTP status code (3 digits) 
  60 static const size_t LEN_CODE 
= 3; 
  62 // ---------------------------------------------------------------------------- 
  64 // ---------------------------------------------------------------------------- 
  66 IMPLEMENT_DYNAMIC_CLASS(wxFTP
, wxProtocol
) 
  67 IMPLEMENT_PROTOCOL(wxFTP
, wxT("ftp"), wxT("ftp"), true) 
  69 // ============================================================================ 
  71 // ============================================================================ 
  73 // ---------------------------------------------------------------------------- 
  74 // wxFTP constructor and destructor 
  75 // ---------------------------------------------------------------------------- 
  79     m_lastError 
= wxPROTO_NOERR
; 
  81     m_currentTransfermode 
= NONE
; 
  83     m_user 
= wxT("anonymous"); 
  84     m_passwd 
<< wxGetUserId() << wxT('@') << wxGetFullHostName(); 
  87     SetFlags(wxSOCKET_NOWAIT
); 
  89     SetDefaultTimeout(60); // Default is Sixty Seconds 
  90     m_bEncounteredError 
= false; 
  97         // if we are streaming, this will issue 
  98         // an FTP ABORT command, to tell the server we are aborting 
 102     // now this issues a "QUIT" command to tell the server we are 
 106 // ---------------------------------------------------------------------------- 
 107 // wxFTP connect and login methods 
 108 // ---------------------------------------------------------------------------- 
 110 bool wxFTP::Connect(const wxSockAddress
& addr
, bool WXUNUSED(wait
)) 
 112     if ( !wxProtocol::Connect(addr
) ) 
 114         m_lastError 
= wxPROTO_NETERR
; 
 120         m_lastError 
= wxPROTO_CONNERR
; 
 124     // we should have 220 welcome message 
 125     if ( !CheckResult('2') ) 
 132     command
.Printf(wxT("USER %s"), m_user
.c_str()); 
 133     char rc 
= SendCommand(command
); 
 136         // 230 return: user accepted without password 
 146     command
.Printf(wxT("PASS %s"), m_passwd
.c_str()); 
 147     if ( !CheckCommand(command
, '2') ) 
 156 bool wxFTP::Connect(const wxString
& host
) 
 160     addr
.Service(wxT("ftp")); 
 162     return Connect(addr
); 
 169         m_lastError 
= wxPROTO_STREAMING
; 
 175         if ( !CheckCommand(wxT("QUIT"), '2') ) 
 177             wxLogDebug(_T("Failed to close connection gracefully.")); 
 181     return wxSocketClient::Close(); 
 184 // ============================================================================ 
 186 // ============================================================================ 
 188 wxSocketBase 
*wxFTP::AcceptIfActive(wxSocketBase 
*sock
) 
 193     // now wait for a connection from server 
 194     wxSocketServer 
*sockSrv 
= (wxSocketServer 
*)sock
; 
 195     if ( !sockSrv
->WaitForAccept() ) 
 197         m_lastError 
= wxPROTO_CONNERR
; 
 198         wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode.")); 
 204         sock 
= sockSrv
->Accept(true); 
 217     if ( !CheckCommand(wxT("ABOR"), '4') ) 
 220     return CheckResult('2'); 
 223 void wxFTP::SetDefaultTimeout(wxUint32 Value
) 
 225     m_uiDefaultTimeout 
= Value
; 
 226     SetTimeout(Value
); // sets it for this socket 
 229 // ---------------------------------------------------------------------------- 
 230 // Send command to FTP server 
 231 // ---------------------------------------------------------------------------- 
 233 char wxFTP::SendCommand(const wxString
& command
) 
 237         m_lastError 
= wxPROTO_STREAMING
; 
 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()) 
 245         m_lastError 
= wxPROTO_NETERR
; 
 250     // don't show the passwords in the logs (even in debug ones) 
 251     wxString cmd
, password
; 
 252     if ( command
.Upper().StartsWith(_T("PASS "), &password
) ) 
 254         cmd 
<< _T("PASS ") << wxString(_T('*'), password
.length()); 
 261     wxLogTrace(FTP_TRACE_MASK
, _T("==> %s"), cmd
.c_str()); 
 262 #endif // __WXDEBUG__ 
 267 // ---------------------------------------------------------------------------- 
 268 // Receive servers reply 
 269 // ---------------------------------------------------------------------------- 
 271 char wxFTP::GetResult() 
 273     // if we've already had a read or write timeout error, the connection is 
 274     // probably toast, so don't bother, it just wastes the users time 
 275     if ( m_bEncounteredError 
) 
 280     // m_lastResult will contain the entire server response, possibly on 
 282     m_lastResult
.clear(); 
 284     // we handle multiline replies here according to RFC 959: it says that a 
 285     // reply may either be on 1 line of the form "xyz ..." or on several lines 
 286     // in whuch case it looks like 
 290     // and the intermeidate lines may start with xyz or not 
 291     bool badReply 
= false; 
 292     bool firstLine 
= true; 
 293     bool endOfReply 
= false; 
 294     while ( !endOfReply 
&& !badReply 
) 
 297         m_lastError 
= ReadLine(this,line
); 
 300             m_bEncounteredError 
= true; 
 304         if ( !m_lastResult
.empty() ) 
 306             // separate from last line 
 307             m_lastResult 
+= _T('\n'); 
 310         m_lastResult 
+= line
; 
 312         // unless this is an intermediate line of a multiline reply, it must 
 313         // contain the code in the beginning and '-' or ' ' following it 
 314         if ( line
.Len() < LEN_CODE 
+ 1 ) 
 322                 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 323                            code
.c_str(), line
.c_str()); 
 326         else // line has at least 4 chars 
 328             // this is the char which tells us what we're dealing with 
 329             wxChar chMarker 
= line
.GetChar(LEN_CODE
); 
 333                 code 
= wxString(line
, LEN_CODE
); 
 334                 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 335                            code
.c_str(), line
.c_str() + LEN_CODE 
+ 1); 
 352             else // subsequent line of multiline reply 
 354                 if ( line
.compare(0, LEN_CODE
, code
) == 0 ) 
 356                     if ( chMarker 
== _T(' ') ) 
 361                     wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 362                                code
.c_str(), line
.c_str() + LEN_CODE 
+ 1); 
 366                     // just part of reply 
 367                     wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 368                                code
.c_str(), line
.c_str()); 
 376         wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."), 
 377                    m_lastResult
.c_str()); 
 379         m_lastError 
= wxPROTO_PROTERR
; 
 384     // if we got here we must have a non empty code string 
 385     return (char)code
[0u]; 
 388 // ---------------------------------------------------------------------------- 
 389 // wxFTP simple commands 
 390 // ---------------------------------------------------------------------------- 
 392 bool wxFTP::SetTransferMode(TransferMode transferMode
) 
 394     if ( transferMode 
== m_currentTransfermode 
) 
 401     switch ( transferMode 
) 
 404             wxFAIL_MSG(_T("unknown FTP transfer mode")); 
 416     if ( !DoSimpleCommand(_T("TYPE"), mode
) ) 
 418         wxLogError(_("Failed to set FTP transfer mode to %s."), 
 419                    (transferMode 
== ASCII 
? _("ASCII") : _("binary"))); 
 424     // If we get here the operation has been successfully completed 
 425     // Set the status-member 
 426     m_currentTransfermode 
= transferMode
; 
 431 bool wxFTP::DoSimpleCommand(const wxChar 
*command
, const wxString
& arg
) 
 433     wxString fullcmd 
= command
; 
 436         fullcmd 
<< _T(' ') << arg
; 
 439     if ( !CheckCommand(fullcmd
, '2') ) 
 441         wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str()); 
 449 bool wxFTP::ChDir(const wxString
& dir
) 
 451     // some servers might not understand ".." if they use different directory 
 452     // tree conventions, but they always understand CDUP - should we use it if 
 453     // dir == ".."? OTOH, do such servers (still) exist? 
 455     return DoSimpleCommand(_T("CWD"), dir
); 
 458 bool wxFTP::MkDir(const wxString
& dir
) 
 460     return DoSimpleCommand(_T("MKD"), dir
); 
 463 bool wxFTP::RmDir(const wxString
& dir
) 
 465     return DoSimpleCommand(_T("RMD"), dir
); 
 468 wxString 
wxFTP::Pwd() 
 472     if ( CheckCommand(wxT("PWD"), '2') ) 
 474         // the result is at least that long if CheckCommand() succeeded 
 475         wxString::const_iterator p 
= m_lastResult
.begin() + LEN_CODE 
+ 1; 
 478             wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), 
 479                        wxString(p
, m_lastResult
.end())); 
 483             for ( ++p
; (bool)*p
; ++p 
) // FIXME-DMARS 
 487                     // check if the quote is doubled 
 489                     if ( !*p 
|| *p 
!= _T('"') ) 
 491                         // no, this is the end 
 494                     //else: yes, it is: this is an embedded quote in the 
 495                     //      filename, treat as normal char 
 503                 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"), 
 504                            m_lastResult
.c_str() + LEN_CODE 
+ 1); 
 510         wxLogDebug(_T("FTP PWD command failed.")); 
 516 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
) 
 520     str 
= wxT("RNFR ") + src
; 
 521     if ( !CheckCommand(str
, '3') ) 
 524     str 
= wxT("RNTO ") + dst
; 
 526     return CheckCommand(str
, '2'); 
 529 bool wxFTP::RmFile(const wxString
& path
) 
 532     str 
= wxT("DELE ") + path
; 
 534     return CheckCommand(str
, '2'); 
 537 // ---------------------------------------------------------------------------- 
 538 // wxFTP port methods 
 539 // ---------------------------------------------------------------------------- 
 541 wxSocketBase 
*wxFTP::GetPort() 
 544     PASSIVE:    Client sends a "PASV" to the server.  The server responds with 
 545                 an address and port number which it will be listening on. Then 
 546                 the client connects to the server at the specified address and 
 549     ACTIVE:     Client sends the server a PORT command which includes an 
 550                 address and port number which the client will be listening on. 
 551                 The server then connects to the client at that address and 
 555     wxSocketBase 
*socket 
= m_bPassive 
? GetPassivePort() : GetActivePort(); 
 558         m_bEncounteredError 
= true; 
 562     // Now set the time for the new socket to the default or user selected 
 564     socket
->SetTimeout(m_uiDefaultTimeout
); 
 569 wxString 
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
, 
 570                                    const wxIPV4address
& addrNew
) 
 572     // Just fills in the return value with the local IP 
 573     // address of the current socket.  Also it fill in the 
 574     // PORT which the client will be listening on 
 576     wxString addrIP 
= addrLocal
.IPAddress(); 
 577     int portNew 
= addrNew
.Service(); 
 579     // We need to break the PORT number in bytes 
 580     addrIP
.Replace(_T("."), _T(",")); 
 582            << wxString::Format(_T("%d"), portNew 
>> 8) << _T(',') 
 583            << wxString::Format(_T("%d"), portNew 
& 0xff); 
 585     // Now we have a value like "10,0,0,1,5,23" 
 589 wxSocketBase 
*wxFTP::GetActivePort() 
 591     // we need an address to listen on 
 592     wxIPV4address addrNew
, addrLocal
; 
 594     addrNew
.AnyAddress(); 
 595     addrNew
.Service(0); // pick an open port number. 
 597     wxSocketServer 
*sockSrv 
= new wxSocketServer(addrNew
); 
 600         // We use Ok() here to see if everything is ok 
 601         m_lastError 
= wxPROTO_PROTERR
; 
 606     //gets the new address, actually it is just the port number 
 607     sockSrv
->GetLocal(addrNew
); 
 609     // Now we create the argument of the PORT command, we send in both 
 610     // addresses because the addrNew has an IP of "0.0.0.0", so we need the 
 611     // value in addrLocal 
 612     wxString port 
= GetPortCmdArgument(addrLocal
, addrNew
); 
 613     if ( !DoSimpleCommand(_T("PORT"), port
) ) 
 615         m_lastError 
= wxPROTO_PROTERR
; 
 617         wxLogError(_("The FTP server doesn't support the PORT command.")); 
 621     sockSrv
->Notify(false); // Don't send any events 
 625 wxSocketBase 
*wxFTP::GetPassivePort() 
 627     if ( !DoSimpleCommand(_T("PASV")) ) 
 629         wxLogError(_("The FTP server doesn't support passive mode.")); 
 633     size_t addrStart 
= m_lastResult
.find(_T('(')); 
 634     size_t addrEnd 
= (addrStart 
== wxString::npos
) 
 636                      : m_lastResult
.find(_T(')'), addrStart
); 
 638     if ( addrEnd 
== wxString::npos 
) 
 640         m_lastError 
= wxPROTO_PROTERR
; 
 644     // get the port number and address 
 646     wxString 
straddr(m_lastResult
, addrStart 
+ 1, addrEnd 
- (addrStart 
+ 1)); 
 647     wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"), 
 648              &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]); 
 650     wxUint32 hostaddr 
= (wxUint16
)a
[2] << 24 | 
 651                         (wxUint16
)a
[3] << 16 | 
 652                         (wxUint16
)a
[4] << 8 | 
 654     wxUint16 port 
= (wxUint16
)(a
[0] << 8 | a
[1]); 
 657     addr
.Hostname(hostaddr
); 
 660     wxSocketClient 
*client 
= new wxSocketClient(); 
 661     if ( !client
->Connect(addr
) ) 
 667     client
->Notify(false); 
 673 // ---------------------------------------------------------------------------- 
 674 // wxFTP download and upload 
 675 // ---------------------------------------------------------------------------- 
 677 class wxInputFTPStream 
: public wxSocketInputStream
 
 680     wxInputFTPStream(wxFTP 
*ftp
, wxSocketBase 
*sock
) 
 681         : wxSocketInputStream(*sock
) 
 684         // socket timeout automatically set in GetPort function 
 687     virtual ~wxInputFTPStream() 
 689         delete m_i_socket
;   // keep at top 
 691         // when checking the result, the stream will 
 692         // almost always show an error, even if the file was 
 693         // properly transfered, thus, lets just grab the result 
 695         // we are looking for "226 transfer completed" 
 696         char code 
= m_ftp
->GetResult(); 
 699             // it was a good transfer. 
 701              m_ftp
->m_streaming 
= false; 
 707             // the connection is probably toast. issue an abort, and 
 708             // then a close. there won't be any more waiting 
 709             // for this connection 
 714         // There was a problem with the transfer and the server 
 715         // has acknowledged it.  If we issue an "ABORT" now, the user 
 716         // would get the "226" for the abort and think the xfer was 
 717         // complete, thus, don't do anything here, just return 
 722     DECLARE_NO_COPY_CLASS(wxInputFTPStream
) 
 725 class wxOutputFTPStream 
: public wxSocketOutputStream
 
 728     wxOutputFTPStream(wxFTP 
*ftp_clt
, wxSocketBase 
*sock
) 
 729         : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
) 
 733     virtual ~wxOutputFTPStream(void) 
 737             // close data connection first, this will generate "transfer 
 742             m_ftp
->GetResult(); // save result so user can get to it 
 744             m_ftp
->m_streaming 
= false; 
 748             // abort data connection first 
 751             // and close it after 
 758     DECLARE_NO_COPY_CLASS(wxOutputFTPStream
) 
 761 wxInputStream 
*wxFTP::GetInputStream(const wxString
& path
) 
 763     if ( ( m_currentTransfermode 
== NONE 
) && !SetTransferMode(BINARY
) ) 
 766     wxSocketBase 
*sock 
= GetPort(); 
 770         m_lastError 
= wxPROTO_NETERR
; 
 774     wxString tmp_str 
= wxT("RETR ") + wxURI::Unescape(path
); 
 775     if ( !CheckCommand(tmp_str
, '1') ) 
 778     sock 
= AcceptIfActive(sock
); 
 782     sock
->SetFlags(wxSOCKET_WAITALL
); 
 786     wxInputFTPStream 
*in_stream 
= new wxInputFTPStream(this, sock
); 
 791 wxOutputStream 
*wxFTP::GetOutputStream(const wxString
& path
) 
 793     if ( ( m_currentTransfermode 
== NONE 
) && !SetTransferMode(BINARY
) ) 
 796     wxSocketBase 
*sock 
= GetPort(); 
 798     wxString tmp_str 
= wxT("STOR ") + path
; 
 799     if ( !CheckCommand(tmp_str
, '1') ) 
 802     sock 
= AcceptIfActive(sock
); 
 806     return new wxOutputFTPStream(this, sock
); 
 809 // ---------------------------------------------------------------------------- 
 810 // FTP directory listing 
 811 // ---------------------------------------------------------------------------- 
 813 bool wxFTP::GetList(wxArrayString
& files
, 
 814                     const wxString
& wildcard
, 
 817     wxSocketBase 
*sock 
= GetPort(); 
 821     // NLST : List of Filenames (including Directory's !) 
 822     // LIST : depending on BS of FTP-Server 
 823     //        - Unix    : result like "ls" command 
 824     //        - Windows : like "dir" command 
 826     wxString 
line(details 
? _T("LIST") : _T("NLST")); 
 827     if ( !wildcard
.empty() ) 
 829         line 
<< _T(' ') << wildcard
; 
 832     if ( !CheckCommand(line
, '1') ) 
 834         m_lastError 
= wxPROTO_PROTERR
; 
 835         wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server")); 
 840     sock 
= AcceptIfActive(sock
); 
 845     while (ReadLine(sock
, line
) == wxPROTO_NOERR 
) 
 852     // the file list should be terminated by "226 Transfer complete"" 
 853     return CheckResult('2'); 
 856 bool wxFTP::FileExists(const wxString
& fileName
) 
 858     // This function checks if the file specified in fileName exists in the 
 859     // current dir. It does so by simply doing an NLST (via GetList). 
 860     // If this succeeds (and the list is not empty) the file exists. 
 863     wxArrayString fileList
; 
 865     if ( GetList(fileList
, fileName
, false) ) 
 867         // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this) 
 868         // displays this behaviour when queried on a nonexistent file: 
 869         // NLST this_file_does_not_exist 
 870         // 150 Opening ASCII data connection for directory listing 
 871         // (no data transferred) 
 872         // 226 Transfer complete 
 873         // Here wxFTP::GetList(...) will succeed but it will return an empty 
 875         retval 
= !fileList
.IsEmpty(); 
 881 // ---------------------------------------------------------------------------- 
 883 // ---------------------------------------------------------------------------- 
 885 int wxFTP::GetFileSize(const wxString
& fileName
) 
 887     // return the filesize of the given file if possible 
 888     // return -1 otherwise (predominantly if file doesn't exist 
 893     // Check for existance of file via wxFTP::FileExists(...) 
 894     if ( FileExists(fileName
) ) 
 898         // First try "SIZE" command using BINARY(IMAGE) transfermode 
 899         // Especially UNIX ftp-servers distinguish between the different 
 900         // transfermodes and reports different filesizes accordingly. 
 901         // The BINARY size is the interesting one: How much memory 
 902         // will we need to hold this file? 
 903         TransferMode oldTransfermode 
= m_currentTransfermode
; 
 904         SetTransferMode(BINARY
); 
 905         command 
<< _T("SIZE ") << fileName
; 
 907         bool ok 
= CheckCommand(command
, '2'); 
 911             // The answer should be one line: "213 <filesize>\n" 
 912             // 213 is File Status (STD9) 
 913             // "SIZE" is not described anywhere..? It works on most servers 
 915             if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"), 
 916                           &statuscode
, &filesize
) == 2 ) 
 918                 // We've gotten a good reply. 
 923                 // Something bad happened.. A "2yz" reply with no size 
 929         // Set transfermode back to the original. Only the "SIZE"-command 
 930         // is dependant on transfermode 
 931         if ( oldTransfermode 
!= NONE 
) 
 933             SetTransferMode(oldTransfermode
); 
 936         // this is not a direct else clause.. The size command might return an 
 937         // invalid "2yz" reply 
 940             // The server didn't understand the "SIZE"-command or it 
 941             // returned an invalid reply. 
 942             // We now try to get details for the file with a "LIST"-command 
 943             // and then parse the output from there.. 
 944             wxArrayString fileList
; 
 945             if ( GetList(fileList
, fileName
, true) ) 
 947                 if ( !fileList
.IsEmpty() ) 
 949                     // We _should_ only get one line in return, but just to be 
 950                     // safe we run through the line(s) returned and look for a 
 951                     // substring containing the name we are looking for. We 
 952                     // stop the iteration at the first occurrence of the 
 953                     // filename. The search is not case-sensitive. 
 954                     bool foundIt 
= false; 
 957                     for ( i 
= 0; !foundIt 
&& i 
< fileList
.GetCount(); i
++ ) 
 959                         foundIt 
= fileList
[i
].Upper().Contains(fileName
.Upper()); 
 964                         // The index i points to the first occurrence of 
 965                         // fileName in the array Now we have to find out what 
 966                         // format the LIST has returned. There are two 
 967                         // "schools": Unix-like 
 969                         // '-rw-rw-rw- owner group size month day time filename' 
 973                         // 'date size filename' 
 975                         // check if the first character is '-'. This would 
 976                         // indicate Unix-style (this also limits this function 
 977                         // to searching for files, not directories) 
 978                         if ( fileList
[i
].Mid(0, 1) == _T("-") ) 
 981                             if ( wxSscanf(fileList
[i
].c_str(), 
 982                                           _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"), 
 985                                 // Hmm... Invalid response 
 986                                 wxLogTrace(FTP_TRACE_MASK
, 
 987                                            _T("Invalid LIST response")); 
 990                         else // Windows-style response (?) 
 992                             if ( wxSscanf(fileList
[i
].c_str(), 
 993                                           _T("%*s %*s %i %*s"), 
 996                                 // something bad happened..? 
 997                                 wxLogTrace(FTP_TRACE_MASK
, 
 998                                            _T("Invalid or unknown LIST response")); 
1007     // filesize might still be -1 when exiting 
1011 #endif // wxUSE_PROTOCOL_FTP