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 // ----------------------------------------------------------------------------
80 m_currentTransfermode
= NONE
;
82 m_username
= wxT("anonymous");
83 m_password
<< wxGetUserId() << wxT('@') << wxGetFullHostName();
86 SetFlags(wxSOCKET_NOWAIT
);
88 m_bEncounteredError
= false;
95 // if we are streaming, this will issue
96 // an FTP ABORT command, to tell the server we are aborting
100 // now this issues a "QUIT" command to tell the server we are
104 // ----------------------------------------------------------------------------
105 // wxFTP connect and login methods
106 // ----------------------------------------------------------------------------
108 bool wxFTP::Connect(const wxSockAddress
& addr
, bool WXUNUSED(wait
))
110 if ( !wxProtocol::Connect(addr
) )
112 m_lastError
= wxPROTO_NETERR
;
118 m_lastError
= wxPROTO_CONNERR
;
122 // we should have 220 welcome message
123 if ( !CheckResult('2') )
130 command
.Printf(wxT("USER %s"), m_username
.c_str());
131 char rc
= SendCommand(command
);
134 // 230 return: user accepted without password
135 m_lastError
= wxPROTO_NOERR
;
141 m_lastError
= wxPROTO_CONNERR
;
146 command
.Printf(wxT("PASS %s"), m_password
.c_str());
147 if ( !CheckCommand(command
, '2') )
149 m_lastError
= wxPROTO_CONNERR
;
154 m_lastError
= wxPROTO_NOERR
;
158 bool wxFTP::Connect(const wxString
& host
)
162 addr
.Service(wxT("ftp"));
164 return Connect(addr
);
171 m_lastError
= wxPROTO_STREAMING
;
177 if ( !CheckCommand(wxT("QUIT"), '2') )
179 m_lastError
= wxPROTO_CONNERR
;
180 wxLogDebug(_T("Failed to close connection gracefully."));
184 return wxSocketClient::Close();
187 // ============================================================================
189 // ============================================================================
191 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
196 // now wait for a connection from server
197 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
198 if ( !sockSrv
->WaitForAccept() )
200 m_lastError
= wxPROTO_CONNERR
;
201 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
207 m_lastError
= wxPROTO_NOERR
;
208 sock
= sockSrv
->Accept(true);
221 if ( !CheckCommand(wxT("ABOR"), '4') )
224 return CheckResult('2');
228 // ----------------------------------------------------------------------------
229 // Send command to FTP server
230 // ----------------------------------------------------------------------------
232 char wxFTP::SendCommand(const wxString
& command
)
236 m_lastError
= wxPROTO_STREAMING
;
240 wxString tmp_str
= command
+ wxT("\r\n");
241 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
242 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
244 m_lastError
= wxPROTO_NETERR
;
249 // don't show the passwords in the logs (even in debug ones)
250 wxString cmd
, password
;
251 if ( command
.Upper().StartsWith(_T("PASS "), &password
) )
253 cmd
<< _T("PASS ") << wxString(_T('*'), password
.length());
261 #endif // __WXDEBUG__
263 m_lastError
= wxPROTO_NOERR
;
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;
306 if ( !m_lastResult
.empty() )
308 // separate from last line
309 m_lastResult
+= _T('\n');
312 m_lastResult
+= line
;
314 // unless this is an intermediate line of a multiline reply, it must
315 // contain the code in the beginning and '-' or ' ' following it
316 if ( line
.Len() < LEN_CODE
+ 1 )
323 else // line has at least 4 chars
325 // this is the char which tells us what we're dealing with
326 wxChar chMarker
= line
.GetChar(LEN_CODE
);
330 code
= wxString(line
, LEN_CODE
);
347 else // subsequent line of multiline reply
349 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
351 if ( chMarker
== _T(' ') )
362 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
363 m_lastResult
.c_str());
365 m_lastError
= wxPROTO_PROTERR
;
370 m_lastError
= wxPROTO_NOERR
;
372 // if we got here we must have a non empty code string
373 return (char)code
[0u];
376 // ----------------------------------------------------------------------------
377 // wxFTP simple commands
378 // ----------------------------------------------------------------------------
380 bool wxFTP::SetTransferMode(TransferMode transferMode
)
382 if ( transferMode
== m_currentTransfermode
)
389 switch ( transferMode
)
392 wxFAIL_MSG(_T("unknown FTP transfer mode"));
404 if ( !DoSimpleCommand(_T("TYPE"), mode
) )
406 wxLogError(_("Failed to set FTP transfer mode to %s."),
407 (transferMode
== ASCII
? _("ASCII") : _("binary")));
412 // If we get here the operation has been successfully completed
413 // Set the status-member
414 m_currentTransfermode
= transferMode
;
419 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
421 wxString fullcmd
= command
;
424 fullcmd
<< _T(' ') << arg
;
427 if ( !CheckCommand(fullcmd
, '2') )
429 wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str());
430 m_lastError
= wxPROTO_NETERR
;
435 m_lastError
= wxPROTO_NOERR
;
439 bool wxFTP::ChDir(const wxString
& dir
)
441 // some servers might not understand ".." if they use different directory
442 // tree conventions, but they always understand CDUP - should we use it if
443 // dir == ".."? OTOH, do such servers (still) exist?
445 return DoSimpleCommand(_T("CWD"), dir
);
448 bool wxFTP::MkDir(const wxString
& dir
)
450 return DoSimpleCommand(_T("MKD"), dir
);
453 bool wxFTP::RmDir(const wxString
& dir
)
455 return DoSimpleCommand(_T("RMD"), dir
);
458 wxString
wxFTP::Pwd()
462 if ( CheckCommand(wxT("PWD"), '2') )
464 // the result is at least that long if CheckCommand() succeeded
465 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
468 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
469 wxString(p
, m_lastResult
.end()));
473 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
477 // check if the quote is doubled
479 if ( !*p
|| *p
!= _T('"') )
481 // no, this is the end
484 //else: yes, it is: this is an embedded quote in the
485 // filename, treat as normal char
493 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
494 m_lastResult
.c_str() + LEN_CODE
+ 1);
500 m_lastError
= wxPROTO_PROTERR
;
501 wxLogDebug(_T("FTP PWD command failed."));
507 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
511 str
= wxT("RNFR ") + src
;
512 if ( !CheckCommand(str
, '3') )
515 str
= wxT("RNTO ") + dst
;
517 return CheckCommand(str
, '2');
520 bool wxFTP::RmFile(const wxString
& path
)
523 str
= wxT("DELE ") + path
;
525 return CheckCommand(str
, '2');
528 // ----------------------------------------------------------------------------
529 // wxFTP port methods
530 // ----------------------------------------------------------------------------
532 wxSocketBase
*wxFTP::GetPort()
535 PASSIVE: Client sends a "PASV" to the server. The server responds with
536 an address and port number which it will be listening on. Then
537 the client connects to the server at the specified address and
540 ACTIVE: Client sends the server a PORT command which includes an
541 address and port number which the client will be listening on.
542 The server then connects to the client at that address and
546 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
549 m_bEncounteredError
= true;
553 // Now set the time for the new socket to the default or user selected
555 socket
->SetTimeout(m_uiDefaultTimeout
);
560 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
561 const wxIPV4address
& addrNew
)
563 // Just fills in the return value with the local IP
564 // address of the current socket. Also it fill in the
565 // PORT which the client will be listening on
567 wxString addrIP
= addrLocal
.IPAddress();
568 int portNew
= addrNew
.Service();
570 // We need to break the PORT number in bytes
571 addrIP
.Replace(_T("."), _T(","));
573 << wxString::Format(_T("%d"), portNew
>> 8) << _T(',')
574 << wxString::Format(_T("%d"), portNew
& 0xff);
576 // Now we have a value like "10,0,0,1,5,23"
580 wxSocketBase
*wxFTP::GetActivePort()
582 // we need an address to listen on
583 wxIPV4address addrNew
, addrLocal
;
585 addrNew
.AnyAddress();
586 addrNew
.Service(0); // pick an open port number.
588 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
591 // We use Ok() here to see if everything is ok
592 m_lastError
= wxPROTO_PROTERR
;
597 //gets the new address, actually it is just the port number
598 sockSrv
->GetLocal(addrNew
);
600 // Now we create the argument of the PORT command, we send in both
601 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
602 // value in addrLocal
603 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
604 if ( !DoSimpleCommand(_T("PORT"), port
) )
606 m_lastError
= wxPROTO_PROTERR
;
608 wxLogError(_("The FTP server doesn't support the PORT command."));
612 m_lastError
= wxPROTO_NOERR
;
613 sockSrv
->Notify(false); // Don't send any events
617 wxSocketBase
*wxFTP::GetPassivePort()
619 if ( !DoSimpleCommand(_T("PASV")) )
621 m_lastError
= wxPROTO_PROTERR
;
622 wxLogError(_("The FTP server doesn't support passive mode."));
626 size_t addrStart
= m_lastResult
.find(_T('('));
627 size_t addrEnd
= (addrStart
== wxString::npos
)
629 : m_lastResult
.find(_T(')'), addrStart
);
631 if ( addrEnd
== wxString::npos
)
633 m_lastError
= wxPROTO_PROTERR
;
637 // get the port number and address
639 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
640 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
641 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
643 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
644 (wxUint16
)a
[3] << 16 |
645 (wxUint16
)a
[4] << 8 |
647 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
650 addr
.Hostname(hostaddr
);
653 wxSocketClient
*client
= new wxSocketClient();
654 if ( !client
->Connect(addr
) )
656 m_lastError
= wxPROTO_CONNERR
;
661 client
->Notify(false);
663 m_lastError
= wxPROTO_NOERR
;
668 // ----------------------------------------------------------------------------
669 // wxFTP download and upload
670 // ----------------------------------------------------------------------------
672 class wxInputFTPStream
: public wxSocketInputStream
675 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
676 : wxSocketInputStream(*sock
)
679 // socket timeout automatically set in GetPort function
682 virtual ~wxInputFTPStream()
684 delete m_i_socket
; // keep at top
686 // when checking the result, the stream will
687 // almost always show an error, even if the file was
688 // properly transfered, thus, lets just grab the result
690 // we are looking for "226 transfer completed"
691 char code
= m_ftp
->GetResult();
694 // it was a good transfer.
696 m_ftp
->m_streaming
= false;
702 // the connection is probably toast. issue an abort, and
703 // then a close. there won't be any more waiting
704 // for this connection
709 // There was a problem with the transfer and the server
710 // has acknowledged it. If we issue an "ABORT" now, the user
711 // would get the "226" for the abort and think the xfer was
712 // complete, thus, don't do anything here, just return
717 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream
);
720 class wxOutputFTPStream
: public wxSocketOutputStream
723 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
724 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
728 virtual ~wxOutputFTPStream(void)
732 // close data connection first, this will generate "transfer
737 m_ftp
->GetResult(); // save result so user can get to it
739 m_ftp
->m_streaming
= false;
743 // abort data connection first
746 // and close it after
753 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream
);
756 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
758 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
760 m_lastError
= wxPROTO_CONNERR
;
764 wxSocketBase
*sock
= GetPort();
768 m_lastError
= wxPROTO_NETERR
;
772 wxString tmp_str
= wxT("RETR ") + wxURI::Unescape(path
);
773 if ( !CheckCommand(tmp_str
, '1') )
776 sock
= AcceptIfActive(sock
);
779 m_lastError
= wxPROTO_CONNERR
;
783 sock
->SetFlags(wxSOCKET_WAITALL
);
787 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
789 m_lastError
= wxPROTO_NOERR
;
793 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
795 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
797 m_lastError
= wxPROTO_CONNERR
;
801 wxSocketBase
*sock
= GetPort();
803 wxString tmp_str
= wxT("STOR ") + path
;
804 if ( !CheckCommand(tmp_str
, '1') )
807 sock
= AcceptIfActive(sock
);
811 m_lastError
= wxPROTO_NOERR
;
812 return new wxOutputFTPStream(this, sock
);
815 // ----------------------------------------------------------------------------
816 // FTP directory listing
817 // ----------------------------------------------------------------------------
819 bool wxFTP::GetList(wxArrayString
& files
,
820 const wxString
& wildcard
,
823 wxSocketBase
*sock
= GetPort();
825 m_lastError
= wxPROTO_NETERR
;
829 // NLST : List of Filenames (including Directory's !)
830 // LIST : depending on BS of FTP-Server
831 // - Unix : result like "ls" command
832 // - Windows : like "dir" command
834 wxString
line(details
? _T("LIST") : _T("NLST"));
835 if ( !wildcard
.empty() )
837 line
<< _T(' ') << wildcard
;
840 if ( !CheckCommand(line
, '1') )
842 m_lastError
= wxPROTO_PROTERR
;
843 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
848 sock
= AcceptIfActive(sock
);
850 m_lastError
= wxPROTO_CONNERR
;
855 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
862 // the file list should be terminated by "226 Transfer complete""
863 m_lastError
= wxPROTO_NOERR
;
864 return CheckResult('2');
867 bool wxFTP::FileExists(const wxString
& fileName
)
869 // This function checks if the file specified in fileName exists in the
870 // current dir. It does so by simply doing an NLST (via GetList).
871 // If this succeeds (and the list is not empty) the file exists.
874 wxArrayString fileList
;
876 if ( GetList(fileList
, fileName
, false) )
878 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
879 // displays this behaviour when queried on a nonexistent file:
880 // NLST this_file_does_not_exist
881 // 150 Opening ASCII data connection for directory listing
882 // (no data transferred)
883 // 226 Transfer complete
884 // Here wxFTP::GetList(...) will succeed but it will return an empty
886 retval
= !fileList
.IsEmpty();
892 // ----------------------------------------------------------------------------
894 // ----------------------------------------------------------------------------
896 int wxFTP::GetFileSize(const wxString
& fileName
)
898 // return the filesize of the given file if possible
899 // return -1 otherwise (predominantly if file doesn't exist
904 // Check for existance of file via wxFTP::FileExists(...)
905 if ( FileExists(fileName
) )
909 // First try "SIZE" command using BINARY(IMAGE) transfermode
910 // Especially UNIX ftp-servers distinguish between the different
911 // transfermodes and reports different filesizes accordingly.
912 // The BINARY size is the interesting one: How much memory
913 // will we need to hold this file?
914 TransferMode oldTransfermode
= m_currentTransfermode
;
915 SetTransferMode(BINARY
);
916 command
<< _T("SIZE ") << fileName
;
918 bool ok
= CheckCommand(command
, '2');
922 // The answer should be one line: "213 <filesize>\n"
923 // 213 is File Status (STD9)
924 // "SIZE" is not described anywhere..? It works on most servers
926 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
927 &statuscode
, &filesize
) == 2 )
929 // We've gotten a good reply.
934 // Something bad happened.. A "2yz" reply with no size
940 // Set transfermode back to the original. Only the "SIZE"-command
941 // is dependant on transfermode
942 if ( oldTransfermode
!= NONE
)
944 SetTransferMode(oldTransfermode
);
947 // this is not a direct else clause.. The size command might return an
948 // invalid "2yz" reply
951 // The server didn't understand the "SIZE"-command or it
952 // returned an invalid reply.
953 // We now try to get details for the file with a "LIST"-command
954 // and then parse the output from there..
955 wxArrayString fileList
;
956 if ( GetList(fileList
, fileName
, true) )
958 if ( !fileList
.IsEmpty() )
960 // We _should_ only get one line in return, but just to be
961 // safe we run through the line(s) returned and look for a
962 // substring containing the name we are looking for. We
963 // stop the iteration at the first occurrence of the
964 // filename. The search is not case-sensitive.
965 bool foundIt
= false;
968 for ( i
= 0; !foundIt
&& i
< fileList
.GetCount(); i
++ )
970 foundIt
= fileList
[i
].Upper().Contains(fileName
.Upper());
975 // The index i points to the first occurrence of
976 // fileName in the array Now we have to find out what
977 // format the LIST has returned. There are two
978 // "schools": Unix-like
980 // '-rw-rw-rw- owner group size month day time filename'
984 // 'date size filename'
986 // check if the first character is '-'. This would
987 // indicate Unix-style (this also limits this function
988 // to searching for files, not directories)
989 if ( fileList
[i
].Mid(0, 1) == _T("-") )
992 if ( wxSscanf(fileList
[i
].c_str(),
993 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
996 // Hmm... Invalid response
997 wxLogDebug(wxT("Invalid LIST response"));
1000 else // Windows-style response (?)
1002 if ( wxSscanf(fileList
[i
].c_str(),
1003 _T("%*s %*s %i %*s"),
1006 // something bad happened..?
1007 wxLogDebug(wxT("Invalid or unknown LIST response"));
1016 // filesize might still be -1 when exiting
1020 #endif // wxUSE_PROTOCOL_FTP