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"
51 #if defined(__WXMAC__)
52 #include "wx/mac/macsock.h"
59 // ----------------------------------------------------------------------------
61 // ----------------------------------------------------------------------------
63 // the length of FTP status code (3 digits)
64 static const size_t LEN_CODE
= 3;
66 // ----------------------------------------------------------------------------
68 // ----------------------------------------------------------------------------
70 IMPLEMENT_DYNAMIC_CLASS(wxFTP
, wxProtocol
)
71 IMPLEMENT_PROTOCOL(wxFTP
, wxT("ftp"), wxT("ftp"), true)
73 // ============================================================================
75 // ============================================================================
77 // ----------------------------------------------------------------------------
78 // wxFTP constructor and destructor
79 // ----------------------------------------------------------------------------
83 m_lastError
= wxPROTO_NOERR
;
85 m_currentTransfermode
= NONE
;
87 m_user
= wxT("anonymous");
88 m_passwd
<< wxGetUserId() << wxT('@') << wxGetFullHostName();
91 SetFlags(wxSOCKET_NOWAIT
);
93 SetDefaultTimeout(60); // Default is Sixty Seconds
94 m_bEncounteredError
= false;
101 // if we are streaming, this will issue
102 // an FTP ABORT command, to tell the server we are aborting
106 // now this issues a "QUIT" command to tell the server we are
110 // ----------------------------------------------------------------------------
111 // wxFTP connect and login methods
112 // ----------------------------------------------------------------------------
114 bool wxFTP::Connect(wxSockAddress
& addr
, bool WXUNUSED(wait
))
116 if ( !wxProtocol::Connect(addr
) )
118 m_lastError
= wxPROTO_NETERR
;
124 m_lastError
= wxPROTO_CONNERR
;
128 // we should have 220 welcome message
129 if ( !CheckResult('2') )
136 command
.Printf(wxT("USER %s"), m_user
.c_str());
137 char rc
= SendCommand(command
);
140 // 230 return: user accepted without password
150 command
.Printf(wxT("PASS %s"), m_passwd
.c_str());
151 if ( !CheckCommand(command
, '2') )
160 bool wxFTP::Connect(const wxString
& host
)
164 addr
.Service(wxT("ftp"));
166 return Connect(addr
);
173 m_lastError
= wxPROTO_STREAMING
;
179 if ( !CheckCommand(wxT("QUIT"), '2') )
181 wxLogDebug(_T("Failed to close connection gracefully."));
185 return wxSocketClient::Close();
188 // ============================================================================
190 // ============================================================================
192 // ----------------------------------------------------------------------------
193 // Send command to FTP server
194 // ----------------------------------------------------------------------------
196 char wxFTP::SendCommand(const wxString
& command
)
200 m_lastError
= wxPROTO_STREAMING
;
204 wxString tmp_str
= command
+ wxT("\r\n");
205 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
206 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
208 m_lastError
= wxPROTO_NETERR
;
213 // don't show the passwords in the logs (even in debug ones)
214 wxString cmd
, password
;
215 if ( command
.Upper().StartsWith(_T("PASS "), &password
) )
217 cmd
<< _T("PASS ") << wxString(_T('*'), password
.length());
224 wxLogTrace(FTP_TRACE_MASK
, _T("==> %s"), cmd
.c_str());
225 #endif // __WXDEBUG__
230 // ----------------------------------------------------------------------------
231 // Recieve servers reply
232 // ----------------------------------------------------------------------------
234 char wxFTP::GetResult()
236 // if we've already had a read or write timeout error, the connection is
237 // probably toast, so don't bother, it just wastes the users time
238 if ( m_bEncounteredError
)
243 // m_lastResult will contain the entire server response, possibly on
245 m_lastResult
.clear();
247 // we handle multiline replies here according to RFC 959: it says that a
248 // reply may either be on 1 line of the form "xyz ..." or on several lines
249 // in whuch case it looks like
253 // and the intermeidate lines may start with xyz or not
254 bool badReply
= false;
255 bool firstLine
= true;
256 bool endOfReply
= false;
257 while ( !endOfReply
&& !badReply
)
260 m_lastError
= ReadLine(this,line
);
263 m_bEncounteredError
= true;
267 if ( !m_lastResult
.empty() )
269 // separate from last line
270 m_lastResult
+= _T('\n');
273 m_lastResult
+= line
;
275 // unless this is an intermediate line of a multiline reply, it must
276 // contain the code in the beginning and '-' or ' ' following it
277 if ( line
.Len() < LEN_CODE
+ 1 )
285 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
286 code
.c_str(), line
.c_str());
289 else // line has at least 4 chars
291 // this is the char which tells us what we're dealing with
292 wxChar chMarker
= line
.GetChar(LEN_CODE
);
296 code
= wxString(line
, LEN_CODE
);
297 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
298 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
315 else // subsequent line of multiline reply
317 if ( wxStrncmp(line
, code
, LEN_CODE
) == 0 )
319 if ( chMarker
== _T(' ') )
324 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
325 code
.c_str(), line
.c_str() + LEN_CODE
+ 1);
329 // just part of reply
330 wxLogTrace(FTP_TRACE_MASK
, _T("<== %s %s"),
331 code
.c_str(), line
.c_str());
339 wxLogDebug(_T("Broken FTP server: '%s' is not a valid reply."),
340 m_lastResult
.c_str());
342 m_lastError
= wxPROTO_PROTERR
;
347 // if we got here we must have a non empty code string
348 return (char)code
[0u];
351 // ----------------------------------------------------------------------------
352 // wxFTP simple commands
353 // ----------------------------------------------------------------------------
355 bool wxFTP::SetTransferMode(TransferMode transferMode
)
357 if ( transferMode
== m_currentTransfermode
)
364 switch ( transferMode
)
367 wxFAIL_MSG(_T("unknown FTP transfer mode"));
379 if ( !DoSimpleCommand(_T("TYPE"), mode
) )
381 wxLogError(_("Failed to set FTP transfer mode to %s."), (const wxChar
*)
382 (transferMode
== ASCII
? _("ASCII") : _("binary")));
387 // If we get here the operation has been successfully completed
388 // Set the status-member
389 m_currentTransfermode
= transferMode
;
394 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
396 wxString fullcmd
= command
;
399 fullcmd
<< _T(' ') << arg
;
402 if ( !CheckCommand(fullcmd
, '2') )
404 wxLogDebug(_T("FTP command '%s' failed."), fullcmd
.c_str());
412 bool wxFTP::ChDir(const wxString
& dir
)
414 // some servers might not understand ".." if they use different directory
415 // tree conventions, but they always understand CDUP - should we use it if
416 // dir == ".."? OTOH, do such servers (still) exist?
418 return DoSimpleCommand(_T("CWD"), dir
);
421 bool wxFTP::MkDir(const wxString
& dir
)
423 return DoSimpleCommand(_T("MKD"), dir
);
426 bool wxFTP::RmDir(const wxString
& dir
)
428 return DoSimpleCommand(_T("RMD"), dir
);
431 wxString
wxFTP::Pwd()
435 if ( CheckCommand(wxT("PWD"), '2') )
437 // the result is at least that long if CheckCommand() succeeded
438 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
441 wxLogDebug(_T("Missing starting quote in reply for PWD: %s"),
442 wxString(p
, m_lastResult
.end()));
446 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
450 // check if the quote is doubled
452 if ( !*p
|| *p
!= _T('"') )
454 // no, this is the end
457 //else: yes, it is: this is an embedded quote in the
458 // filename, treat as normal char
466 wxLogDebug(_T("Missing ending quote in reply for PWD: %s"),
467 m_lastResult
.c_str() + LEN_CODE
+ 1);
473 wxLogDebug(_T("FTP PWD command failed."));
479 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
483 str
= wxT("RNFR ") + src
;
484 if ( !CheckCommand(str
, '3') )
487 str
= wxT("RNTO ") + dst
;
489 return CheckCommand(str
, '2');
492 bool wxFTP::RmFile(const wxString
& path
)
495 str
= wxT("DELE ") + path
;
497 return CheckCommand(str
, '2');
500 // ----------------------------------------------------------------------------
501 // wxFTP download and upload
502 // ----------------------------------------------------------------------------
504 class wxInputFTPStream
: public wxSocketInputStream
507 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
508 : wxSocketInputStream(*sock
)
511 // socket timeout automatically set in GetPort function
514 virtual ~wxInputFTPStream()
516 delete m_i_socket
; // keep at top
518 // when checking the result, the stream will
519 // almost always show an error, even if the file was
520 // properly transfered, thus, lets just grab the result
522 // we are looking for "226 transfer completed"
523 char code
= m_ftp
->GetResult();
526 // it was a good transfer.
528 m_ftp
->m_streaming
= false;
534 // the connection is probably toast. issue an abort, and
535 // then a close. there won't be any more waiting
536 // for this connection
541 // There was a problem with the transfer and the server
542 // has acknowledged it. If we issue an "ABORT" now, the user
543 // would get the "226" for the abort and think the xfer was
544 // complete, thus, don't do anything here, just return
549 DECLARE_NO_COPY_CLASS(wxInputFTPStream
)
552 class wxOutputFTPStream
: public wxSocketOutputStream
555 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
556 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
560 virtual ~wxOutputFTPStream(void)
564 // close data connection first, this will generate "transfer
569 m_ftp
->GetResult(); // save result so user can get to it
571 m_ftp
->m_streaming
= false;
575 // abort data connection first
578 // and close it after
585 DECLARE_NO_COPY_CLASS(wxOutputFTPStream
)
588 void wxFTP::SetDefaultTimeout(wxUint32 Value
)
590 m_uiDefaultTimeout
= Value
;
591 SetTimeout(Value
); // sets it for this socket
595 wxSocketBase
*wxFTP::GetPort()
598 PASSIVE: Client sends a "PASV" to the server. The server responds with
599 an address and port number which it will be listening on. Then
600 the client connects to the server at the specified address and
603 ACTIVE: Client sends the server a PORT command which includes an
604 address and port number which the client will be listening on.
605 The server then connects to the client at that address and
609 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
612 m_bEncounteredError
= true;
616 // Now set the time for the new socket to the default or user selected
618 socket
->SetTimeout(m_uiDefaultTimeout
);
623 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
628 // now wait for a connection from server
629 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
630 if ( !sockSrv
->WaitForAccept() )
632 m_lastError
= wxPROTO_CONNERR
;
633 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
639 sock
= sockSrv
->Accept(true);
646 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
647 const wxIPV4address
& addrNew
)
649 // Just fills in the return value with the local IP
650 // address of the current socket. Also it fill in the
651 // PORT which the client will be listening on
653 wxString addrIP
= addrLocal
.IPAddress();
654 int portNew
= addrNew
.Service();
656 // We need to break the PORT number in bytes
657 addrIP
.Replace(_T("."), _T(","));
659 << wxString::Format(_T("%d"), portNew
>> 8) << _T(',')
660 << wxString::Format(_T("%d"), portNew
& 0xff);
662 // Now we have a value like "10,0,0,1,5,23"
666 wxSocketBase
*wxFTP::GetActivePort()
668 // we need an address to listen on
669 wxIPV4address addrNew
, addrLocal
;
671 addrNew
.AnyAddress();
672 addrNew
.Service(0); // pick an open port number.
674 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
677 // We use Ok() here to see if everything is ok
678 m_lastError
= wxPROTO_PROTERR
;
683 //gets the new address, actually it is just the port number
684 sockSrv
->GetLocal(addrNew
);
686 // Now we create the argument of the PORT command, we send in both
687 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
688 // value in addrLocal
689 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
690 if ( !DoSimpleCommand(_T("PORT "), port
) )
692 m_lastError
= wxPROTO_PROTERR
;
694 wxLogError(_("The FTP server doesn't support the PORT command."));
698 sockSrv
->Notify(false); // Don't send any events
702 wxSocketBase
*wxFTP::GetPassivePort()
704 if ( !DoSimpleCommand(_T("PASV")) )
706 wxLogError(_("The FTP server doesn't support passive mode."));
710 const wxChar
*addrStart
= wxStrchr(m_lastResult
, _T('('));
711 const wxChar
*addrEnd
= addrStart
? wxStrchr(addrStart
, _T(')')) : NULL
;
714 m_lastError
= wxPROTO_PROTERR
;
719 // get the port number and address
721 wxString
straddr(addrStart
+ 1, addrEnd
);
722 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
723 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
725 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
726 (wxUint16
)a
[3] << 16 |
727 (wxUint16
)a
[4] << 8 |
729 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
732 addr
.Hostname(hostaddr
);
735 wxSocketClient
*client
= new wxSocketClient();
736 if ( !client
->Connect(addr
) )
742 client
->Notify(false);
753 if ( !CheckCommand(wxT("ABOR"), '4') )
756 return CheckResult('2');
759 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
761 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
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
);
780 sock
->SetFlags(wxSOCKET_WAITALL
);
784 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
789 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
791 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
794 wxSocketBase
*sock
= GetPort();
796 wxString tmp_str
= wxT("STOR ") + path
;
797 if ( !CheckCommand(tmp_str
, '1') )
800 sock
= AcceptIfActive(sock
);
804 return new wxOutputFTPStream(this, sock
);
807 // ----------------------------------------------------------------------------
808 // FTP directory listing
809 // ----------------------------------------------------------------------------
811 bool wxFTP::GetList(wxArrayString
& files
,
812 const wxString
& wildcard
,
815 wxSocketBase
*sock
= GetPort();
819 // NLST : List of Filenames (including Directory's !)
820 // LIST : depending on BS of FTP-Server
821 // - Unix : result like "ls" command
822 // - Windows : like "dir" command
824 wxString
line(details
? _T("LIST") : _T("NLST"));
825 if ( !wildcard
.empty() )
827 line
<< _T(' ') << wildcard
;
830 if ( !CheckCommand(line
, '1') )
832 m_lastError
= wxPROTO_PROTERR
;
833 wxLogDebug(_T("FTP 'LIST' command returned unexpected result from server"));
838 sock
= AcceptIfActive(sock
);
843 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
850 // the file list should be terminated by "226 Transfer complete""
851 return CheckResult('2');
854 bool wxFTP::FileExists(const wxString
& fileName
)
856 // This function checks if the file specified in fileName exists in the
857 // current dir. It does so by simply doing an NLST (via GetList).
858 // If this succeeds (and the list is not empty) the file exists.
861 wxArrayString fileList
;
863 if ( GetList(fileList
, fileName
, false) )
865 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
866 // displays this behaviour when queried on a nonexistent file:
867 // NLST this_file_does_not_exist
868 // 150 Opening ASCII data connection for directory listing
869 // (no data transferred)
870 // 226 Transfer complete
871 // Here wxFTP::GetList(...) will succeed but it will return an empty
873 retval
= !fileList
.IsEmpty();
879 // ----------------------------------------------------------------------------
881 // ----------------------------------------------------------------------------
883 int wxFTP::GetFileSize(const wxString
& fileName
)
885 // return the filesize of the given file if possible
886 // return -1 otherwise (predominantly if file doesn't exist
891 // Check for existance of file via wxFTP::FileExists(...)
892 if ( FileExists(fileName
) )
896 // First try "SIZE" command using BINARY(IMAGE) transfermode
897 // Especially UNIX ftp-servers distinguish between the different
898 // transfermodes and reports different filesizes accordingly.
899 // The BINARY size is the interesting one: How much memory
900 // will we need to hold this file?
901 TransferMode oldTransfermode
= m_currentTransfermode
;
902 SetTransferMode(BINARY
);
903 command
<< _T("SIZE ") << fileName
;
905 bool ok
= CheckCommand(command
, '2');
909 // The answer should be one line: "213 <filesize>\n"
910 // 213 is File Status (STD9)
911 // "SIZE" is not described anywhere..? It works on most servers
913 if ( wxSscanf(GetLastResult().c_str(), _T("%i %i"),
914 &statuscode
, &filesize
) == 2 )
916 // We've gotten a good reply.
921 // Something bad happened.. A "2yz" reply with no size
927 // Set transfermode back to the original. Only the "SIZE"-command
928 // is dependant on transfermode
929 if ( oldTransfermode
!= NONE
)
931 SetTransferMode(oldTransfermode
);
934 // this is not a direct else clause.. The size command might return an
935 // invalid "2yz" reply
938 // The server didn't understand the "SIZE"-command or it
939 // returned an invalid reply.
940 // We now try to get details for the file with a "LIST"-command
941 // and then parse the output from there..
942 wxArrayString fileList
;
943 if ( GetList(fileList
, fileName
, true) )
945 if ( !fileList
.IsEmpty() )
947 // We _should_ only get one line in return, but just to be
948 // safe we run through the line(s) returned and look for a
949 // substring containing the name we are looking for. We
950 // stop the iteration at the first occurrence of the
951 // filename. The search is not case-sensitive.
952 bool foundIt
= false;
955 for ( i
= 0; !foundIt
&& i
< fileList
.GetCount(); i
++ )
957 foundIt
= fileList
[i
].Upper().Contains(fileName
.Upper());
962 // The index i points to the first occurrence of
963 // fileName in the array Now we have to find out what
964 // format the LIST has returned. There are two
965 // "schools": Unix-like
967 // '-rw-rw-rw- owner group size month day time filename'
971 // 'date size filename'
973 // check if the first character is '-'. This would
974 // indicate Unix-style (this also limits this function
975 // to searching for files, not directories)
976 if ( fileList
[i
].Mid(0, 1) == _T("-") )
979 if ( wxSscanf(fileList
[i
].c_str(),
980 _T("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
983 // Hmm... Invalid response
984 wxLogTrace(FTP_TRACE_MASK
,
985 _T("Invalid LIST response"));
988 else // Windows-style response (?)
990 if ( wxSscanf(fileList
[i
].c_str(),
991 _T("%*s %*s %i %*s"),
994 // something bad happened..?
995 wxLogTrace(FTP_TRACE_MASK
,
996 _T("Invalid or unknown LIST response"));
1005 // filesize might still be -1 when exiting
1009 #endif // wxUSE_PROTOCOL_FTP