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 // ----------------------------------------------------------------------------
189 // Send command to FTP server
190 // ----------------------------------------------------------------------------
192 char wxFTP::SendCommand(const wxString
& command
)
196 m_lastError
= wxPROTO_STREAMING
;
200 wxString tmp_str
= command
+ wxT("\r\n");
201 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
202 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
204 m_lastError
= wxPROTO_NETERR
;
209 // don't show the passwords in the logs (even in debug ones)
210 wxString cmd
, password
;
211 if ( command
.Upper().StartsWith(_T("PASS "), &password
) )
213 cmd
<< _T("PASS ") << wxString(_T('*'), password
.length());
220 wxLogTrace(FTP_TRACE_MASK
, _T("==> %s"), cmd
.c_str());
221 #endif // __WXDEBUG__
226 // ----------------------------------------------------------------------------
227 // Recieve servers reply
228 // ----------------------------------------------------------------------------
230 char wxFTP::GetResult()
232 // if we've already had a read or write timeout error, the connection is
233 // probably toast, so don't bother, it just wastes the users time
234 if ( m_bEncounteredError
)
239 // m_lastResult will contain the entire server response, possibly on
241 m_lastResult
.clear();
243 // we handle multiline replies here according to RFC 959: it says that a
244 // reply may either be on 1 line of the form "xyz ..." or on several lines
245 // in whuch case it looks like
249 // and the intermeidate lines may start with xyz or not
250 bool badReply
= false;
251 bool firstLine
= true;
252 bool endOfReply
= false;
253 while ( !endOfReply
&& !badReply
)
256 m_lastError
= ReadLine(this,line
);
259 m_bEncounteredError
= true;
263 if ( !m_lastResult
.empty() )
265 // separate from last line
266 m_lastResult
+= _T('\n');
269 m_lastResult
+= line
;
271 // unless this is an intermediate line of a multiline reply, it must
272 // contain the code in the beginning and '-' or ' ' following it
273 if ( line
.Len() < LEN_CODE
+ 1 )
281 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
282 code
.c_str(), line
.c_str());
285 else // line has at least 4 chars
287 // this is the char which tells us what we're dealing with
288 wxChar chMarker
= line
.GetChar(LEN_CODE
);
292 code
= wxString(line
, LEN_CODE
);
293 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
294 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
311 else // subsequent line of multiline reply
313 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
315 if ( chMarker
== _T(' ') )
320 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
321 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
325 // just part of reply
326 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
327 code
.c_str(), line
.c_str());
335 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
336 m_lastResult
.c_str());
338 m_lastError
= wxPROTO_PROTERR
;
343 // if we got here we must have a non empty code string
344 return (char)code
[0u];
347 // ----------------------------------------------------------------------------
348 // wxFTP simple commands
349 // ----------------------------------------------------------------------------
351 bool wxFTP::SetTransferMode(TransferMode transferMode
)
353 if ( transferMode
== m_currentTransfermode
)
360 switch ( transferMode
)
363 wxFAIL_MSG(_T("unknown FTP transfer mode"));
375 if ( !DoSimpleCommand(_T("TYPE"), mode
) )
377 wxLogError(_("Failed to set FTP transfer mode to %s."),
378 (transferMode
== ASCII
? _("ASCII") : _("binary")));
383 // If we get here the operation has been successfully completed
384 // Set the status-member
385 m_currentTransfermode
= transferMode
;
390 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
392 wxString fullcmd
= command
;
395 fullcmd
<< _T(' ') << arg
;
398 if ( !CheckCommand(fullcmd
, '2') )
400 wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str());
408 bool wxFTP::ChDir(const wxString
& dir
)
410 // some servers might not understand ".." if they use different directory
411 // tree conventions, but they always understand CDUP - should we use it if
412 // dir == ".."? OTOH, do such servers (still) exist?
414 return DoSimpleCommand(_T("CWD"), dir
);
417 bool wxFTP::MkDir(const wxString
& dir
)
419 return DoSimpleCommand(_T("MKD"), dir
);
422 bool wxFTP::RmDir(const wxString
& dir
)
424 return DoSimpleCommand(_T("RMD"), dir
);
427 wxString
wxFTP::Pwd()
431 if ( CheckCommand(wxT("PWD"), '2') )
433 // the result is at least that long if CheckCommand() succeeded
434 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
437 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
438 wxString(p
, m_lastResult
.end()));
442 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
446 // check if the quote is doubled
448 if ( !*p
|| *p
!= _T('"') )
450 // no, this is the end
453 //else: yes, it is: this is an embedded quote in the
454 // filename, treat as normal char
462 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
463 m_lastResult
.c_str() + LEN_CODE
+ 1);
469 wxLogDebug(_T("FTP PWD command failed."));
475 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
479 str
= wxT("RNFR ") + src
;
480 if ( !CheckCommand(str
, '3') )
483 str
= wxT("RNTO ") + dst
;
485 return CheckCommand(str
, '2');
488 bool wxFTP::RmFile(const wxString
& path
)
491 str
= wxT("DELE ") + path
;
493 return CheckCommand(str
, '2');
496 // ----------------------------------------------------------------------------
497 // wxFTP download and upload
498 // ----------------------------------------------------------------------------
500 class wxInputFTPStream
: public wxSocketInputStream
503 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
504 : wxSocketInputStream(*sock
)
507 // socket timeout automatically set in GetPort function
510 virtual ~wxInputFTPStream()
512 delete m_i_socket
; // keep at top
514 // when checking the result, the stream will
515 // almost always show an error, even if the file was
516 // properly transfered, thus, lets just grab the result
518 // we are looking for "226 transfer completed"
519 char code
= m_ftp
->GetResult();
522 // it was a good transfer.
524 m_ftp
->m_streaming
= false;
530 // the connection is probably toast. issue an abort, and
531 // then a close. there won't be any more waiting
532 // for this connection
537 // There was a problem with the transfer and the server
538 // has acknowledged it. If we issue an "ABORT" now, the user
539 // would get the "226" for the abort and think the xfer was
540 // complete, thus, don't do anything here, just return
545 DECLARE_NO_COPY_CLASS(wxInputFTPStream
)
548 class wxOutputFTPStream
: public wxSocketOutputStream
551 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
552 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
556 virtual ~wxOutputFTPStream(void)
560 // close data connection first, this will generate "transfer
565 m_ftp
->GetResult(); // save result so user can get to it
567 m_ftp
->m_streaming
= false;
571 // abort data connection first
574 // and close it after
581 DECLARE_NO_COPY_CLASS(wxOutputFTPStream
)
584 void wxFTP::SetDefaultTimeout(wxUint32 Value
)
586 m_uiDefaultTimeout
= Value
;
587 SetTimeout(Value
); // sets it for this socket
591 wxSocketBase
*wxFTP::GetPort()
594 PASSIVE: Client sends a "PASV" to the server. The server responds with
595 an address and port number which it will be listening on. Then
596 the client connects to the server at the specified address and
599 ACTIVE: Client sends the server a PORT command which includes an
600 address and port number which the client will be listening on.
601 The server then connects to the client at that address and
605 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
608 m_bEncounteredError
= true;
612 // Now set the time for the new socket to the default or user selected
614 socket
->SetTimeout(m_uiDefaultTimeout
);
619 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
624 // now wait for a connection from server
625 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
626 if ( !sockSrv
->WaitForAccept() )
628 m_lastError
= wxPROTO_CONNERR
;
629 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
635 sock
= sockSrv
->Accept(true);
642 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
643 const wxIPV4address
& addrNew
)
645 // Just fills in the return value with the local IP
646 // address of the current socket. Also it fill in the
647 // PORT which the client will be listening on
649 wxString addrIP
= addrLocal
.IPAddress();
650 int portNew
= addrNew
.Service();
652 // We need to break the PORT number in bytes
653 addrIP
.Replace(_T("."), _T(","));
655 << wxString::Format(_T("%d"), portNew
>> 8) << _T(',')
656 << wxString::Format(_T("%d"), portNew
& 0xff);
658 // Now we have a value like "10,0,0,1,5,23"
662 wxSocketBase
*wxFTP::GetActivePort()
664 // we need an address to listen on
665 wxIPV4address addrNew
, addrLocal
;
667 addrNew
.AnyAddress();
668 addrNew
.Service(0); // pick an open port number.
670 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
673 // We use Ok() here to see if everything is ok
674 m_lastError
= wxPROTO_PROTERR
;
679 //gets the new address, actually it is just the port number
680 sockSrv
->GetLocal(addrNew
);
682 // Now we create the argument of the PORT command, we send in both
683 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
684 // value in addrLocal
685 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
686 if ( !DoSimpleCommand(_T("PORT"), port
) )
688 m_lastError
= wxPROTO_PROTERR
;
690 wxLogError(_("The FTP server doesn't support the PORT command."));
694 sockSrv
->Notify(false); // Don't send any events
698 wxSocketBase
*wxFTP::GetPassivePort()
700 if ( !DoSimpleCommand(_T("PASV")) )
702 wxLogError(_("The FTP server doesn't support passive mode."));
706 size_t addrStart
= m_lastResult
.find(_T('('));
707 size_t addrEnd
= (addrStart
== wxString::npos
)
709 : m_lastResult
.find(_T(')'), addrStart
);
711 if ( addrEnd
== wxString::npos
)
713 m_lastError
= wxPROTO_PROTERR
;
717 // get the port number and address
719 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
720 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
721 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
723 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
724 (wxUint16
)a
[3] << 16 |
725 (wxUint16
)a
[4] << 8 |
727 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
730 addr
.Hostname(hostaddr
);
733 wxSocketClient
*client
= new wxSocketClient();
734 if ( !client
->Connect(addr
) )
740 client
->Notify(false);
751 if ( !CheckCommand(wxT("ABOR"), '4') )
754 return CheckResult('2');
757 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
759 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
762 wxSocketBase
*sock
= GetPort();
766 m_lastError
= wxPROTO_NETERR
;
770 wxString tmp_str
= wxT("RETR ") + wxURI::Unescape(path
);
771 if ( !CheckCommand(tmp_str
, '1') )
774 sock
= AcceptIfActive(sock
);
778 sock
->SetFlags(wxSOCKET_WAITALL
);
782 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
787 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
789 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
792 wxSocketBase
*sock
= GetPort();
794 wxString tmp_str
= wxT("STOR ") + path
;
795 if ( !CheckCommand(tmp_str
, '1') )
798 sock
= AcceptIfActive(sock
);
802 return new wxOutputFTPStream(this, sock
);
805 // ----------------------------------------------------------------------------
806 // FTP directory listing
807 // ----------------------------------------------------------------------------
809 bool wxFTP::GetList(wxArrayString
& files
,
810 const wxString
& wildcard
,
813 wxSocketBase
*sock
= GetPort();
817 // NLST : List of Filenames (including Directory's !)
818 // LIST : depending on BS of FTP-Server
819 // - Unix : result like "ls" command
820 // - Windows : like "dir" command
822 wxString
line(details
? _T("LIST") : _T("NLST"));
823 if ( !wildcard
.empty() )
825 line
<< _T(' ') << wildcard
;
828 if ( !CheckCommand(line
, '1') )
830 m_lastError
= wxPROTO_PROTERR
;
831 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
836 sock
= AcceptIfActive(sock
);
841 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
848 // the file list should be terminated by "226 Transfer complete""
849 return CheckResult('2');
852 bool wxFTP::FileExists(const wxString
& fileName
)
854 // This function checks if the file specified in fileName exists in the
855 // current dir. It does so by simply doing an NLST (via GetList).
856 // If this succeeds (and the list is not empty) the file exists.
859 wxArrayString fileList
;
861 if ( GetList(fileList
, fileName
, false) )
863 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
864 // displays this behaviour when queried on a nonexistent file:
865 // NLST this_file_does_not_exist
866 // 150 Opening ASCII data connection for directory listing
867 // (no data transferred)
868 // 226 Transfer complete
869 // Here wxFTP::GetList(...) will succeed but it will return an empty
871 retval
= !fileList
.IsEmpty();
877 // ----------------------------------------------------------------------------
879 // ----------------------------------------------------------------------------
881 int wxFTP::GetFileSize(const wxString
& fileName
)
883 // return the filesize of the given file if possible
884 // return -1 otherwise (predominantly if file doesn't exist
889 // Check for existance of file via wxFTP::FileExists(...)
890 if ( FileExists(fileName
) )
894 // First try "SIZE" command using BINARY(IMAGE) transfermode
895 // Especially UNIX ftp-servers distinguish between the different
896 // transfermodes and reports different filesizes accordingly.
897 // The BINARY size is the interesting one: How much memory
898 // will we need to hold this file?
899 TransferMode oldTransfermode
= m_currentTransfermode
;
900 SetTransferMode(BINARY
);
901 command
<< _T("SIZE ") << fileName
;
903 bool ok
= CheckCommand(command
, '2');
907 // The answer should be one line: "213 <filesize>\n"
908 // 213 is File Status (STD9)
909 // "SIZE" is not described anywhere..? It works on most servers
911 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
912 &statuscode
, &filesize
) == 2 )
914 // We've gotten a good reply.
919 // Something bad happened.. A "2yz" reply with no size
925 // Set transfermode back to the original. Only the "SIZE"-command
926 // is dependant on transfermode
927 if ( oldTransfermode
!= NONE
)
929 SetTransferMode(oldTransfermode
);
932 // this is not a direct else clause.. The size command might return an
933 // invalid "2yz" reply
936 // The server didn't understand the "SIZE"-command or it
937 // returned an invalid reply.
938 // We now try to get details for the file with a "LIST"-command
939 // and then parse the output from there..
940 wxArrayString fileList
;
941 if ( GetList(fileList
, fileName
, true) )
943 if ( !fileList
.IsEmpty() )
945 // We _should_ only get one line in return, but just to be
946 // safe we run through the line(s) returned and look for a
947 // substring containing the name we are looking for. We
948 // stop the iteration at the first occurrence of the
949 // filename. The search is not case-sensitive.
950 bool foundIt
= false;
953 for ( i
= 0; !foundIt
&& i
< fileList
.GetCount(); i
++ )
955 foundIt
= fileList
[i
].Upper().Contains(fileName
.Upper());
960 // The index i points to the first occurrence of
961 // fileName in the array Now we have to find out what
962 // format the LIST has returned. There are two
963 // "schools": Unix-like
965 // '-rw-rw-rw- owner group size month day time filename'
969 // 'date size filename'
971 // check if the first character is '-'. This would
972 // indicate Unix-style (this also limits this function
973 // to searching for files, not directories)
974 if ( fileList
[i
].Mid(0, 1) == _T("-") )
977 if ( wxSscanf(fileList
[i
].c_str(),
978 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
981 // Hmm... Invalid response
982 wxLogTrace(FTP_TRACE_MASK
,
983 _T("Invalid LIST response"));
986 else // Windows-style response (?)
988 if ( wxSscanf(fileList
[i
].c_str(),
989 _T("%*s %*s %i %*s"),
992 // something bad happened..?
993 wxLogTrace(FTP_TRACE_MASK
,
994 _T("Invalid or unknown LIST response"));
1003 // filesize might still be -1 when exiting
1007 #endif // wxUSE_PROTOCOL_FTP