1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/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
, unsigned short port
)
165 else if (!addr
.Service(wxT("ftp")))
168 return Connect(addr
);
175 m_lastError
= wxPROTO_STREAMING
;
181 if ( !CheckCommand(wxT("QUIT"), '2') )
183 m_lastError
= wxPROTO_CONNERR
;
184 wxLogDebug(wxT("Failed to close connection gracefully."));
188 return wxSocketClient::Close();
191 // ============================================================================
193 // ============================================================================
195 wxSocketBase
*wxFTP::AcceptIfActive(wxSocketBase
*sock
)
200 // now wait for a connection from server
201 wxSocketServer
*sockSrv
= (wxSocketServer
*)sock
;
202 if ( !sockSrv
->WaitForAccept() )
204 m_lastError
= wxPROTO_CONNERR
;
205 wxLogError(_("Timeout while waiting for FTP server to connect, try passive mode."));
210 m_lastError
= wxPROTO_NOERR
;
211 sock
= sockSrv
->Accept(true);
224 if ( !CheckCommand(wxT("ABOR"), '4') )
227 return CheckResult('2');
231 // ----------------------------------------------------------------------------
232 // Send command to FTP server
233 // ----------------------------------------------------------------------------
235 char wxFTP::SendCommand(const wxString
& command
)
239 m_lastError
= wxPROTO_STREAMING
;
243 wxString tmp_str
= command
+ wxT("\r\n");
244 const wxWX2MBbuf tmp_buf
= tmp_str
.mb_str();
245 if ( Write(wxMBSTRINGCAST tmp_buf
, strlen(tmp_buf
)).Error())
247 m_lastError
= wxPROTO_NETERR
;
251 // don't show the passwords in the logs (even in debug ones)
252 wxString cmd
, password
;
253 if ( command
.Upper().StartsWith(wxT("PASS "), &password
) )
255 cmd
<< wxT("PASS ") << wxString(wxT('*'), password
.length());
264 m_lastError
= wxPROTO_NOERR
;
268 // ----------------------------------------------------------------------------
269 // Receive servers reply
270 // ----------------------------------------------------------------------------
272 char wxFTP::GetResult()
274 // if we've already had a read or write timeout error, the connection is
275 // probably toast, so don't bother, it just wastes the users time
276 if ( m_bEncounteredError
)
281 // m_lastResult will contain the entire server response, possibly on
283 m_lastResult
.clear();
285 // we handle multiline replies here according to RFC 959: it says that a
286 // reply may either be on 1 line of the form "xyz ..." or on several lines
287 // in whuch case it looks like
291 // and the intermeidate lines may start with xyz or not
292 bool badReply
= false;
293 bool firstLine
= true;
294 bool endOfReply
= false;
295 while ( !endOfReply
&& !badReply
)
298 m_lastError
= ReadLine(this,line
);
301 m_bEncounteredError
= true;
307 if ( !m_lastResult
.empty() )
309 // separate from last line
310 m_lastResult
+= wxT('\n');
313 m_lastResult
+= line
;
315 // unless this is an intermediate line of a multiline reply, it must
316 // contain the code in the beginning and '-' or ' ' following it
317 if ( line
.Len() < LEN_CODE
+ 1 )
324 else // line has at least 4 chars
326 // this is the char which tells us what we're dealing with
327 wxChar chMarker
= line
.GetChar(LEN_CODE
);
331 code
= wxString(line
, LEN_CODE
);
348 else // subsequent line of multiline reply
350 if ( line
.compare(0, LEN_CODE
, code
) == 0 )
352 if ( chMarker
== wxT(' ') )
363 wxLogDebug(wxT("Broken FTP server: '%s' is not a valid reply."),
364 m_lastResult
.c_str());
366 m_lastError
= wxPROTO_PROTERR
;
371 m_lastError
= wxPROTO_NOERR
;
373 // if we got here we must have a non empty code string
374 return (char)code
[0u];
377 // ----------------------------------------------------------------------------
378 // wxFTP simple commands
379 // ----------------------------------------------------------------------------
381 bool wxFTP::SetTransferMode(TransferMode transferMode
)
383 if ( transferMode
== m_currentTransfermode
)
390 switch ( transferMode
)
393 wxFAIL_MSG(wxT("unknown FTP transfer mode"));
405 if ( !DoSimpleCommand(wxT("TYPE"), mode
) )
407 wxLogError(_("Failed to set FTP transfer mode to %s."),
408 (transferMode
== ASCII
? _("ASCII") : _("binary")));
413 // If we get here the operation has been successfully completed
414 // Set the status-member
415 m_currentTransfermode
= transferMode
;
420 bool wxFTP::DoSimpleCommand(const wxChar
*command
, const wxString
& arg
)
422 wxString fullcmd
= command
;
425 fullcmd
<< wxT(' ') << arg
;
428 if ( !CheckCommand(fullcmd
, '2') )
430 wxLogDebug(wxT("FTP command '%s' failed."), fullcmd
.c_str());
431 m_lastError
= wxPROTO_NETERR
;
436 m_lastError
= wxPROTO_NOERR
;
440 bool wxFTP::ChDir(const wxString
& dir
)
442 // some servers might not understand ".." if they use different directory
443 // tree conventions, but they always understand CDUP - should we use it if
444 // dir == ".."? OTOH, do such servers (still) exist?
446 return DoSimpleCommand(wxT("CWD"), dir
);
449 bool wxFTP::MkDir(const wxString
& dir
)
451 return DoSimpleCommand(wxT("MKD"), dir
);
454 bool wxFTP::RmDir(const wxString
& dir
)
456 return DoSimpleCommand(wxT("RMD"), dir
);
459 wxString
wxFTP::Pwd()
463 if ( CheckCommand(wxT("PWD"), '2') )
465 // the result is at least that long if CheckCommand() succeeded
466 wxString::const_iterator p
= m_lastResult
.begin() + LEN_CODE
+ 1;
467 if ( *p
!= wxT('"') )
469 wxLogDebug(wxT("Missing starting quote in reply for PWD: %s"),
470 wxString(p
, m_lastResult
.end()));
474 for ( ++p
; (bool)*p
; ++p
) // FIXME-DMARS
476 if ( *p
== wxT('"') )
478 // check if the quote is doubled
480 if ( !*p
|| *p
!= wxT('"') )
482 // no, this is the end
485 //else: yes, it is: this is an embedded quote in the
486 // filename, treat as normal char
494 wxLogDebug(wxT("Missing ending quote in reply for PWD: %s"),
495 m_lastResult
.c_str() + LEN_CODE
+ 1);
501 m_lastError
= wxPROTO_PROTERR
;
502 wxLogDebug(wxT("FTP PWD command failed."));
508 bool wxFTP::Rename(const wxString
& src
, const wxString
& dst
)
512 str
= wxT("RNFR ") + src
;
513 if ( !CheckCommand(str
, '3') )
516 str
= wxT("RNTO ") + dst
;
518 return CheckCommand(str
, '2');
521 bool wxFTP::RmFile(const wxString
& path
)
524 str
= wxT("DELE ") + path
;
526 return CheckCommand(str
, '2');
529 // ----------------------------------------------------------------------------
530 // wxFTP port methods
531 // ----------------------------------------------------------------------------
533 wxSocketBase
*wxFTP::GetPort()
536 PASSIVE: Client sends a "PASV" to the server. The server responds with
537 an address and port number which it will be listening on. Then
538 the client connects to the server at the specified address and
541 ACTIVE: Client sends the server a PORT command which includes an
542 address and port number which the client will be listening on.
543 The server then connects to the client at that address and
547 wxSocketBase
*socket
= m_bPassive
? GetPassivePort() : GetActivePort();
550 m_bEncounteredError
= true;
554 // Now set the time for the new socket to the default or user selected
556 socket
->SetTimeout(m_uiDefaultTimeout
);
561 wxString
wxFTP::GetPortCmdArgument(const wxIPV4address
& addrLocal
,
562 const wxIPV4address
& addrNew
)
564 // Just fills in the return value with the local IP
565 // address of the current socket. Also it fill in the
566 // PORT which the client will be listening on
568 wxString addrIP
= addrLocal
.IPAddress();
569 int portNew
= addrNew
.Service();
571 // We need to break the PORT number in bytes
572 addrIP
.Replace(wxT("."), wxT(","));
574 << wxString::Format(wxT("%d"), portNew
>> 8) << wxT(',')
575 << wxString::Format(wxT("%d"), portNew
& 0xff);
577 // Now we have a value like "10,0,0,1,5,23"
581 wxSocketBase
*wxFTP::GetActivePort()
583 // we need an address to listen on
584 wxIPV4address addrNew
, addrLocal
;
586 addrNew
.AnyAddress();
587 addrNew
.Service(0); // pick an open port number.
589 wxSocketServer
*sockSrv
= new wxSocketServer(addrNew
);
592 // We use Ok() here to see if everything is ok
593 m_lastError
= wxPROTO_PROTERR
;
598 //gets the new address, actually it is just the port number
599 sockSrv
->GetLocal(addrNew
);
601 // Now we create the argument of the PORT command, we send in both
602 // addresses because the addrNew has an IP of "0.0.0.0", so we need the
603 // value in addrLocal
604 wxString port
= GetPortCmdArgument(addrLocal
, addrNew
);
605 if ( !DoSimpleCommand(wxT("PORT"), port
) )
607 m_lastError
= wxPROTO_PROTERR
;
609 wxLogError(_("The FTP server doesn't support the PORT command."));
613 m_lastError
= wxPROTO_NOERR
;
614 sockSrv
->Notify(false); // Don't send any events
618 wxSocketBase
*wxFTP::GetPassivePort()
620 if ( !DoSimpleCommand(wxT("PASV")) )
622 m_lastError
= wxPROTO_PROTERR
;
623 wxLogError(_("The FTP server doesn't support passive mode."));
627 size_t addrStart
= m_lastResult
.find(wxT('('));
628 size_t addrEnd
= (addrStart
== wxString::npos
)
630 : m_lastResult
.find(wxT(')'), addrStart
);
632 if ( addrEnd
== wxString::npos
)
634 m_lastError
= wxPROTO_PROTERR
;
638 // get the port number and address
640 wxString
straddr(m_lastResult
, addrStart
+ 1, addrEnd
- (addrStart
+ 1));
641 wxSscanf(straddr
, wxT("%d,%d,%d,%d,%d,%d"),
642 &a
[2],&a
[3],&a
[4],&a
[5],&a
[0],&a
[1]);
644 wxUint32 hostaddr
= (wxUint16
)a
[2] << 24 |
645 (wxUint16
)a
[3] << 16 |
646 (wxUint16
)a
[4] << 8 |
648 wxUint16 port
= (wxUint16
)(a
[0] << 8 | a
[1]);
651 addr
.Hostname(hostaddr
);
654 wxSocketClient
*client
= new wxSocketClient();
655 if ( !client
->Connect(addr
) )
657 m_lastError
= wxPROTO_CONNERR
;
662 client
->Notify(false);
664 m_lastError
= wxPROTO_NOERR
;
669 // ----------------------------------------------------------------------------
670 // wxFTP download and upload
671 // ----------------------------------------------------------------------------
673 class wxInputFTPStream
: public wxSocketInputStream
676 wxInputFTPStream(wxFTP
*ftp
, wxSocketBase
*sock
)
677 : wxSocketInputStream(*sock
)
680 // socket timeout automatically set in GetPort function
683 virtual ~wxInputFTPStream()
685 delete m_i_socket
; // keep at top
687 // when checking the result, the stream will
688 // almost always show an error, even if the file was
689 // properly transfered, thus, lets just grab the result
691 // we are looking for "226 transfer completed"
692 char code
= m_ftp
->GetResult();
695 // it was a good transfer.
697 m_ftp
->m_streaming
= false;
703 // the connection is probably toast. issue an abort, and
704 // then a close. there won't be any more waiting
705 // for this connection
710 // There was a problem with the transfer and the server
711 // has acknowledged it. If we issue an "ABORT" now, the user
712 // would get the "226" for the abort and think the xfer was
713 // complete, thus, don't do anything here, just return
718 wxDECLARE_NO_COPY_CLASS(wxInputFTPStream
);
721 class wxOutputFTPStream
: public wxSocketOutputStream
724 wxOutputFTPStream(wxFTP
*ftp_clt
, wxSocketBase
*sock
)
725 : wxSocketOutputStream(*sock
), m_ftp(ftp_clt
)
729 virtual ~wxOutputFTPStream(void)
733 // close data connection first, this will generate "transfer
738 m_ftp
->GetResult(); // save result so user can get to it
740 m_ftp
->m_streaming
= false;
744 // abort data connection first
747 // and close it after
754 wxDECLARE_NO_COPY_CLASS(wxOutputFTPStream
);
757 wxInputStream
*wxFTP::GetInputStream(const wxString
& path
)
759 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
761 m_lastError
= wxPROTO_CONNERR
;
765 wxSocketBase
*sock
= GetPort();
769 m_lastError
= wxPROTO_NETERR
;
773 wxString tmp_str
= wxT("RETR ") + wxURI::Unescape(path
);
774 if ( !CheckCommand(tmp_str
, '1') )
777 sock
= AcceptIfActive(sock
);
780 m_lastError
= wxPROTO_CONNERR
;
784 sock
->SetFlags(wxSOCKET_WAITALL
);
788 wxInputFTPStream
*in_stream
= new wxInputFTPStream(this, sock
);
790 m_lastError
= wxPROTO_NOERR
;
794 wxOutputStream
*wxFTP::GetOutputStream(const wxString
& path
)
796 if ( ( m_currentTransfermode
== NONE
) && !SetTransferMode(BINARY
) )
798 m_lastError
= wxPROTO_CONNERR
;
802 wxSocketBase
*sock
= GetPort();
804 wxString tmp_str
= wxT("STOR ") + path
;
805 if ( !CheckCommand(tmp_str
, '1') )
808 sock
= AcceptIfActive(sock
);
812 m_lastError
= wxPROTO_NOERR
;
813 return new wxOutputFTPStream(this, sock
);
816 // ----------------------------------------------------------------------------
817 // FTP directory listing
818 // ----------------------------------------------------------------------------
820 bool wxFTP::GetList(wxArrayString
& files
,
821 const wxString
& wildcard
,
824 wxSocketBase
*sock
= GetPort();
826 m_lastError
= wxPROTO_NETERR
;
830 // NLST : List of Filenames (including Directory's !)
831 // LIST : depending on BS of FTP-Server
832 // - Unix : result like "ls" command
833 // - Windows : like "dir" command
835 wxString
line(details
? wxT("LIST") : wxT("NLST"));
836 if ( !wildcard
.empty() )
838 line
<< wxT(' ') << wildcard
;
841 if ( !CheckCommand(line
, '1') )
843 m_lastError
= wxPROTO_PROTERR
;
844 wxLogDebug(wxT("FTP 'LIST' command returned unexpected result from server"));
849 sock
= AcceptIfActive(sock
);
851 m_lastError
= wxPROTO_CONNERR
;
856 while (ReadLine(sock
, line
) == wxPROTO_NOERR
)
863 // the file list should be terminated by "226 Transfer complete""
864 m_lastError
= wxPROTO_NOERR
;
865 return CheckResult('2');
868 bool wxFTP::FileExists(const wxString
& fileName
)
870 // This function checks if the file specified in fileName exists in the
871 // current dir. It does so by simply doing an NLST (via GetList).
872 // If this succeeds (and the list is not empty) the file exists.
875 wxArrayString fileList
;
877 if ( GetList(fileList
, fileName
, false) )
879 // Some ftp-servers (Ipswitch WS_FTP Server 1.0.5 does this)
880 // displays this behaviour when queried on a nonexistent file:
881 // NLST this_file_does_not_exist
882 // 150 Opening ASCII data connection for directory listing
883 // (no data transferred)
884 // 226 Transfer complete
885 // Here wxFTP::GetList(...) will succeed but it will return an empty
887 retval
= !fileList
.IsEmpty();
893 // ----------------------------------------------------------------------------
895 // ----------------------------------------------------------------------------
897 int wxFTP::GetFileSize(const wxString
& fileName
)
899 // return the filesize of the given file if possible
900 // return -1 otherwise (predominantly if file doesn't exist
905 // Check for existance of file via wxFTP::FileExists(...)
906 if ( FileExists(fileName
) )
910 // First try "SIZE" command using BINARY(IMAGE) transfermode
911 // Especially UNIX ftp-servers distinguish between the different
912 // transfermodes and reports different filesizes accordingly.
913 // The BINARY size is the interesting one: How much memory
914 // will we need to hold this file?
915 TransferMode oldTransfermode
= m_currentTransfermode
;
916 SetTransferMode(BINARY
);
917 command
<< wxT("SIZE ") << fileName
;
919 bool ok
= CheckCommand(command
, '2');
923 // The answer should be one line: "213 <filesize>\n"
924 // 213 is File Status (STD9)
925 // "SIZE" is not described anywhere..? It works on most servers
927 if ( wxSscanf(GetLastResult().c_str(), wxT("%i %i"),
928 &statuscode
, &filesize
) == 2 )
930 // We've gotten a good reply.
935 // Something bad happened.. A "2yz" reply with no size
941 // Set transfermode back to the original. Only the "SIZE"-command
942 // is dependant on transfermode
943 if ( oldTransfermode
!= NONE
)
945 SetTransferMode(oldTransfermode
);
948 // this is not a direct else clause.. The size command might return an
949 // invalid "2yz" reply
952 // The server didn't understand the "SIZE"-command or it
953 // returned an invalid reply.
954 // We now try to get details for the file with a "LIST"-command
955 // and then parse the output from there..
956 wxArrayString fileList
;
957 if ( GetList(fileList
, fileName
, true) )
959 if ( !fileList
.IsEmpty() )
961 // We _should_ only get one line in return, but just to be
962 // safe we run through the line(s) returned and look for a
963 // substring containing the name we are looking for. We
964 // stop the iteration at the first occurrence of the
965 // filename. The search is not case-sensitive.
966 const size_t numFiles
= fileList
.size();
968 for ( i
= 0; i
< fileList
.GetCount(); i
++ )
970 if ( fileList
[i
].Upper().Contains(fileName
.Upper()) )
976 // The index i points to the first occurrence of
977 // fileName in the array Now we have to find out what
978 // format the LIST has returned. There are two
979 // "schools": Unix-like
981 // '-rw-rw-rw- owner group size month day time filename'
985 // 'date size filename'
987 // check if the first character is '-'. This would
988 // indicate Unix-style (this also limits this function
989 // to searching for files, not directories)
990 if ( fileList
[i
].Mid(0, 1) == wxT("-") )
993 if ( wxSscanf(fileList
[i
].c_str(),
994 wxT("%*s %*s %*s %*s %i %*s %*s %*s %*s"),
997 // Hmm... Invalid response
998 wxLogDebug(wxT("Invalid LIST response"));
1001 else // Windows-style response (?)
1003 if ( wxSscanf(fileList
[i
].c_str(),
1004 wxT("%*s %*s %i %*s"),
1007 // something bad happened..?
1008 wxLogDebug(wxT("Invalid or unknown LIST response"));
1017 // filesize might still be -1 when exiting
1021 #endif // wxUSE_PROTOCOL_FTP