1 ///////////////////////////////////////////////////////////////////////////// 
   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 // Created:     07/07/1997 
  12 // Copyright:   (c) 1997, 1998 Guilhem Lavaux 
  13 // Licence:     wxWindows licence 
  14 ///////////////////////////////////////////////////////////////////////////// 
  16 // ============================================================================ 
  18 // ============================================================================ 
  20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) 
  21   #pragma implementation "ftp.h" 
  24 // ---------------------------------------------------------------------------- 
  26 // ---------------------------------------------------------------------------- 
  28 // For compilers that support precompilation, includes "wx.h". 
  29 #include "wx/wxprec.h" 
  35 #if wxUSE_PROTOCOL_FTP 
  39     #include "wx/string.h" 
  45 #include "wx/sckaddr.h" 
  46 #include "wx/socket.h" 
  48 #include "wx/sckstrm.h" 
  49 #include "wx/protocol/protocol.h" 
  50 #include "wx/protocol/ftp.h" 
  52 #if defined(__WXMAC__) 
  53     #include "wx/mac/macsock.h" 
  60 // ---------------------------------------------------------------------------- 
  62 // ---------------------------------------------------------------------------- 
  64 // the length of FTP status code (3 digits) 
  65 static const size_t LEN_CODE 
= 3; 
  67 // ---------------------------------------------------------------------------- 
  69 // ---------------------------------------------------------------------------- 
  71 IMPLEMENT_DYNAMIC_CLASS(wxFTP
, wxProtocol
) 
  72 IMPLEMENT_PROTOCOL(wxFTP
, wxT("ftp"), wxT("ftp"), TRUE
) 
  74 // ============================================================================ 
  76 // ============================================================================ 
  78 // ---------------------------------------------------------------------------- 
  79 // wxFTP constructor and destructor 
  80 // ---------------------------------------------------------------------------- 
  84     m_lastError 
= wxPROTO_NOERR
; 
  86     m_currentTransfermode 
= NONE
; 
  88     m_user 
= wxT("anonymous"); 
  89     m_passwd 
<< wxGetUserId() << wxT('@') << wxGetFullHostName(); 
  92     SetFlags(wxSOCKET_NONE
); 
 105 // ---------------------------------------------------------------------------- 
 106 // wxFTP connect and login methods 
 107 // ---------------------------------------------------------------------------- 
 109 bool wxFTP::Connect(wxSockAddress
& addr
, bool WXUNUSED(wait
)) 
 111     if ( !wxProtocol::Connect(addr
) ) 
 113         m_lastError 
= wxPROTO_NETERR
; 
 119         m_lastError 
= wxPROTO_CONNERR
; 
 123     // we should have 220 welcome message 
 124     if ( !CheckResult('2') ) 
 131     command
.Printf(wxT("USER %s"), m_user
.c_str()); 
 132     char rc 
= SendCommand(command
); 
 135         // 230 return: user accepted without password 
 145     command
.Printf(wxT("PASS %s"), m_passwd
.c_str()); 
 146     if ( !CheckCommand(command
, '2') ) 
 155 bool wxFTP::Connect(const wxString
& host
) 
 159     addr
.Service(wxT("ftp")); 
 161     return Connect(addr
); 
 168         m_lastError 
= wxPROTO_STREAMING
; 
 174         if ( !CheckCommand(wxT("QUIT"), '2') ) 
 176             wxLogDebug(_T("Failed to close connection gracefully.")); 
 180     return wxSocketClient::Close(); 
 183 // ============================================================================ 
 185 // ============================================================================ 
 187 // ---------------------------------------------------------------------------- 
 188 // Send command to FTP server 
 189 // ---------------------------------------------------------------------------- 
 191 char wxFTP::SendCommand(const wxString
& command
) 
 195         m_lastError 
= wxPROTO_STREAMING
; 
 199     wxString tmp_str 
= command 
+ wxT("\r\n"); 
 200     const wxWX2MBbuf tmp_buf 
= tmp_str
.mb_str(); 
 201     if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error()) 
 203         m_lastError 
= wxPROTO_NETERR
; 
 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
) ) 
 212         cmd 
<< _T("PASS ") << wxString(_T('*'), password
.length()); 
 219     wxLogTrace(FTP_TRACE_MASK
, _T("==> %s"), cmd
.c_str()); 
 220 #endif // __WXDEBUG__ 
 225 // ---------------------------------------------------------------------------- 
 226 // Recieve servers reply 
 227 // ---------------------------------------------------------------------------- 
 229 char wxFTP::GetResult() 
 233     // m_lastResult will contain the entire server response, possibly on 
 235     m_lastResult
.clear(); 
 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 
 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 
) 
 250         m_lastError 
= ReadLine(line
); 
 254         if ( !m_lastResult
.empty() ) 
 256             // separate from last line 
 257             m_lastResult 
+= _T('\n'); 
 260         m_lastResult 
+= line
; 
 262         // unless this is an intermediate line of a multiline reply, it must 
 263         // contain the code in the beginning and '-' or ' ' following it 
 264         if ( line
.Len() < LEN_CODE 
+ 1 ) 
 272                 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 273                            code
.c_str(), line
.c_str()); 
 276         else // line has at least 4 chars 
 278             // this is the char which tells us what we're dealing with 
 279             wxChar chMarker 
= line
.GetChar(LEN_CODE
); 
 283                 code 
= wxString(line
, LEN_CODE
); 
 284                 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 285                            code
.c_str(), line
.c_str() + LEN_CODE 
+ 1); 
 302             else // subsequent line of multiline reply 
 304                 if ( wxStrncmp(line
, code
, LEN_CODE
) == 0 ) 
 306                     if ( chMarker 
== _T(' ') ) 
 311                     wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 312                                code
.c_str(), line
.c_str() + LEN_CODE 
+ 1); 
 316                     // just part of reply 
 317                     wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"), 
 318                                code
.c_str(), line
.c_str()); 
 326         wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."), 
 327                    m_lastResult
.c_str()); 
 329         m_lastError 
= wxPROTO_PROTERR
; 
 334     // if we got here we must have a non empty code string 
 338 // ---------------------------------------------------------------------------- 
 339 // wxFTP simple commands 
 340 // ---------------------------------------------------------------------------- 
 342 bool wxFTP::SetTransferMode(TransferMode transferMode
) 
 344     if ( transferMode 
== m_currentTransfermode 
) 
 351     switch ( transferMode 
) 
 354             wxFAIL_MSG(_T("unknown FTP transfer mode")); 
 366     if ( !DoSimpleCommand(_T("TYPE"), mode
) ) 
 368         wxLogError(_("Failed to set FTP transfer mode to %s."), (const wxChar
*) 
 369                    (transferMode 
== ASCII 
? _("ASCII") : _("binary"))); 
 374     // If we get here the operation has been succesfully completed 
 375     // Set the status-member 
 376     m_currentTransfermode 
= transferMode
; 
 381 bool wxFTP::DoSimpleCommand(const wxChar 
*command
, const wxString
& arg
) 
 383     wxString fullcmd 
= command
; 
 386         fullcmd 
<< _T(' ') << arg
; 
 389     if ( !CheckCommand(fullcmd
, '2') ) 
 391         wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str()); 
 399 bool wxFTP::ChDir(const wxString
& dir
) 
 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? 
 405     return DoSimpleCommand(_T("CWD"), dir
); 
 408 bool wxFTP::MkDir(const wxString
& dir
) 
 410     return DoSimpleCommand(_T("MKD"), dir
); 
 413 bool wxFTP::RmDir(const wxString
& dir
) 
 415     return DoSimpleCommand(_T("RMD"), dir
); 
 418 wxString 
wxFTP::Pwd() 
 422     if ( CheckCommand(wxT("PWD"), '2') ) 
 424         // the result is at least that long if CheckCommand() succeeded 
 425         const wxChar 
*p 
= m_lastResult
.c_str() + LEN_CODE 
+ 1; 
 428             wxLogDebug(_T("Missing starting quote in reply for PWD: %s"), p
); 
 436                     // check if the quote is doubled 
 438                     if ( !*p 
|| *p 
!= _T('"') ) 
 440                         // no, this is the end 
 443                     //else: yes, it is: this is an embedded quote in the 
 444                     //      filename, treat as normal char 
 452                 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"), 
 453                            m_lastResult
.c_str() + LEN_CODE 
+ 1); 
 459         wxLogDebug(_T("FTP PWD command failed.")); 
 465 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
) 
 469     str 
= wxT("RNFR ") + src
; 
 470     if ( !CheckCommand(str
, '3') ) 
 473     str 
= wxT("RNTO ") + dst
; 
 475     return CheckCommand(str
, '2'); 
 478 bool wxFTP::RmFile(const wxString
& path
) 
 481     str 
= wxT("DELE ") + path
; 
 483     return CheckCommand(str
, '2'); 
 486 // ---------------------------------------------------------------------------- 
 487 // wxFTP download and upload 
 488 // ---------------------------------------------------------------------------- 
 490 class wxInputFTPStream 
: public wxSocketInputStream
 
 493     wxInputFTPStream(wxFTP 
*ftp
, wxSocketBase 
*sock
) 
 494         : wxSocketInputStream(*sock
) 
 498         // FIXME make the timeout configurable 
 500         // set a shorter than default timeout 
 501         m_i_socket
->SetTimeout(60); // 1 minute 
 504     size_t GetSize() const { return m_ftpsize
; } 
 506     virtual ~wxInputFTPStream() 
 512             // wait for "226 transfer completed" 
 513             m_ftp
->CheckResult('2'); 
 515             m_ftp
->m_streaming 
= FALSE
; 
 522         // delete m_i_socket; // moved to top of destructor to accomodate wu-FTPd >= 2.6.0 
 528     DECLARE_NO_COPY_CLASS(wxInputFTPStream
) 
 531 class wxOutputFTPStream 
: public wxSocketOutputStream
 
 534     wxOutputFTPStream(wxFTP 
*ftp_clt
, wxSocketBase 
*sock
) 
 535         : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
) 
 539     virtual ~wxOutputFTPStream(void) 
 543             // close data connection first, this will generate "transfer 
 548             m_ftp
->CheckResult('2'); 
 550             m_ftp
->m_streaming 
= FALSE
; 
 554             // abort data connection first 
 557             // and close it after 
 564     DECLARE_NO_COPY_CLASS(wxOutputFTPStream
) 
 567 wxSocketClient 
*wxFTP::GetPort() 
 571     if ( !DoSimpleCommand(_T("PASV")) ) 
 573         wxLogError(_("The FTP server doesn't support passive mode.")); 
 578     const wxChar 
*addrStart 
= wxStrchr(m_lastResult
, _T('(')); 
 581         m_lastError 
= wxPROTO_PROTERR
; 
 586     const wxChar 
*addrEnd 
= wxStrchr(addrStart
, _T(')')); 
 589         m_lastError 
= wxPROTO_PROTERR
; 
 594     wxString 
straddr(addrStart 
+ 1, addrEnd
); 
 596     wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"), 
 597              &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]); 
 599     wxUint32 hostaddr 
= (wxUint16
)a
[5] << 24 | 
 600                         (wxUint16
)a
[4] << 16 | 
 601                         (wxUint16
)a
[3] << 8 | 
 603     wxUint16 port 
= (wxUint16
)a
[0] << 8 | a
[1]; 
 606     addr
.Hostname(hostaddr
); 
 609     wxSocketClient 
*client 
= new wxSocketClient(); 
 610     if ( !client
->Connect(addr
) ) 
 616     client
->Notify(FALSE
); 
 627     if ( !CheckCommand(wxT("ABOR"), '4') ) 
 630     return CheckResult('2'); 
 633 wxInputStream 
*wxFTP::GetInputStream(const wxString
& path
) 
 636     wxInputFTPStream 
*in_stream
; 
 638     if ( ( m_currentTransfermode 
== NONE 
) && !SetTransferMode(BINARY
) ) 
 641     wxSocketClient 
*sock 
= GetPort(); 
 645         m_lastError 
= wxPROTO_NETERR
; 
 649     wxString tmp_str 
= wxT("RETR ") + wxURL::ConvertFromURI(path
); 
 650     if ( !CheckCommand(tmp_str
, '1') ) 
 655     in_stream 
= new wxInputFTPStream(this, sock
); 
 657     pos_size 
= m_lastResult
.Index(wxT('(')); 
 658     if ( pos_size 
!= wxNOT_FOUND 
) 
 660         wxString str_size 
= m_lastResult(pos_size
+1, m_lastResult
.Index(wxT(')'))-1); 
 662         in_stream
->m_ftpsize 
= wxAtoi(WXSTRINGCAST str_size
); 
 665     sock
->SetFlags(wxSOCKET_WAITALL
); 
 670 wxOutputStream 
*wxFTP::GetOutputStream(const wxString
& path
) 
 672     if ( ( m_currentTransfermode 
== NONE 
) && !SetTransferMode(BINARY
) ) 
 675     wxSocketClient 
*sock 
= GetPort(); 
 677     wxString tmp_str 
= wxT("STOR ") + path
; 
 678     if ( !CheckCommand(tmp_str
, '1') ) 
 683     return new wxOutputFTPStream(this, sock
); 
 686 // ---------------------------------------------------------------------------- 
 687 // FTP directory listing 
 688 // ---------------------------------------------------------------------------- 
 690 bool wxFTP::GetList(wxArrayString
& files
, 
 691                     const wxString
& wildcard
, 
 694     wxSocketBase 
*sock 
= GetPort(); 
 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 
 703     wxString 
line(details 
? _T("LIST") : _T("NLST")); 
 706         line 
<< _T(' ') << wildcard
; 
 709     if (!CheckCommand(line
, '1')) 
 714     while ( ReadLine(sock
, line
) == wxPROTO_NOERR 
) 
 720     // the file list should be terminated by "226 Transfer complete"" 
 721     if ( !CheckResult('2') ) 
 727 bool wxFTP::FileExists(const wxString
& fileName
) 
 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. 
 734     wxArrayString fileList
; 
 736     if ( GetList(fileList
, fileName
, FALSE
) ) 
 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 
 744         // Here wxFTP::GetList(...) will succeed but it will return an empty 
 746         retval 
= !fileList
.IsEmpty(); 
 752 // ---------------------------------------------------------------------------- 
 754 // ---------------------------------------------------------------------------- 
 756 int wxFTP::GetFileSize(const wxString
& fileName
) 
 758     // return the filesize of the given file if possible 
 759     // return -1 otherwise (predominantly if file doesn't exist 
 764     // Check for existance of file via wxFTP::FileExists(...) 
 765     if ( FileExists(fileName
) ) 
 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? 
 774         TransferMode oldTransfermode 
= m_currentTransfermode
; 
 775         SetTransferMode(BINARY
); 
 776         command 
<< _T("SIZE ") << fileName
; 
 778         bool ok 
= CheckCommand(command
, '2'); 
 782             // The answer should be one line: "213 <filesize>\n" 
 783             // 213 is File Status (STD9) 
 784             // "SIZE" is not described anywhere..? It works on most servers 
 786             if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"), 
 787                           &statuscode
, &filesize
) == 2 ) 
 789                 // We've gotten a good reply. 
 794                 // Something bad happened.. A "2yz" reply with no size 
 800         // Set transfermode back to the original. Only the "SIZE"-command 
 801         // is dependant on transfermode 
 802         if ( oldTransfermode 
!= NONE 
) 
 804             SetTransferMode(oldTransfermode
); 
 807         // this is not a direct else clause.. The size command might return an 
 808         // invalid "2yz" reply 
 811             // The server didn't understand the "SIZE"-command or it 
 812             // returned an invalid reply. 
 813             // We now try to get details for the file with a "LIST"-command 
 814             // and then parse the output from there.. 
 815             wxArrayString fileList
; 
 816             if ( GetList(fileList
, fileName
, TRUE
) ) 
 818                 if ( !fileList
.IsEmpty() ) 
 820                     // We _should_ only get one line in return, but just to be 
 821                     // safe we run through the line(s) returned and look for a 
 822                     // substring containing the name we are looking for. We 
 823                     // stop the iteration at the first occurrence of the 
 824                     // filename. The search is not case-sensitive. 
 825                     bool foundIt 
= FALSE
; 
 828                     for ( i 
= 0; !foundIt 
&& i 
< fileList
.Count(); i
++ ) 
 830                         foundIt 
= fileList
[i
].Upper().Contains(fileName
.Upper()); 
 835                         // The index i points to the first occurrence of 
 836                         // fileName in the array Now we have to find out what 
 837                         // format the LIST has returned. There are two 
 838                         // "schools": Unix-like 
 840                         // '-rw-rw-rw- owner group size month day time filename' 
 844                         // 'date size filename' 
 846                         // check if the first character is '-'. This would 
 847                         // indicate Unix-style (this also limits this function 
 848                         // to searching for files, not directories) 
 849                         if ( fileList
[i
].Mid(0, 1) == _T("-") ) 
 852                             if ( wxSscanf(fileList
[i
].c_str(), 
 853                                           _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"), 
 856                                 // Hmm... Invalid response 
 857                                 wxLogTrace(FTP_TRACE_MASK
, 
 858                                            _T("Invalid LIST response")); 
 861                         else // Windows-style response (?) 
 863                             if ( wxSscanf(fileList
[i
].c_str(), 
 864                                           _T("%*s %*s %i %*s"), 
 867                                 // something bad happened..? 
 868                                 wxLogTrace(FTP_TRACE_MASK
, 
 869                                            _T("Invalid or unknown LIST response")); 
 878     // filesize might still be -1 when exiting 
 882 #endif // wxUSE_PROTOCOL_FTP